/***************************************************************************
                          Segmnter2.cpp  -  description
                             -------------------
    begin                : Do Feb 16 2005
    copyright            : (C) 2005 by klaus frieler
    email                : kgf@omniversum.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "Segmenter2.h"

#define INVLOG2 1.4427

namespace Segmenter{
   using namespace Transformations;
MRESULT LinearBoundaryCluster::getBoundaryStrengths(Vector& ret)const
{
   return getBoundaryStrengths(ret, 0, END);
}

bool  BoundaryCluster::isValid()const
{
   if(m_bounds.empty()) return false;
   const Melody* pMel = m_bounds[0]->getMelody();
   if(!pMel || pMel->empty()) return false;
   for(uint i = 1; i<m_bounds.size(); ++i){
      if(m_bounds[i]->getMelody() != pMel) return false;
   }
   return true;
}

MRESULT LinearBoundaryCluster::getBoundaryStrengths(Vector& ret, const uint start, uint end)const
{
   ret.clear();
   if(!isValid()){
      return ERR_BAD_OBJECT;
   }
   for(uint i = 0; i<m_bounds.size(); ++i){
      Vector tmp;
      MRESULT mr = m_bounds[i]->getBoundaryStrengths(tmp, start, end);
      if(FAIL(mr)) return mr;
   
      ret +=  tmp;
   }
   ret *= m_dFactor;
   /*
   cout<<"[Linear] Start = "<<start<<" End = "<<end<<endl;
   ret.dump();
   */
   return SUCC_OK;
}

TemperleyGapScore::TemperleyGapScore(const Melody* pMel) : BoundaryBase(pMel)
{
   if(pMel){
      calcGapScores();
   }
}
MRESULT TemperleyGapScore::calcGapScores()
{
   MRESULT  mr = SUCC_OK;
   m_gapScores.clear();

   IOI   iois(m_pMel);
   OOI   oois(m_pMel);
   AverageIOI avIOI(m_pMel);
   
   Vector gap1, gap2, gap3;
   
   mr = iois.get(gap1);
   if(FAIL(mr)) return mr;
   
   mr = oois.get(gap2);   
   if(FAIL(mr)) return mr;
   
   mr = avIOI.get(gap3);
   if(FAIL(mr)) return mr;

   for(uint i = 0; i<gap1.size(); ++i){
      if(gap3[i] == 0){
         return ERR_UNEXPECTED;
      }
      double sum = (gap1[i] + gap2[i])/gap3[i];
      m_gapScores.push_back(sum);
   }

   return mr;
}

MRESULT TemperleyGapScore::getBoundaryStrengths(Vector& ret)const
{
   return getBoundaryStrengths(ret, 0, END);
}

MRESULT TemperleyGapScore::getBoundaryStrengths(Vector& ret, const uint start, uint end)const
{

   ret.clear();
   if(!isValid() || m_gapScores.empty() ){
      return ERR_BAD_OBJECT;
   }
   if(end == END){
      end = size()-1;
   }
   if(end<start || end>size()-1){
      return ERR_INVALIDARG;
   } 
   uint effStart = start-1, effEnd = end+1;

   if( start == 0 ){
      ret.push_back(0);
      effStart = start;
   }
   if(end == size()-1){
      effEnd = size()-1;
   }
   for(uint i = effStart; i<effEnd; ++i){
      double sum = m_gapScores[i];
      ret.push_back(sum * m_dFactor);
   }
   if(end == size()-1){
      ret.push_back(0);
   }

   /*
   cout<<"[Gap] Start = "<<start<<" End = "<<end<<endl;
   ret.dump();
   */
   //cout<<"Gap Bound0 = "<<ret[0]<<" gapEnd "<<ret[ret.size()-1]<<endl;

   return SUCC_OK;
}

MRESULT TemperleyLengthBoundary::getBoundaryStrengths(Vector& ret)const
{
   return getBoundaryStrengths(ret, 0, END);
}

