/***************************************************************************
 *   Copyright (C) 2004 by Predrag Viceic                                  *
 *   viceic@net2000.ch                                             *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "wavecanvas.h"
/**
        \fn WaveCanvas::WaveCanvas(QWidget* parent)
        Initialises member variables
    */
WaveCanvas::WaveCanvas(QWidget* parent)
 : QCanvas(parent)
{
    zoomRatio=1;
    soundManager=0;
    bgColor=Qt::white;
    spectrumBgColor=Qt::blue;
    _hasRuler=FALSE;
    maxZoomRatio=-1;
    background=0;
    backgroundPix=0;
    viewMode=WAVE_VIEWMODE;
    drawing=FALSE;
    pixmapXOrigin=0;
    hasPixmapV=FALSE;
    leftMaximaBuffer=0;
    rightMaximaBuffer=0;
    spectrogramMaxBuffer=0;
    spectrogramMaxBufferNbValues=0;
    visibleWidth=0;
}


WaveCanvas::~WaveCanvas()
{
    //zapBuffers();
}

/**
        \fn WaveCanvas::drawBackgroundImage(long start_sample, long end_sample)

        launches drawSpectrum(..) or drawWave(..) depending on view mode

        \sa drawWave(long,long)
        \sa drawSpectrum(long,long)
        \sa drawBackground()
*/
void WaveCanvas::drawBackgroundImage(long start_sample, long end_sample){
    if(viewMode==SPECTRUM_VIEWMODE) drawSpectrum(start_sample,end_sample);
    else drawWave(start_sample,end_sample);
}

/**
        \fn WaveCanvas::drawBackground( QPainter & localpainter, const QRect & clip )

        Reimplements the QCanvas::drawBackground. Copies computed pixmap onto background pixmap

        \sa drawWave(long,long)
        \sa drawSpectrum(long,long)
        \sa drawBackgroundImage(long,long)
*/
void WaveCanvas::drawBackground( QPainter & localpainter, const QRect & clip ){
        QMutex mutex;
        mutex.lock();
        if(soundManager!=0 && soundManager->hasSound()){
            if(backgroundPix){
                localpainter.drawPixmap(clip.x(),0,
                                                    *backgroundPix,
                                                    clip.x()-pixmapXOrigin,0,
                                                    clip.width(),backgroundPix->height());
            }
        }
        mutex.unlock();
}




/*!
    \fn WaveCanvas::setZoomRatio(long)

    sets the zoom ratio and computes the max zoom ratio (used for ruler coord system)

 */
void WaveCanvas::setZoomRatio(long zr)
{
    if(visibleWidth>0){
        zoomRatio=zr;
        maxZoomRatio=soundManager->getFrames()/visibleWidth;
    }
}


/*!
    \fn WaveCanvas::setSoundManager(SoudManager*)

    assigns the pointer to the current SoundManager
 */
void WaveCanvas::setSoundManager(SoundManager* sm)
{
	soundManager=sm;
}




/*!
    \fn WaveCanvas::setBgColor(int r, int g, int b)

 */
void WaveCanvas::setBgColor(int r, int g, int b)
{
    bgColor.setRgb(r,g,b);
}


/*!
    \fn WaveCanvas::hasRuler(bool)
 */
void WaveCanvas::hasRuler(bool _hr)
{
    _hasRuler=_hr;
}


/*!
    \fn WaveCanvas::getRulerWidth();
 */
int WaveCanvas::getRulerHeight()
{
    if(_hasRuler) return 20;
    else return 0;
}


/*!
    \fn WaveCanvas::forceRepaint()

    forces the repaint of the visible part of canvas

    \sa forceRepaint(long, long)
 */
