/***************************************************************************
                          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"

uint ggT(uint m, uint n)
{
  uint z = 0;
  while (n>0){
    z = n;
    n = m % n;
    m = z;
  }
  return m;
}

uint kgV(uint m, uint n)
{
  return (m*n)/ggT(m,n);
}

uint BarPosition::getNearestTick(const uint ticks, const uint division, const uint quant)
{
   int retTicks = 0;
   if(quant == 0) return ticks;
   uint minDiff = division;
   int gridPoint =  division/quant;
   if(gridPoint == 0) gridPoint = 1; 
   for(uint i = ticks/gridPoint; i<=ticks/gridPoint+1&& minDiff != 0; ++i){
      uint tmp = i*gridPoint;
      if((uint)abs((int)(ticks-tmp)) <minDiff){
         minDiff = abs((int)(ticks-tmp));
         retTicks = tmp;
      }
   }
   return retTicks;

}

uint BarPosition::getNearestTick(const uint quant)
{
   int retTicks = 0;
   uint minDiff = m_sig.ticks();
   int gridPoint =  m_sig.ticks()/quant;
   for(uint i = 0; i<=quant; ++i){
      uint tmp = i*gridPoint;
      if((uint)abs((int)(m_nTicks-tmp)) <minDiff){
         minDiff = abs((int)(m_nTicks-tmp));
         retTicks = tmp;
      }
   }
   return BarPosition::getNearestTick(m_nTicks, m_sig.ticks(), quant);
   return retTicks;
}

int BarPosition::quantize(uint quantLevel)
{
   double sigTicks = m_sig.count()*m_sig.ticks();
   uint newTicks = 0;
   int quantError = 0;

   if(m_sig.ticks() % 6){
      quantLevel = (m_sig.ticks() % 8) ? ((m_sig.ticks() % 4)? 2: 4) : 8;
   }
   //old: && sigTicks/m_nTicks != floor(sigTicks/m_nTicks)
   if(m_nTicks!= 0 ){
      newTicks    =  getNearestTick(quantLevel);
      quantError  =  newTicks -m_nTicks;
      m_nTicks    =  newTicks;
      if(m_nTicks== m_sig.ticks()){
         m_nTicks = 0;
         m_nBeat++;
         if(m_nBeat>m_sig.count()){
            m_nBar++;
            m_nBeat = 1;
         }
      }
   }
   return quantError;
}

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

void  BarPosition::addBeats(const uint beats)
{
    m_nBeat += beats;
    if(m_nBeat>m_sig.count()){
       uint bars = (m_nBeat-1)/m_sig.count();
       m_nBar += bars;
       m_nBeat = ((m_nBeat-1) % m_sig.count())+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_sig.ticks() % quarter) != 0)  return false;
      for(uint i = 0;i<count; ++i){
         addTicks(m_sig.ticks()/quarter);
      }
   }
   else{
      quarter = 4/reci;
      if(4 % reci != 0) return false;
      addBeats(count * quarter);
   }
   
   return true;
}

uint  BarPosition::getTicksToNextBeat()
{
   return m_sig.ticks()-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;
   BarPosition barPos = *begin();

   //cout<<"Enter BarTimeline::normalize"<<endl;
   //barPos.dump();
   if(barPos.getBeat() == 1 && barPos.getTicks() == 0){
      offset = 1;
   }

   if(barPos.getBar() == 1){
      if(offset == 1){
         return;
      }
   }
   //cout<<"Offset = "<<offset<<endl;
   for(barIter it = begin() ; it != end(); ++it){
      //cout<<"Bar (before): "<<it->getBar()<<endl;
      it->setBar(it->getBar() - barPos.getBar() + offset);
      //cout<<"Bar (after): "<<it->getBar()<<endl;
   }

   //cout<<"Leave BarTimeline::normalize"<<endl;

}

bool  BarTimeline::isQuantized()
{

   for(barIter it = begin(); it != end(); ++it){
      uint sigBarTicks = it->getTicksPerBar();
      uint ticks = it->getTicks();      
      if(ticks != 0){
         if(sigBarTicks % ticks) return false;
      }
   }
   return true;
}

BarTimeline BarTimeline::quantize(indizes& removed, uint quantLevel)
{
   BarTimeline tmp;
   BarTimeline ret;
   uint i = 0, j = 0;
   for( barIter it = begin(); it != end(); ++it){
      BarPosition pos = *it;
      int err = pos.quantize(quantLevel);
      //cout<<"QE = "<<err<<endl;
      tmp.push_back(pos);
   }
   //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);
         //cout<<"Found duplicate BarPosition at "<<i+j<<endl;
      }
      i += j;
   }
   
   return ret;
}

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

duration BarTimeline::getTickDurations() const
{
   duration 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().getTicksPerBar();
      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();
      //cout<<"Duration = "<<(tTicks2-tTicks1)<<endl;
      ret.push_back(tTicks2-tTicks1);
   }
   return ret;
}

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

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

   return minTicks;
}

