/***************************************************************************
                          BarTime.cpp  -  description
                             -------------------
    begin                : Fre Jul 23 2004
    copyright            : (C) 2004 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 "BarTime.h"
#include "MetricTree.h"
#include "Misc.h"


uint BarPosition::getNearestTick(const uint quant)
{
   int retTicks = 0;
   uint minDiff = m_pSig->m_nTicks;
   int gridPoint =  m_pSig->m_nTicks/quant;

   for(uint i = m_nTicks/gridPoint; i<=m_nTicks/gridPoint+1&& minDiff != 0; ++i){
      uint tmp = i*gridPoint;
      if((uint)abs((int)(m_nTicks-tmp)) <minDiff){
         minDiff = abs((int)(m_nTicks-tmp));
         retTicks = tmp;
      }
   }
   return retTicks;
}

bool BarPosition::isQuantized(const uint quant)
{
   if(m_nTicks == 0) return true;
   uint divTick = m_pSig->m_nTicks/quant;

   if(divTick && m_nTicks % divTick){
      return false;
   }
   return true;   
}

int BarPosition::quantize(const uint quant)
{
   uint newTicks = 0;
   int quantError = 0;
   if(m_nTicks== 0 ) return quantError;

   newTicks    =  getNearestTick(quant);
   quantError  =  newTicks -m_nTicks;
   m_nTicks    =  newTicks;
   if(m_nTicks== m_pSig->m_nTicks){
      m_nTicks = 0;
      m_nBeat++;
      if(m_nBeat>m_pSig->m_nCount){
         m_nBar++;
         m_nBeat = 1;
      }
   }
   return quantError;
}

void  BarPosition::addTicks(const uint ticks)
{
   m_nTicks += ticks;
   if(m_nTicks>=m_pSig->m_nTicks){
      uint beats = m_nTicks/m_pSig->m_nTicks;
      addBeats(beats);
      m_nTicks = m_nTicks % m_pSig->m_nTicks;
   }
   
}

void  BarPosition::addBeats(const uint beats)
{
    m_nBeat += beats;
    if(m_nBeat>m_pSig->m_nCount){
       uint bars = (m_nBeat-1)/m_pSig->m_nCount;
       m_nBar += bars;
       m_nBeat = ((m_nBeat-1) % m_pSig->m_nCount)+1;
    }
}

void  BarPosition::addBars(const uint bars)
{
   m_nBar += bars;
}

bool BarPosition::addNth(const uint reci, const uint count)
{
   uint quarter = reci/4;
   if(quarter>1){
      if( (m_pSig->m_nTicks % quarter) != 0)  return false;
      for(uint i = 0;i<count; ++i){
         addTicks(m_pSig->m_nTicks/quarter);
      }
   }
   else{
      quarter = 4/reci;
      if(4 % reci != 0) return false;
      addBeats(count * quarter);
   }
   
   return true;
}
uint BarPosition::getUnitTicks(const uint nTimeBase)const
{
   uint unitCount = getSignature().m_nTicks/nTimeBase/m_nBeatDivision;
   return unitCount * nTimeBase;
}

uint  BarPosition::getTicksToNextBeat()
{
   return m_pSig->m_nTicks-m_nTicks;
}

BarTimeline& BarTimeline::operator=(const BarTimeline& btl)
{
   clear();
   for( uint i = 0; i<btl.size(); ++i){
      push_back(btl[i]);
   }
   m_nTimeBase = btl.m_nTimeBase;
   return *this;
}

void  BarTimeline::normalize()
{
   uint offset  = 0;
   if(empty()) return;
   BarPosition barPos = *begin();

   if(barPos.getBeat() == 1 && barPos.getTicks() == 0){
      offset = 1;
   }

   if(barPos.getBar() == 1){
      if(offset == 1){
         return;
      }
   }
   for(barIter it = begin() ; it != end(); ++it){
      it->setBar(it->getBar() - barPos.getBar() + offset);
   }
}

bool  BarTimeline::isQuantized(const uint quant)
{
   for(barIter it = begin(); it != end(); ++it){
      if(!it->isQuantized(quant))return false;
   }
   return true;
}

BarTimeline BarTimeline::quantize(const uint quant, indizes& removed)
{
   BarTimeline tmp;
   BarTimeline ret;
   uint i = 0, j = 0;
   for( barIter it = begin(); it != end(); ++it){
      if(!it->isQuantized(quant))
         it->quantize(quant);
      tmp.push_back(*it);
   }
   tmp.dump();   
   for(i = 0; i<tmp.size();){
      ret.push_back(tmp[i]);
      for(j = 1; i+j<tmp.size(); ++j){
         if(tmp[i] != tmp[i+j]) break;
         removed.push_back(i+j);
         //cerr<<"Found duplicate BarPosition at "<<i+j<<endl;
      }
      i += j;
   }
   
   return ret;
}

void  BarTimeline::dump()
{
   for( barIter it = begin(); it != end(); ++it){
      it->dump();
   }   
}

durations BarTimeline::getTickDurations() const
{
   durations ret;

   for(const_barIter it = begin(); it != end()-1; ++it){
      BarPosition pos1 = *it;
      BarPosition pos2 = *(it+1);
      //pos1.dump();
      //pos2.dump();
      uint barTicks = pos1.getSignature().getBarTicks();
      if(pos2<=pos1){
         cerr<<"Invalid BarTimeline."<<endl;
         return ret;
      }
      uint diffBar    = pos2.getBar()- pos1.getBar();
      uint tTicks1 = pos1.getTotalTicks();
      //uint tTicks2 = diffBar * barTicks + pos2.getTotalTicks();
      uint tTicks2 = pos2.getTotalTicks();
      //cerr<<"Duration = "<<(tTicks2-tTicks1)<<endl;
      ret.push_back(tTicks2-tTicks1);
   }
   return ret;
}

/*
int BarPosition::getBeatLevel(const uint nTimeBase)const
{
   if(nTimeBase == 0) return 0;
   uint nCount = getSignature().m_nCount;
   uint ticks = getTicks();
   uint beat = getBeat();
   if(ticks == 0){
      MetricTree mTree(nCount);
      eBeatType bt1 = mTree.getBeatType(beat);
      if(bt1 == eBeat_unknown) return -999;
      if(bt1 == eBeat_primary) return 1;
      return 0;
   }
   uint unitCount = getSignature().m_nTicks/nTimeBase/m_nBeatDivision;
   uint redTicks = ticks/(unitCount*nTimeBase) + 1;
   uint bonus  = 0;
   
   if(m_nBeatDivision >3){
      bonus = -1;
      uint k = m_nBeatDivision;
      while(k % 2 == 0){
         bonus++;
         k /=2;
      }
      k = m_nBeatDivision ;
      while(k % 3 == 0){
         bonus++;
         k /=3;
      }
   }
   MetricTree mTree(m_nBeatDivision);
   eBeatType bt1 = mTree.getBeatType(redTicks);
   if(bt1 == eBeat_unknown) return -999;
   if(bt1 == eBeat_primary) return -0 - bonus;
   return -1 -bonus;   
}
*/
int BarTimeline::getBeatLevel(const uint idx)const
{
   if(idx>=size()) return 0;
   return at(idx).getBeatLevel(m_nTimeBase);
}