void WaveCanvas::forceRepaint()
{
    if(backgroundPix && backgroundPix->width()>0){
        long start_sample=pixmapXOrigin*zoomRatio;
        long end_sample= start_sample+visibleWidth*zoomRatio;
        //cout<<"S: "<<start_sample<<" E: "<<end_sample<<" PX: "<<pixmapXOrigin<<" Z: "<<zoomRatio<<endl;
        /*
        assert(start_sample>=0 &&
                end_sample<=soundManager->getFrames() && end_sample>start_sample);
        */
        forceRepaint(start_sample,end_sample);
    }else{
        forceRepaint(0,soundManager->getFrames());
    }

}
/*!
    \fn WaveCanvas::forceRepaint(long start_sample, long end_sample)

    forces the repaint of the canvas from start_sample to end_sample

    \sa forceRepaint()
 */
void WaveCanvas::forceRepaint(long start_sample,long end_sample)
{
    /**
       \todo find out why sometimes extremly huge or negative values enter here
    */

    //cout<<"repainting from "<<start_sample<<" to "<<end_sample<<" pixmap origin "<<pixmapXOrigin<<"\n";
    assert(start_sample>=0);
    //assert(end_sample<=soundManager->getFrames());
    end_sample=end_sample<=soundManager->getFrames()?end_sample:soundManager->getFrames();
    assert(end_sample>start_sample);
    if(start_sample>=0 && end_sample>0 && end_sample<=soundManager->getFrames()){
        drawBackgroundImage(start_sample,end_sample);
        QRect rect(start_sample/zoomRatio,0,(end_sample-start_sample)/zoomRatio,height());
        setChanged(rect);
        update();
    }
}



/*!
    \fn WaveCanvas::changeViewMode(int)

    changes the view mode and forces the repaint


 */
void WaveCanvas::changeViewMode(int vm)
{
    viewMode=vm<=1?vm:0;
    forceRepaint();
}


/*!
    \fn WaveCanvas::drawWave(long,long)

    draws the wave in amplitude domain from start_sample to end_sample and stores the pixmap

 */
