//    tonalisa - software to look at overtone-structures
//    Copyright (C) 2016  Dominik Schmidt-Philipp
//
//    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 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
#include <QPainter>
#include <QPainterPath>
#include <QBrush>
#include <QDebug>

#include <math.h>
#include <lo/lo.h>
#include <lo/lo_cpp.h>

#include "spiral.h"

#if(!defined(M_PI))
  #define M_PI 3.14159265358979323846264338327
  #define M_PI_2 (M_PI / 2.0)
#endif

Spiral::Spiral(QQuickItem *parent)
  : FrequencyView(parent)
{
  f_base = 136.1;
  m_octaves = 4.5;
}

// Getter---------------------------------------------
float Spiral::fBase() const {
  return f_base;
}
float Spiral::octaves() const {
  return m_octaves;
}
// Setter -------------------------------------------
void Spiral::setFBase(const float &fBase)
{
  if( f_base != fBase ) {
    f_base = fBase;
    update();
    emit fBaseChanged();
  }
}
void Spiral::setOctaves(const float &octaves)
{
  if(m_octaves != octaves) {
    m_octaves = octaves;
    update();
    emit zoomChanged();
  }
}
void Spiral::setPointerFreq(const double &pointerFreq) {
  if ( m_pointerFreq != pointerFreq ) {
    m_pointerFreq = pointerFreq;
    updatePointerPos();
    emit pointerFreqChanged();
  }
}
void Spiral::setPointerAmp(const double &pointerAmp) {
  if ( m_pointerAmp != pointerAmp ) {
    m_pointerAmp = pointerAmp;
    updatePointerPos();
    emit pointerAmpChanged();
  }
}
void Spiral::setGrid(const QList<double> &grid) {
  m_gridData.clear();
  m_gridData = grid;
  calculateGrid();
}
void Spiral::calculateGrid() {
  double eps = pow(2,1/1200.0);
  
  QVector<double>grid_delta;
  double upper;

  for(int i=0; i<m_gridData.size(); i++){
    if(i+1 == m_gridData.size()) upper =  1200.0;
    else upper = m_gridData[i+1];
    grid_delta << (upper - m_gridData[i]);
  }

  double start = f_base;
  if(m_gridLowest > f_base) start=m_gridLowest;
  double end=f_base*pow(2,m_octaves); 
  if(m_gridHighest && m_gridHighest < f_base*pow(2,m_octaves))
    end=m_gridHighest;
  
  double gridPitch = m_gridPitch;
  if (m_gridTunedStep) {
    gridPitch = gridPitch*pow(eps,-m_gridData[m_gridTunedStep]);
  }

  double freq = gridPitch;

  int reg=0;
  while(freq > start){
    freq *= 0.5;
    reg--;
  }

  m_grid.clear();
  
  for( int i=0; freq<end; i++) {
    if (i==m_gridData.size()) { 
      i=0;
      reg++; // next octave
    }
    freq = gridPitch * pow(2,reg) * pow(eps, m_gridData[i]);
    if(freq >= start && freq <= end)
      m_grid << freq;
  }
  update();
}
//---------------------------------------------------

void Spiral::oscSendPeaks()
{
  lo::Address a("localhost", "9000");
  lo::Message f;
  lo::Message am;
  
  QVector<double> freq;
  QVector<double> amps;
  int size = m_peaks.size()/2;
  freq.reserve(size);
  amps.reserve(size);
  for ( int i=0; i<size; i++) {
    freq << m_peaks[2*i];
    amps << m_peaks[2*i+1];
  }
  
  for (int i=0;i<freq.size();i++) {
    f.add("f", freq[i]);
    am.add("f", amps[i]);
  }
  a.send("/current/freqs", f);
  a.send("/current/amps", am);
}


void Spiral::updatePointerPos()
{
  QPointF p = freq2spiral(m_pointerFreq,m_pointerAmp);
  p *= scale();
  p += QPointF(width()/2,height()/2);
  m_pointerPos = p;
  emit pointerPosChanged();
}

QPointF Spiral::freq2spiral(double freq, double amp)
{
  double arg,x,y;
  double log2 = log(2);
  double f_base_log = log(f_base);

  if(amp) {
    amp = 20*log10(amp)/96; // 0dB vollaussteuerung -96 dB kleinster Wert
    if(amp<-1.0)
      amp = -1.0;
  }
  else amp = -1.0;

  arg = log(freq)-f_base_log;
  arg /= log2;
  x = ((1+amp) + arg) * sin( arg * M_PI * 2);
  y = ((1+amp) + arg) * cos( arg * M_PI * 2);
  
  return QPointF(x,y);
}

double Spiral::scale()
{
    return width()/m_octaves/2;
}

void Spiral::printSpiral(QPainter *painter)
{
  double from = f_base;
  double to = pow(2,m_octaves)*f_base;
  double step = pow(2,1/120.0);
  QPointF point;
  QPainterPath spiral;

  spiral.moveTo(0,0);
  for ( double freq=from; freq<=to; freq*=step ) {
    point=freq2spiral(freq);
    spiral.lineTo(point*scale());
  } 
  spiral.translate(width()/2,height()/2);
  painter->drawPath( spiral );
}

void Spiral::printGrid(QPainter *painter)
{
  // the grid array holds cent values
  // m_grid holds frequency values

  QPointF point;
  QPainterPath path;
  for ( int i=0; i<m_grid.size(); i++ ) {
    path.moveTo( freq2spiral( m_grid[i], 0.0 )*scale() );
    path.lineTo( freq2spiral( m_grid[i], 1.0 )*scale() );
  }
  path.translate(width()/2,height()/2);
  painter->drawPath( path );
}
void Spiral::printFFT(QPainter *painter)
{

}
void Spiral::printPeaks(QPainter *painter)
{
  oscSendPeaks();
  QVector<double> freq;
  QVector<double> amps;
  int size = m_peaks.size()/2;
  freq.reserve(size);
  amps.reserve(size);
  for ( int i=0; i<size; i++) {
    freq << m_peaks[2*i];
    amps << m_peaks[2*i+1];
  }
  // some harmonic default peaks for testing
  /*  for ( int i=1; i<64; i++ ) {
    freq << i*f_base;
    amps << 1.0/i;
    }*/

  QPointF point;
  QPainterPath path;
  for ( int i=0; i<freq.size(); i++ ) {
    path.moveTo(scale() * freq2spiral(freq[i]));
    path.lineTo(scale() * freq2spiral(freq[i],amps[i]));
  }
  path.translate(width()/2,height()/2);
  painter->drawPath( path );
}

void Spiral::paint(QPainter *painter)
{
  painter->setRenderHints(QPainter::Antialiasing, true);
  
  QPen pen(Qt::lightGray, 0.3);  
  painter->setPen(pen);
  
  if ( paint_coord ) printSpiral(painter);
  if ( paint_grid ) printGrid(painter);
  if ( paint_fft ) printFFT(painter);
  painter->setPen(QColor("black"));
  if ( paint_peaks) printPeaks(painter);
  
  //  painter->drawEllipse((scale() * freq));
}
