//    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 <QtQuick/QQuickPaintedItem>
#include <QPainter>
#include <QDebug>
#include <math.h>

#include "frequencyview.h"
#include "cartesian.h"

Cartesian::Cartesian(QQuickItem *parent)
  :FrequencyView(parent)
{
  paint_fft = true;
  paint_dissonance = false;
  m_rightHz = 10000.0;
  m_leftHz = 0.0;
  m_dbFloor = -120.0;
  m_logx=false;
  m_maxDissonance=1.0;
 }
// Interface ---------------------------------------------------------
// getter:
float Cartesian::rightHz()  {
  return m_rightHz;
}
float Cartesian::leftHz()  {
  return m_leftHz;
}
bool Cartesian::paintDissonance() {
  return paint_dissonance;
}
QList<double> Cartesian::dissonance() {
  return m_dissonance;
}
float Cartesian::dbFloor() const {
  return m_dbFloor;
}
bool Cartesian::logx() const {
  return m_logx;
}
float Cartesian::maxDissonance() const {
  return m_maxDissonance;
}
// setter:
void Cartesian::setRightHz( float &val) {
  if ( m_rightHz != val ) {
    m_rightHz = val;
    emit rightHzChanged();
    update();
  }
}
void Cartesian::setLeftHz( float &val) {
  if ( m_leftHz != val ) {
    m_leftHz = val;
    emit leftHzChanged();
    update();
  }
}
void Cartesian::setPaintDissonance(const bool &paint) {
  if( paint_dissonance != paint ) {
    paint_dissonance = paint;
    update();
  }
}
void Cartesian::setDissonance(QList<double> &dissonance)
{
  m_dissonance.clear();
  m_dissonance = dissonance;
  update();
  emit dissonanceChanged();
}
void Cartesian::setMaxDissonance( const float maxDissonance){
  if ( m_maxDissonance != maxDissonance){
    m_maxDissonance = maxDissonance;
    update();
    emit maxDissonanceChanged();
  }
}
void Cartesian::setDbFloor(const float &dbFloor)
{
  if(m_dbFloor != dbFloor) {
    m_dbFloor = dbFloor;
    emit dbFloorChanged();
    update();
  }
}
void Cartesian::setLogx( const bool &logx) {
  if(m_logx != logx) {
    m_logx = logx;
    update();
    emit logxChanged();
  }
}
void Cartesian::setPointerFreq(const double &pointerFreq) {
  if ( m_pointerFreq != pointerFreq ) {
    m_pointerFreq = pointerFreq;
    updatePointerPos();
    emit pointerFreqChanged();
  }
}
void Cartesian::setPointerAmp(const double &pointerAmp) {
  if ( m_pointerAmp != pointerAmp ) {
    m_pointerAmp = pointerAmp;
    updatePointerPos();
    emit pointerAmpChanged();
  }
}
void Cartesian::setGrid(const QList<double> &grid) {
  m_gridData.clear();
  m_gridData = grid;
  m_gridSteps = m_gridData.size();
  calculateGrid();
  
}
void Cartesian::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]);
  }
  
  float start = m_leftHz;
  if(m_gridLowest > m_leftHz) start=m_gridLowest;
  float end=m_rightHz; 
  if(m_gridHighest && m_gridHighest < end)
    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+1.0)
      m_grid << freq;
    
  }
  update();
}
// -------------------------------------------------------------------
void Cartesian::updatePointerPos() {
  QPointF p(hertzToX(m_pointerFreq), scaleAmp(m_pointerAmp));
  m_pointerPos = p;
  emit pointerPosChanged();
}
float Cartesian::binToHertz(long bin)
{
  float hertz;
  hertz = m_sampleRate / m_fftSize;
  hertz = hertz * bin;
  return hertz;
}
long Cartesian::hertzToBin(float hertz)
{
  long bin;
  bin = floor(hertz * m_fftSize / m_sampleRate);
  return bin;
}
float Cartesian::binToX(long bin)
{
  float x;
  x = hertzToX(binToHertz(bin));
  return x;
//  float x;
//  x = (binToHertz(bin)-m_leftHz) / (m_rightHz - m_leftHz);
//  x *= (float)width();
//  return x;
}
float Cartesian::hertzToX(double hertz)
{
  float x;
  if(m_logx){
    float right_end = ratio2cent(m_rightHz/m_leftHz);
    x = ratio2cent(hertz/m_leftHz);
    x /= right_end;
  }
  else {
    x = (hertz - m_leftHz) / (m_rightHz - m_leftHz);
  }
  x *= width();
  return x;
}
float Cartesian::scaleAmp(double val)
{
  float scaled;
  scaled = 20.0*log10(val)/m_dbFloor;
  scaled *= height();
  return scaled;
}
float Cartesian::ratio2cent(double ratio){
  return 1200.0 * log(ratio)/log(2);
}
double Cartesian::cent2ratio(float cent){
  return pow(2,cent/1200.0);
}

void Cartesian::printFFT(QPainter *painter)
{
  int from = hertzToBin(m_leftHz);
  int to = hertzToBin(m_rightHz);
  QPointF point;
  QPainterPath path;
  path.moveTo(0,scaleAmp(m_fftData[from]));
  for ( int i=from; i<to; i++ ) {
    path.lineTo(binToX(i),scaleAmp(m_fftData[i]));

  }
  painter->drawPath( path );
}

void Cartesian::printPeaks(QPainter *painter)
{
  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];
  }

  QPen pen(Qt::green, 1);
  painter->setPen(pen);
  float xpos;
  for ( int i=0; i<freq.size(); i++ ) {
    xpos = hertzToX(freq[i]);
    painter->drawLine(xpos,height(),xpos,scaleAmp(amps[i]));
  }
}

void Cartesian::printDissonance(QPainter *painter)
{
  QPainterPath path;
  float stepSize = width()/m_dissonance.size();
  double max=m_maxDissonance;
  //for (int i=0; i<m_dissonance.size(); i++)
  // if (m_dissonance[i] > max )
  //    max = m_dissonance[i];
  
  
  painter->setPen(QColor("lightgrey"));
  path.moveTo(0,height()-m_dissonance[0]*height()/max);
  //path.moveTo(0,height()/pow(m_dissonance[0]+1.0,2));
  for (int i=1; i<m_dissonance.size(); i++)
    //path.lineTo(i*stepSize,height()/pow(m_dissonance[i]+1.0,4));
    path.lineTo(i*stepSize,height()- m_dissonance[i]*height()/max);
  painter->drawPath(path);
}

void Cartesian::printGrid(QPainter *painter)
{
  QPainterPath path;
  double x;
  QPen pen(QColor("gray"), 0.3);
  painter->setPen(pen);//QColor("gray"));
  for ( int i=0; i<m_grid.size(); i++) {
    x=hertzToX(m_grid[i]);
    path.moveTo(x,height());
    path.lineTo(x,0);
    
    if(i%m_gridSteps == 0){
      painter->drawText(x,20,QString::number(m_grid[i]));
    }
  }
  painter->drawPath(path);
}

void Cartesian::paint(QPainter *painter)
{
  painter->setRenderHints(QPainter::Antialiasing, true);
  QPen black_pen(Qt::black, 1);
  painter->setPen(black_pen);

  if ( paint_grid ) printGrid(painter);
    painter->setPen(black_pen);
  if ( paint_fft ) printFFT(painter);
  if ( paint_peaks ) printPeaks(painter);
  if ( paint_dissonance ) printDissonance(painter);
  
}