MRESULT TemperleyLengthBoundary::getBoundaryStrengths(Vector& ret, const uint start, uint end)const
{
   ret.clear();
   if(!isValid() || m_dOptimalLength<1.0){
      return ERR_BAD_OBJECT;
   }
   if(end == END){
      end = size()-1;
   }
   if(end<start){
      return ERR_INVALIDARG;
   } 

   double len = (end-start+1)/m_dOptimalLength;
   double bound = m_dFactor*fabs(INVLOG2*log(len));
   ret.push_back(bound);
   for(uint i = 0; i<end-start; ++i){
      ret.push_back(0.0);
   }
   ret.push_back(bound);
   /*
   cout<<"[Length] Start = "<<start<<" End = "<<end<<endl;
   ret.dump();
   */
   return SUCC_OK;
}

MRESULT MetricParallelBoundary::getBoundaryStrengths(Vector& ret)const
{
   return getBoundaryStrengths(ret, 0, END);
}

MRESULT MetricParallelBoundary::getBoundaryStrengths(Vector& ret, const uint start, uint end)const
{
   ret.clear();
   
   if(!isValid()){
      return ERR_BAD_OBJECT;
   }
   
   if(end == END){
      end = size()-1;
   }
   if(end<start || end>size()-1){
      return ERR_INVALIDARG;
   }
   double bound = 0;
   if(end != size()-1) {
      Tone tone1     = m_pMel->at(start);
      Tone tone2     = m_pMel->at(end+1);
      bound          = m_dFactor*getPhasePenalty(tone1, tone2);
   }
   ret.push_back(bound);
   for(uint i = 0; i<end-start; ++i){
      ret.push_back(0.0);
   }
   ret.push_back(bound);
   /*
   cout<<"[Metric] Start = "<<start<<" End = "<<end<<endl;
   ret.dump();
   */
   return SUCC_OK;
}

double MetricParallelBoundary::getPhasePenalty(const Tone& tone1, const Tone& tone2)const
{
   BarPosition pos1  = tone1.getBarPosition(), pos2 = tone2.getBarPosition();
   Signature   sig   = pos1.getSignature();
   
   if(pos1 == invalidBarPosition || pos2 == invalidBarPosition) return 0.0;

   double penalty = m_dPhase4;
   if(pos1.getTicks() != pos2.getTicks()) return penalty;

   uint beat1 = pos1.getBeat(), beat2 = pos2.getBeat();
   if(beat1 == beat2) return 0.0;
   
   for(uint num = 2; num<6; ++num){
      if(sig.count()>num && sig.count()%num == 0){
         if((beat1-beat2)%(sig.count()/num)== 0){
            penalty = m_dPhase3;
            break;
         }
      }
   }
   return penalty;

}
LBDMBase::LBDMBase(const Melody* pMel, Transformation *pInterval): BoundaryBase(pMel), m_pInterval(pInterval)
{
   setIntervals();
}

MRESULT LBDMBase::setIntervals()
{
   MRESULT  mr = SUCC_OK;
   if(m_pMel && m_pInterval){
      mr = m_pInterval->get(m_intervals);
   }
   return mr;
}

MRESULT LBDMBase::getBoundaryStrengths(Vector& ret)const
{
   return getBoundaryStrengths(ret, 0, END);
}

MRESULT LBDMBase::getBoundaryStrengths(Vector& ret, const uint start, uint end)const
{
   ret.clear();
   if(!isValid()|| m_intervals.empty() || m_boundaries.empty()){ 
      return ERR_BAD_OBJECT;
   }
   if(end == END){
      end = size()-1;
   }
   if(end<start || end>size()-1){
      return ERR_INVALIDARG;
   } 
   uint effStart = start-1, effEnd = end+1;

   if( start == 0 ){
      ret.push_back(0);
      effStart = start;
   }
   if(end == size()-1){
      effEnd = size()-1;
   }
   for(uint i = effStart; i<effEnd; ++i){
      double sum = m_boundaries[i];
      ret.push_back(sum * m_dFactor);
   }
   if(end == size()-1){
      ret.push_back(0);
   }
   return SUCC_OK;
}


int PlainLBDM::getIntervalRelation(const double d1, const double d2) const
{
   return d1>=d2 ? (d1>d2 ? 1 : 0) : -1;
}