int BarPosition::getBeatLevel(const uint nTimeBase)const
{
   if(nTimeBase == 0) return 0;
   uint nCount = getSignature().m_nCount;
   uint ticks  = getTicks();
   uint beat   = getBeat();
   if(ticks == 0){
      MetricTree2 mTree(nCount);
      return mTree.getBeatLevel(beat);
   }   
   uint unitCount = getSignature().m_nTicks/nTimeBase/m_nBeatDivision;
   uint redTicks = ticks/(unitCount*nTimeBase) + 1;
   MetricTree2 mTree(m_nBeatDivision);
   int depth   = static_cast<int>(mTree.getDepth());
   int bl      = static_cast<int>(mTree.getBeatLevel(redTicks));
   return bl - depth;
}

uint BarTimeline::isSyncope(const uint idx, const int level)const
{
   if(idx>=size()) return 0;
   int bl1 = at(idx).getBeatLevel(m_nTimeBase);
   uint nCount = 0;
   uint div = getBeatDivision(idx, nCount);
   
   //at(idx).dump();
   //cerr<<"Beat level: "<<bl1<<endl;
   //cerr<<"Div ="<<div<<endl<<endl;
   
   if(bl1 != level-1) return 0;
   if(idx<size()-1){
      int bl2 = at(idx+1).getBeatLevel( m_nTimeBase);
      //if(bl1 == bl2) return 1;
      uint diff = diffTicks(idx, idx+1);
      uint unitTicks = at(idx).getSignature().m_nTicks/at(idx).getBeatDivision();
      MetricTree2 mTree(at(idx).getBeatDivision());
      uint minSpan = 0;
      switch(level){
         case 1:
            minSpan = at(idx).getSignature().m_nTicks;
            break;
         case 0:
            minSpan = 0;
            minSpan = unitTicks * mTree.getMinUnit();
            break;
         case -1:
            if(mTree.getDepth()>2){
               minSpan = at(idx).getUnitTicks(m_nTimeBase)* mTree.getMainDivision();
            }
            else{
               minSpan = at(idx).getUnitTicks(m_nTimeBase);            
            }
            break;
         case -2:
            minSpan = at(idx).getUnitTicks(m_nTimeBase);
            break;
      }
      //cerr<<"Diff = "<<diff<<" min. span = "<<minSpan<<endl;
      if(diff>minSpan) return 1;
      return 0;
   }
   return 1;
}