void WaveCanvas::drawWave(long start_sample,long end_sample)
{
    /*
    QProgressDialog progress("Drawing wave...",NULL,end_sample-start_sample,NULL,"progress",TRUE);
     progress.setMinimumDuration(3000);
    */
    //cout<<"Drawing from "<<start_sample<<" to "<<end_sample<<"\n";
    if(soundManager!=0 && soundManager->hasSound()&&
       zoomRatio>=1 && width()>0 && height()>0){
            long jump=zoomRatio;
            if(jump<1) jump=1;
            if(start_sample<0)start_sample=0;
            if(end_sample>=soundManager->getFrames()) end_sample=soundManager->getFrames()-jump;
            bool wasEmpty=TRUE;
            int v_height=height()-getRulerHeight();
            int v_width=ceil((end_sample-start_sample)/float(zoomRatio));
            int c_x=0;
            if(v_width<1) return;
            if(!backgroundPix){
                backgroundPix=new QPixmap(v_width,height());
            }
            else if(backgroundPix->width()!=v_width || backgroundPix->height()!=height()){
                        backgroundPix->resize(v_width,height());
            }else{
                wasEmpty=FALSE;
            }
            QPainter localpainter(backgroundPix);
            localpainter.fillRect(backgroundPix->rect(),QBrush(bgColor));


            QColor wavecolor(50,50,255);
            int translate=0;

            if(_hasRuler /*&& wasEmpty*/){

                QFont font;
                font.setPixelSize(8);
                localpainter.setFont(font);
                localpainter.fillRect(0,0,v_width,getRulerHeight(),QBrush(lightGray));
                localpainter.setPen(black);
                localpainter.drawLine(0,getRulerHeight(),v_width,getRulerHeight());
                int ticksDistance=(maxZoomRatio/zoomRatio)*10;
                int midTicksDistance=ticksDistance/2;
                int mid=0;
                QString number="";
                int count=0;
                int nbTicksToOrigin=0;
                if(pixmapXOrigin!=0 && ticksDistance!=0){
                    nbTicksToOrigin=pixmapXOrigin%ticksDistance;
                    for (int i=ticksDistance-nbTicksToOrigin;
                                i<v_width;i+=ticksDistance){
                        if(i<v_width)
                            localpainter.drawLine(i,0,i,getRulerHeight()-4);
                        mid=i+midTicksDistance;
                        if(mid<v_width){
                            localpainter.drawLine(mid,0,mid,getRulerHeight()/2);
                        }
                        /*
                        if(count%(v_width/10)==0){
                            number=QString::number(start_sample+(i*zoomRatio));
                            localpainter.drawText(i,getRulerHeight()/2+2,number);
                        }
                        */
                    }
                }
                translate=translate+getRulerHeight();
                count++;
            }

            /*if(!wasEmpty) translate=translate+getRulerHeight();*/
            //end drawRuler
            translate=translate+(double)v_height/4.0;
            int x=c_x;
            //float sampleHeightFactor=(0.8/SHRT_MAX)*(v_height/4);
            float sampleHeightFactor=0.8*(v_height/4);
            localpainter.setPen(wavecolor);
            if(!leftMaximaBuffer || !rightMaximaBuffer){
                 //oh some caches..ya know..
                leftMaximaBuffer=new maxAndMin[(int)ceil(soundManager->getFrames()/jump)];
                rightMaximaBuffer=new maxAndMin[(int)ceil(soundManager->getFrames()/jump)];
                for (int i=0;i<(int)ceil(soundManager->getFrames()/jump);i++){
                    leftMaximaBuffer[i].hasValue=0;
                    rightMaximaBuffer[i].hasValue=0;
                }
            }
            for (int i=start_sample;i<=end_sample-jump;i=i+jump){
                    float maxL=0;
                    float minL=0;
                    float maxR=0;
                    float minR=0;
                    if(!leftMaximaBuffer[i/zoomRatio].hasValue ||!rightMaximaBuffer[i/zoomRatio].hasValue){
                        for (int j=i;j<=i+jump;j++){
                            float valL=soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[j];
                            float valR=soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[j];
                            if(valL>=0){
                                if(valL>maxL) maxL=valL;
                            }else if(valL<=0){
                                if(valL<minL) minL=valL;
                            }
                            if(valR>=0){
                                if(valR>maxR) maxR=valR;
                            }else if(valR<=0){
                                if(valR<minR) minR=valR;
                            }
                        }
                        leftMaximaBuffer[i/jump].min=minL;
                        leftMaximaBuffer[i/jump].max=maxL;
                        leftMaximaBuffer[i/jump].hasValue=1;
                        rightMaximaBuffer[i/jump].min=minR;
                        rightMaximaBuffer[i/jump].max=maxR;
                        rightMaximaBuffer[i/jump].hasValue=1;
                    }else{
                        minL=leftMaximaBuffer[i/jump].min;
                        maxL=leftMaximaBuffer[i/jump].max;
                        minR=rightMaximaBuffer[i/jump].min;
                        maxR=rightMaximaBuffer[i/jump].max;
                    }

                    //if(hasPixmapV) return; //avoid cascading redraws
                    localpainter.drawLine(x,translate,x,(int)((float)-maxL*sampleHeightFactor)+translate);
                    localpainter.drawLine(x,translate,x,(int)((float)-minL*sampleHeightFactor)+translate);

                    localpainter.drawLine(x,translate+(double)v_height/2.0,
                                                            x,(int)((float)-maxR*sampleHeightFactor)+translate+(double)v_height/2.0);
                    localpainter.drawLine(x,translate+(double)v_height/2.0,
                                                            x,(int)((float)-minR*sampleHeightFactor)+translate+(double)v_height/2.0);

                    x++;
                    /*
                    if((i-start_sample)%(jump*1000)==0){
                        progress.setProgress(i);
                        progress.setLabelText("Drawing wave...(frame: "+QString::number(i)+")");
                    }
                    */
            }
            hasPixmapV=TRUE;
    }
}