MRESULT PlainLBDM::calcBoundaries()
{
   double   sum = 0;
   int      rel1 = 0, rel2  = 0;

   if(!isValid()|| m_intervals.empty()){ 
      return ERR_BAD_OBJECT;
   }
   m_boundaries.push_back(0.0);

   for(uint i = 0; i<m_intervals.size(); ++i){
      sum   = 0.0;
      rel1  = 0;
      rel2  = 0;
      if(i>0){
         rel1 = getIntervalRelation(m_intervals[i-1], m_intervals[i]); 
         //cout<<"int[i-1] = "<<m_intervals[i-1]<<" int[i] = "<<m_intervals[i];
      }
      if(i<m_intervals.size()-1){
         rel2 = getIntervalRelation(m_intervals[i], m_intervals[i+1]); 
         //cout<<" int[i+1] = "<< m_intervals[i+1]<<endl;
      }
      //cout<<" R1 = "<<rel1<<" R2 = "<<rel2<<endl;
      if(rel2 != 0 ){
         sum += m_dBonus;
      }
      
      if(rel1 != 0){
         sum += m_dDiff;
         if(rel1<0) sum += m_dLarger;
       }
      if(rel2 != 0){
         sum += m_dDiff;
         if(rel2>0) sum += m_dLarger;
      }
      //cout<<"Sum = "<<sum<<endl;
      m_boundaries.push_back(sum);
   }
   m_boundaries.push_back(0.0);

   return SUCC_OK;
}
double RefinedLBDM::alpha(double x, double y)const
{
   double diff = x-y, sum = x+y;
   if(diff == 0 || sum == 0) return 0;
   diff = fabs(diff/(x+y));
   if(diff<1e-6) diff = 0.0;
   return diff;
}

MRESULT  RefinedLBDM::calcBoundaries()
{
   if(!isValid() || m_intervals.empty()){
      return ERR_BAD_OBJECT;
   }

   double   alpha1 = 0, alpha2 = 0, sum = 0;
   m_boundaries.push_back(0.0);
   for(uint i = 0; i<m_intervals.size(); ++i){
      alpha1 = 0; alpha2 = 0;
      if(i>0){
         alpha1 = m_intervals[i] * alpha(m_intervals[i-1], m_intervals[i]);
         //cout<<"m_intervals[i] = "<< m_intervals[i]<<" m_intervals[i-1] = "<<m_intervals[i-1]<<" alpha1 = "<<alpha1<<endl;
      }
      if(i<m_intervals.size()-1){
         alpha2 = m_intervals[i] * alpha(m_intervals[i], m_intervals[i+1]);
         //cout<<"intervals[i] = "<< m_intervals[i]<<" intervals[i+1] = "<<m_intervals[i+1]<<" alpha2 = "<<alpha2<<endl;
      }
      sum = alpha1+ alpha2;
      //cout<<"sum = "<<sum<<endl;
      m_boundaries.push_back(sum);
   }
   m_boundaries.push_back(0.0);

   double max = m_boundaries.getMax();
   if(max >0){
      m_boundaries *= 1/max;
   }

   return SUCC_OK;

}

MRESULT LocalMaxima::analyze(Vector& ret)const
{
   ret.clear();
   if(!m_pBoundary->isValid()){
      return ERR_BAD_OBJECT;
   }
   Vector   bounds;
   MRESULT mr = m_pBoundary->getBoundaryStrengths(bounds);

   if(FAIL(mr)) return mr;
   //bounds.dump("LocalMax\n");
   if(bounds.size()<3){
      return SUCC_FAIL;
   }
   double val = 0;
   for(uint i = 1; i<bounds.size()-1; ++i){
      val  = 0;
      double left = bounds[i-1], mid = bounds[i], right = bounds[i+1];
      //cout<<setprecision(2)<<"L: ="<<left<<" M: "<<mid<<" R: = "<<right<<endl;
      if(left <= mid && right<= mid){
         val = 1.0;
         if(left==mid || right==mid){
            val = 0.5;
         }
         if(left==mid && right==mid){
            val = 0.0;
         }
      }
      ret.push_back(val);
   }

   return SUCC_OK;
}