uint BarTimeline::diffTicks(const uint idx1, const uint idx2) const
{
   if(idx1>=size() || idx2>=size()) return uint(-1);
   uint i = idx1>=idx2 ? idx2 : idx1;
   uint j = idx1>=idx2 ? idx1 : idx2;
   uint sumTicks = 0;
   uint diff = at(j).getTotalTicks()- at(i).getTotalTicks();

   return diff;
}

uint BarTimeline::getTimebase() const
{
   uint minTicks = 0;
   
   if(m_nTimeBase != 0) return m_nTimeBase;
   
   durations dur = getTickDurations();

   if(dur.empty()) return minTicks;
   if(dur.size() == 1 ) return at(0).getSignature().m_nTicks;
   minTicks = ggT(dur[0], dur[1]);
   //cerr<<"Start minticks = "<<minTicks<<endl;
   for(uint i = 2; i<dur.size(); ++i){
      uint ticks = ggT(dur[i], minTicks);
      //cerr<<"Ticks = "<<ticks<<" minticks = "<<minTicks<<endl;
      if(ticks<minTicks) minTicks = ticks;
   }
   //cerr<<"End minticks = "<<minTicks<<endl;

   return minTicks;
}

void  BarTimeline::rescaleTicks(uint nFactor)
{
   for( barIter it = begin(); it != end(); ++it){
      it->setTicks(it->getTicks() * nFactor);
   }   
}
uint BarTimeline::getBeatDivision(const uint i, uint& nCount )const
{
   nCount      =  1;
   if(i>=size() || m_nTimeBase == 0) return 0;
   uint k = i;
   uint division = at(i).getSignature().m_nTicks/m_nTimeBase;
   if(division==0) return 1;
   uint min = division;
   vector<uint> units;
   while(true){
      if(k >= size()|| at(k).getBeat() > at(i).getBeat() || at(k).getBar() > at(i).getBar()) break;
      uint unit = at(k).getTicks()/m_nTimeBase;
      if(at(k).getTicks() !=0){ 
         uint v = ggT(division, unit);
         //if(v==1) return division;
         if(v<=min) min = v;
      }
      k++;
   }
   nCount = k-i;
   return division/min;
}

void  BarTimeline::setBeatDivisions()
{
   for(uint i = 0; i<size();){
      uint nCount = 1;
      uint div = getBeatDivision(i, nCount);
      for(uint k = i; k<i+nCount; ++k){
         at(k).setBeatDivision(div);
         //at(k).dump();
      }
      i += nCount;
   }
}

BarPosition BarPosition::nextFullBeat()const
{
   BarPosition ret(*this);
   ret.m_nBeat++;
   ret.m_nTicks = 0;
   if(ret.m_nBeat> m_pSig->m_nCount){
      ret.m_nBar +=1;
      ret.m_nBeat = 1;
   }
   return ret;
}

BarPosition BarPosition::nextPrimaryBeat()const
{
   BarPosition ret(*this);
   MetricTree mTree(m_pSig->m_nCount);

   ret.m_nTicks = 0;

   for(uint i = ret.m_nBeat+1; i<=m_pSig->m_nCount; ++i){  
      ret.m_nBeat = i;
      eBeatType bt1 = mTree.getBeatType(ret.m_nBeat);
      if(bt1 == eBeat_primary){
         return ret;
      }
   }
   if(ret.m_nBeat==m_pSig->m_nCount ){
      ret.m_nBar +=1;
      ret.m_nBeat = 1;
   }
   return ret;
}