/*!
    \fn WaveCanvas::drawSpectrum(long,long)

    draws the wave as spectrogram from start_sample to end_sample and stores the pixmap
 */
void WaveCanvas::drawSpectrum(long start_sample,long end_sample)
{
    std::cout<<"start drawing spectrum\n";
    QColor color(0,255,0);
    int nbbands=soundManager->getHiresFreqNbBands();
    float** subband_energies=soundManager->getHiresFreqSubbandEnergies();
    float minima=soundManager->getHiresFreqFFTMinima();
    float maxima=soundManager->getHiresFreqFFTMaxima();
    float maximaLog=10*log10(maxima);
    float minimaLog=minima==0?0:10*log10(minima);
    float spectralMeanOverFrames=(maximaLog-minimaLog)/2;
    int fftAdvance=soundManager->getHiresFreqFFTAdvance();

    /*
    QProgressDialog progress("Drawing specturm...",NULL,end_sample-start_sample,NULL,"progress",TRUE);
     progress.setMinimumDuration(2000);
    */
     if(soundManager!=0 && soundManager->hasSound()&& zoomRatio>=1){
            long jump=zoomRatio;
            if(jump<1) jump=1;
            if(jump<fftAdvance) jump=fftAdvance;
            if(start_sample<0)start_sample=0;
            if(end_sample>=soundManager->getFrames()) end_sample=soundManager->getFrames();
            int v_height=height()-getRulerHeight();
            int v_width=ceil((end_sample-start_sample)/float(zoomRatio));
            int c_x=0;

           if(!backgroundPix){
                backgroundPix=new QPixmap(v_width,height());
            }
            else if(backgroundPix->width()!=v_width || backgroundPix->height()!=height()){
                        backgroundPix->resize(v_width,height());
            }
            QPainter localpainter(backgroundPix);
            QFont font;
            font.setPixelSize(8);
            localpainter.setFont(font);
            localpainter.fillRect(backgroundPix->rect(),QBrush(bgColor));



            int translate=0;
           if(_hasRuler){
                localpainter.fillRect(0,0,v_width,getRulerHeight(),QBrush(lightGray));
                localpainter.setPen(black);
                localpainter.drawLine(0,getRulerHeight(),v_width,getRulerHeight());
                int ticksDistance=(maxZoomRatio/zoomRatio)*10;
                int midTicksDistance=ticksDistance/2;
                int mid=0;
                QString number="";
                int count=0;
                 if(pixmapXOrigin!=0 && ticksDistance!=0){
                    for (int i=ticksDistance-(pixmapXOrigin%ticksDistance);
                                i<v_width;i+=ticksDistance){
                        if(i<v_width)
                            localpainter.drawLine(i,0,i,getRulerHeight()-4);
                        mid=i+midTicksDistance;
                        if(mid<v_width){
                            localpainter.drawLine(mid,0,mid,getRulerHeight()/2);
                        }
                        /*
                        if(count%(v_width/10)==0){
                            number=QString::number(start_sample+(i*zoomRatio));
                            localpainter.drawText(i,getRulerHeight()/2+2,number);
                        }
                        */
                    }
                }
                translate=translate+getRulerHeight();
                count++;
            }
            /*if(!wasEmpty) translate=translate+getRulerHeight();*/
            //end drawRuler

            int x=c_x;
            float bandHeightFactor=float(v_height)/float(nbbands);
            float bandWidthFactor=float(fftAdvance)/float(zoomRatio);
            MyMath::smartRound bwFactorRound;
            bwFactorRound.init(bandWidthFactor,v_width);
            MyMath::smartRound bhFactorRound;
            bhFactorRound.init(bandHeightFactor,
                               round(v_height/bandHeightFactor));
            std::cout<<"band height factor: "<<bandHeightFactor<<" band width factor:"<<bandWidthFactor<<"\n";
            std::cout<<"jump: "<<jump<<"\n";
            float max=FLT_MIN;
            float val=0;
            int colorValue=0;
            QWMatrix matrix=localpainter.worldMatrix();
            matrix.translate(0,translate);
            localpainter.setWorldMatrix(matrix);

            //oh, some caches..
            if(!spectrogramMaxBuffer){
                spectrogramMaxBuffer=
                        new spectrogramMax[(int)ceil(soundManager->getFrames()/jump)];
                spectrogramMaxBufferNbValues=(int)ceil(soundManager->getFrames()/jump);
                for (int i=0;i<(int)ceil(soundManager->getFrames()/jump);i++){
                    spectrogramMaxBuffer[i].hasValue=0;
                    spectrogramMaxBuffer[i].max=new float[nbbands];
                }
            }
            int roundoff=0;
            int lastchunk=end_sample+jump>soundManager->getFrames()?
                                        soundManager->getFrames()%jump:0;
            for (int i=start_sample;i<end_sample-lastchunk;i=i+jump){
                    bwFactorRound.round();
                    for(int band=0;band<nbbands;band++){
                        bhFactorRound.round();
                        max=FLT_MIN;
                        if(!spectrogramMaxBuffer[i/jump].hasValue){
                            for (int j=i;j<=i+jump;j=j+fftAdvance){
                                        val=subband_energies[nbbands-1-band][j/fftAdvance];
                                        if(val>max) max=val;
                            }
                            if(soundManager->getSubstractSpectralMean())
                                max=10*log10(max)-spectralMeanOverFrames;
                            else
                                max=10*log10(max);
                            spectrogramMaxBuffer[i/jump].max[band]=max;
                            if(band==nbbands-1) spectrogramMaxBuffer[i/jump].hasValue=1;
                        }else{
                            max=spectrogramMaxBuffer[i/jump].max[band];
                        }

                        colorValue=MyMath::linear_regression(minimaLog,maximaLog,
                                                                                                255,0,max);
                        colorValue=colorValue>255?255:colorValue<0?0:colorValue;
                        //color.setRgb(colorValue,colorValue,colorValue);
                        color.setHsv(colorValue,255,255);
                        //if(hasPixmap) return; //avoid cascading redraws
                        if(colorValue!=0){
                            localpainter.setPen(color);
                            if(bandHeightFactor<=1.0 && bandWidthFactor<=1.0){
                                localpainter.drawPoint(x,band*bandHeightFactor);
                            }else if (bandHeightFactor>1.0 && bandWidthFactor<=1.0){
                                localpainter.drawLine(x,band*bandHeightFactor,
                                                                        x,band*bandHeightFactor+bandHeightFactor);
                            }else if(bandWidthFactor>1.0 && bandHeightFactor<=1.0){
                                if(x!=x+bwFactorRound.rounded()){
                                    localpainter.drawLine(x,band*bandHeightFactor,
                                            x+bwFactorRound.rounded(),band*bandHeightFactor);
                                }
                            }else{
                                localpainter.fillRect(x,band*bandHeightFactor,
                                        bwFactorRound.rounded(),ceil(bandHeightFactor),
                                        QBrush(color));
			    }
                         }
                    }
                    if(bandWidthFactor<=1){
                        x++;
                        roundoff++;
                    }else{
                        x+=bwFactorRound.rounded();//ceil(bandWidthFactor);
                        roundoff+=bwFactorRound.rounded();
                    }
                    //cout<<"rounded to: "<<bwFactorRound.rounded()<<endl;
                    /*
                    if(i%(jump*100)==0){
                        progress.setProgress(i);
                        progress.setLabelText("Drawing spectrum...(frame: "+QString::number(i)+")");
                    }
                    */
            }
            if(lastchunk>0){
                localpainter.fillRect(x,0,x+lastchunk/zoomRatio,v_height,QBrush(blue));
            }
            font.setPixelSize(12);
            font.setWeight(QFont::Bold);
            localpainter.setPen(black);
            localpainter.setFont(font);
            QFontMetrics font_metrics(font);
            QString highFreqText=
                    QString::number(soundManager->getHiresFreqEndFreq())+" Hz";
            QString lowFreqText=
                    QString::number(soundManager->getHiresFreqStartFreq())+" Hz";
            QString middleFreqText=
                    QString::number(soundManager->getHiresFreqStartFreq()+
                    (soundManager->getHiresFreqEndFreq()-
                    soundManager->getHiresFreqStartFreq())/2)+" Hz";
            localpainter.drawText(c_x,font_metrics.height(),highFreqText);
            localpainter.drawText(c_x+v_width/2,font_metrics.height(),highFreqText);
            localpainter.drawText(c_x+v_width-font_metrics.width(highFreqText),
                                                    font_metrics.height(),highFreqText);

            localpainter.drawText(c_x,v_height/2,middleFreqText);
            localpainter.drawText(c_x+v_width/2,v_height/2,
                                  middleFreqText);
            localpainter.drawText(c_x+v_width-font_metrics.width(lowFreqText),
                                  v_height/2,middleFreqText);

            localpainter.drawText(c_x,v_height-font_metrics.height(),lowFreqText);
            localpainter.drawText(c_x+v_width/2,v_height-font_metrics.height(),
                                                    lowFreqText);
            localpainter.drawText(c_x+v_width-font_metrics.width(lowFreqText),
                                  v_height-font_metrics.height(),lowFreqText);
            hasPixmapV=TRUE;
            cout<<"real width: "<<v_width;
            cout<<" rounded width: "<<roundoff<<endl;
    }
    std::cout<<"end drawing spectrum\n";
}