MRESULT LocalMaximaMaxima::analyze(Vector& ret)const
{
   ret.clear();
   if(!m_pBoundary->isValid()){
      return ERR_BAD_OBJECT;
   }
   Vector   bounds;
   MRESULT mr = m_pBoundary->getBoundaryStrengths(bounds);
   if(FAIL(mr)) return mr;

   if(bounds.size()<3){
      return SUCC_FAIL;
   }
   //remove (dummy) first and last
   bounds.pop_back();
   bounds.remove(0);

   iData pos1, pos2, pos3;
   Vector locMax     = bounds.getLocalMaxima(pos1);
   Vector loclocMax  = locMax.getLocalMaxima(pos2);

   for(uint i = 0; i<pos2.size();++i){
      pos2[i] = pos1[pos2[i]];
   }
   pos1.clear();
   for(uint i = 0; i<pos2.size();++i){
      for(uint j = 0; i+j+1<pos2.size() && pos2[i+j] == pos2[i+j+1]; ++j){++i;}
      pos1.push_back(pos2[i]);
   }

   for(uint i = 0; i<bounds.size(); ++i){
      ret.push_back(0.0);
   }
   for(uint i = 0; i<bounds.size(); ++i){
      for(uint j = 0; j < pos1.size(); ++j){
         if(i == pos1[j]){ 
            ret[i] = 1.0;
            break;
         }
      }
   }
   return SUCC_OK;
}

MRESULT TemperleyDPA::analyze(Vector& ret)const
{
   MRESULT mr = SUCC_OK;
   ret.clear();

   //sanity
   if(!m_pBoundary->isValid()){
      return ERR_BAD_OBJECT;
   }
   
   Vector   bounds;

   //init helpers
   uint sz = m_pBoundary->size();
   if(sz<=0){
      return ERR_UNEXPECTED;
   }
   double *analysis  = new double[sz];
   uint   *best      = new uint[sz];
   if(!analysis || !best){;
      return ERR_OUTOFMEMORY;
   }
   memset(analysis, 0, sizeof(double)*sz);
   memset(best, 0, sizeof(uint)*sz);

   //do actual analysis
   for(uint i = 0; i<sz-1; ++i){
      double bestScore = -1e3;
      //cout<<endl;
      for(uint j = 1; i>=j-1 && j <=m_iTestLength; ++j){
         double lenFactor = sqrt((double)j);
         mr = m_pBoundary->getBoundaryStrengths(bounds, i-j+1, i);
         if(FAIL(mr)) 
            break;
         double boundVal = (bounds[0]+bounds[bounds.size()-1])/2;
         if(j==i+1){
            //boundVal = bounds[bounds.size()-1];
         }
         double score = boundVal;
         if(m_bUseOverallLengthPenalty) score *= lenFactor;
         //score +=analysis[i-j];
         //cout<<"("<<i<<" "<<j<<") val = "<<boundVal<<" total score "<<score<<" best = "<<bestScore<<endl;
         //cout<<"("<<i<<" "<<j<<") val = "<<boundVal<<" bounds[0] = "<<bounds[0]<<" bounds["<<bounds.size()-1<<"] = "<<bounds[bounds.size()-1]<<endl;
         //cout<<"("<<i<<" "<<j<<") val = "<<boundVal<<" analysis = "<<analysis[i-j+1]<<" score "<<score<<endl;
         //cout<<"("<<i<<" "<<j<<") score = "<<score<<" ";
         //bounds.dump();
         //cout<<"("<<i<<" "<<j<<") val = "<<boundVal<<endl;
         if(score>bestScore){
            best[i+1] = j;
            analysis[i+1] = score;
            bestScore   = score;
            //cout<<"analysis = "<<analysis[i]<<" score = "<<score<<" Bestscore= "<<bestScore<<" Best i = "<<best[i]<<endl;
         }
      }
   }
   for(uint i = 0; i<sz;++i){
      //cout<<"New: Analysis["<<i<<"] = "<<analysis[i]<<" best = "<<best[i]<<endl;
   }
   memset(analysis, 0, sizeof(double)*sz);
   if(SUCCESS(mr)){
      int k = sz-1;
      while(k>0){
         //cout<<"k = "<<k<<" best[k] = "<<best[k]<<endl;
         k -= best[k];
         if(k<0) k = 0;
         analysis[k] = 1.0;
      }
   }
   for(uint i = 1; i<sz; ++i){
      ret.push_back(analysis[i]);
   }
   delete [] analysis;
   delete [] best;
   return mr;
}

//end of namespace
}