/*!
    \fn WaveCanvas::setPixmapXOrigin(int)

    sets the origin of the pixmap
    \sa pixmapXOrigin
 */
void WaveCanvas::setPixmapXOrigin(int x_orig)
{
    pixmapXOrigin=x_orig;
}


/*!
    \fn WaveCanvas::hasPixmap()

    \return  is the pixmap is already computed?
 */
bool WaveCanvas::hasPixmap()
{
    return hasPixmapV;
}


/*!
    \fn WaveCanvas::zapBuffers()
 */
void WaveCanvas::zapBuffers()
{
    //cout<<"in wavecanvas (parent ["<<parent()->parent()->name()<<"]) started zapping wave canvas buffers\n";
    if (leftMaximaBuffer) zaparr(leftMaximaBuffer);
    if (rightMaximaBuffer) zaparr(rightMaximaBuffer);
    if (spectrogramMaxBuffer){
        for (int i=0;i<spectrogramMaxBufferNbValues;i++){
            if (spectrogramMaxBuffer[i].max){
                zaparr(spectrogramMaxBuffer[i].max);
                spectrogramMaxBuffer[i].hasValue=FALSE;
            }
        }
        spectrogramMaxBufferNbValues=0;
        if(spectrogramMaxBuffer) zaparr(spectrogramMaxBuffer);
    }
    cout<<"in wavecanvas (parent ["<<parent()->parent()->name()<<"]) Buffers zapped\n";
}


/*!
    \fn WaveCanvas::zapBuffers(long start, long end)
 */
void WaveCanvas::zapBuffers(long start, long end)
{
    for (int i=start/zoomRatio;i<end/zoomRatio;i++){
        if (leftMaximaBuffer && &leftMaximaBuffer[i]) leftMaximaBuffer[i].hasValue=0;
        if (rightMaximaBuffer && &rightMaximaBuffer[i]) rightMaximaBuffer[i].hasValue=0;
        if (spectrogramMaxBuffer && &spectrogramMaxBuffer[i]) spectrogramMaxBuffer[i].hasValue=0;
    }
}



/*!
    \fn WaveCanvas::setVisibleWidth(int w)
 */
void WaveCanvas::setVisibleWidth(int w)
{
    visibleWidth=w;
}
