#include "analysiswindow.h"
#include "ui_analysiswindow.h"
#include <QByteArray>
#include <QMessageBox>
#include <QFileDialog>
#include <QLabel>

#include <string>
#include <math.h>

#include "world/world.h"
#include "standMelCepstrum/standMelCepstrum.h"

const int fs = 44100;
const double framePeriod = 2.0;

#define CEPSTRUM_DIMENSION 32

void analysisWindow::saveMelCepstrum(QString &s)
{
    standMelCepstrum cepstrum;
    QByteArray byteArray = s.toLocal8Bit();
    string output = byteArray.data();
    double *t, *noiseRatio;
    t = new double[volumeLength];
    noiseRatio = new double[volumeLength];

    double srcDeviation, srcAverage;
    double dstDeviation, dstAverage;

    for(int index = 0; index < volumeLength; index++){
        dstAverage = srcAverage = 0.0; dstDeviation = srcDeviation = 1.0e-22;
        double *srcR = new double[srcSpecgram.fftLength];
        double *dstR = new double[srcSpecgram.fftLength];
        for(int i = 64; i < srcSpecgram.fftLength / 4; i++){
            double r;
            r = dstTransSpecgram.ap[index][i*2] * dstTransSpecgram.ap[index][i*2] + dstTransSpecgram.ap[index][i*2-1] * dstTransSpecgram.ap[index][i*2-1];
            r = sqrt(r);
            dstR[i] = r;
            dstAverage += r;
            r = srcSpecgram.ap[index][i*2] * srcSpecgram.ap[index][i*2] + srcSpecgram.ap[index][i*2-1] * srcSpecgram.ap[index][i*2-1];
            r = sqrt(r);
            srcAverage += r;
            srcR[i] = r;
        }
        dstAverage /= srcSpecgram.fftLength / 4 - 63;
        srcAverage /= srcSpecgram.fftLength / 4 - 63;
        for(int i = 64; i < srcSpecgram.fftLength / 4; i++){
            dstDeviation += (dstR[i] - dstAverage) * (dstR[i] - dstAverage);
            srcDeviation += (srcR[i] - srcAverage) * (srcR[i] - srcAverage);
        }
        noiseRatio[index] = sqrt(dstDeviation / srcDeviation);

        delete[] srcR;
        delete[] dstR;
    }

    cepstrum.calculateMelCepstrum(CEPSTRUM_DIMENSION, transInverse, dstTransSpecgram.f0, noiseRatio, srcSpecgram.spec,
                                  dstTransSpecgram.spec, volumeLength, srcSpecgram.fftLength, fs, framePeriod);
    cepstrum.writeMelCepstrum(output);
    delete[] noiseRatio;
}

void analysisWindow::saveMelCepstrum()
{
    standMelCepstrum cepstrum;
    QString filter, fileName;
    QFileDialog dialog;
    QByteArray byteArray;
    double *noiseRatio;
    noiseRatio = new double[volumeLength];
    filter = "MelCepstrum File (*.stt)";
    dialog.setAcceptMode(QFileDialog::AcceptSave);
    fileName = dialog.getSaveFileName(this, tr("Save .stt file"), src.directoryPath);
    if(QString::compare(fileName, "") != 0){
        string output;
        byteArray = fileName.toLocal8Bit();
        output = byteArray.data();
        double srcDeviation, srcAverage;
        double dstDeviation, dstAverage;

        for(int index = 0; index < volumeLength; index++){
            dstAverage = srcAverage = 0.0; dstDeviation = srcDeviation = 1.0e-22;
            double *srcR = new double[srcSpecgram.fftLength];
            double *dstR = new double[srcSpecgram.fftLength];
            for(int i = 64; i < srcSpecgram.fftLength / 4; i++){
                double r;
                r = dstTransSpecgram.ap[index][i*2] * dstTransSpecgram.ap[index][i*2] + dstTransSpecgram.ap[index][i*2-1] * dstTransSpecgram.ap[index][i*2-1];
                r = sqrt(r);
                dstR[i] = r;
                dstAverage += r;
                r = srcSpecgram.ap[index][i*2] * srcSpecgram.ap[index][i*2] + srcSpecgram.ap[index][i*2-1] * srcSpecgram.ap[index][i*2-1];
                r = sqrt(r);
                srcAverage += r;
                srcR[i] = r;
            }
            dstAverage /= srcSpecgram.fftLength / 4 - 63;
            srcAverage /= srcSpecgram.fftLength / 4 - 63;
            for(int i = 64; i < srcSpecgram.fftLength / 4; i++){
                dstDeviation += (dstR[i] - dstAverage) * (dstR[i] - dstAverage);
                srcDeviation += (srcR[i] - srcAverage) * (srcR[i] - srcAverage);
            }
            noiseRatio[index] = sqrt(dstDeviation / srcDeviation);

            delete[] srcR;
            delete[] dstR;
        }

        cepstrum.calculateMelCepstrum(CEPSTRUM_DIMENSION, transInverse, dstTransSpecgram.f0, noiseRatio, srcSpecgram.spec, dstTransSpecgram.spec, volumeLength, srcSpecgram.fftLength, fs, framePeriod);
        cepstrum.writeMelCepstrum(output);
    }
    delete[] noiseRatio;
}

// ベーススペクトル・時間転写後のアペンドのスペクトル・メルケプストラムを使ってモーフィングしたスペクトルの３つを登録する
void analysisWindow::setSpectrumView(void)
{
    int fftl = srcSpecgram.fftLength;
    double maxSpectrumValue = 0.0;
    fftw_complex *srcCepstrum = new fftw_complex[fftl];
    fftw_complex *dstCepstrum = new fftw_complex[fftl];
    double *tmpSpectrum = new double[fftl];
    fftw_plan inverseFFT;
    fftw_plan forwardFFT;
    delete[] morphSpectrum;
    srcSpectrum = srcSpecgram.spec[position+10];
    dstSpectrum = dstTransSpecgram.spec[position+10];
    morphSpectrum = new double[fftl];

    for(int i = 0; i < fftl / 2; i++){
        if(maxSpectrumValue < srcSpectrum[i]){
            maxSpectrumValue = srcSpectrum[i];
        }
        if(maxSpectrumValue < dstSpectrum[i]){
            maxSpectrumValue = dstSpectrum[i];
        }
    }

    standMelCepstrum::stretchToMelScale(tmpSpectrum, srcSpectrum, fftl / 2 + 1, fs / 2);
    standMelCepstrum::stretchToMelScale(morphSpectrum, dstSpectrum, fftl / 2 + 1, fs / 2);

    for(int i = 0; i <= fftl / 2; i++){
        tmpSpectrum[i] = log(tmpSpectrum[i] + 1.0e-50) / fftl;
        morphSpectrum[i] = log(morphSpectrum[i] + 1.0e-50) / fftl;
        tmpSpectrum[i] = morphSpectrum[i] - tmpSpectrum[i];
    }
    for(int i = fftl / 2 + 1; i < fftl; i++){
        tmpSpectrum[i] = tmpSpectrum[fftl - i];
        morphSpectrum[i] = morphSpectrum[fftl - i];
    }
    inverseFFT = fftw_plan_dft_r2c_1d(fftl, tmpSpectrum, srcCepstrum, FFTW_ESTIMATE);
    fftw_execute(inverseFFT);
    fftw_destroy_plan(inverseFFT);

//    inverseFFT = fftw_plan_dft_r2c_1d(fftl, morphSpectrum, dstCepstrum, FFTW_ESTIMATE);
//    fftw_execute(inverseFFT);
//    fftw_destroy_plan(inverseFFT);

//    srcCepstrum[0][0] = dstCepstrum[0][0];
//    for(int i = 1; i < CEPSTRUM_DIMENSION; i++){
//        srcCepstrum[i][0] = dstCepstrum[i][0];
//        srcCepstrum[i][1] = dstCepstrum[i][1];
//    }
    for(int i = CEPSTRUM_DIMENSION; i <= fftl / 2; i++){
        srcCepstrum[i][0] = srcCepstrum[i][1] = 0.0;
    }

    forwardFFT = fftw_plan_dft_c2r_1d(fftl, srcCepstrum, morphSpectrum, FFTW_ESTIMATE);
    fftw_execute(forwardFFT);
    fftw_destroy_plan(forwardFFT);

    for(int i = 0; i < fftl; i++){
        tmpSpectrum[i] = exp(morphSpectrum[i]);
    }

    standMelCepstrum::stretchFromMelScale(morphSpectrum, tmpSpectrum, fftl / 2 + 1, fs / 2);

    for(int i = 0; i < fftl; i++){
        morphSpectrum[i] *= srcSpectrum[i];
    }

    ui->spectrumView->destroyGraphs();
    ui->spectrumView->addGraph(srcSpectrum, fftl / 2);
    ui->spectrumView->addGraph(dstSpectrum, fftl / 2);
    ui->spectrumView->addGraph(morphSpectrum, fftl / 2);
    ui->spectrumView->setGraphStyle(QMyGraphWidget::logGraph, maxSpectrumValue * 1.0e-6, maxSpectrumValue );

    delete[] tmpSpectrum;
    delete[] srcCepstrum;
    delete[] dstCepstrum;
}

// ベース波形のスペクトログラムと，時間転写後のアペンドス側波形のペクトログラムをセットする．
void analysisWindow::setSpecgramView(void)
{
    double *tmp;
    int tmpLength = min(srcWaveBuffer.size(), dstWaveBuffer.size());
    tmp = new double[tmpLength];

    srcSpecgram.create(volumeLength, getFFTLengthForStar(fs));
    for(int i = 0; i < tmpLength; i++){
        tmp[i] = srcWaveBuffer[i];
    }
    dio(tmp, tmpLength, fs, framePeriod, srcSpecgram.t, srcSpecgram.f0);
    star(tmp, tmpLength, fs, srcSpecgram.t, srcSpecgram.f0, srcSpecgram.spec);
    platinum(tmp, tmpLength, fs, srcSpecgram.t, srcSpecgram.f0, srcSpecgram.spec, srcSpecgram.ap );

    dstSpecgram.create(volumeLength, getFFTLengthForStar(fs));
    for(int i = 0; i < tmpLength; i++){
        tmp[i] = dstWaveBuffer[i];
    }
    dio(tmp, tmpLength, fs, framePeriod, dstSpecgram.t, dstSpecgram.f0);
    star(tmp, tmpLength, fs, srcSpecgram.t, dstSpecgram.f0, dstSpecgram.spec);
    platinum(tmp, tmpLength, fs, dstSpecgram.t, dstSpecgram.f0, dstSpecgram.spec, dstSpecgram.ap);

    dstTransSpecgram.create(volumeLength, getFFTLengthForStar(fs));
    for(int i = 0; i < volumeLength; i++){
        double rate = transFunction[i];
        int index = rate;
        rate -= index;
        for(int j = 0; j <= dstSpecgram.fftLength / 2; j++){
            if(index >= volumeLength - 1){
                dstTransSpecgram.spec[i][j] = dstSpecgram.spec[volumeLength - 1][j];
            }else{
                dstTransSpecgram.spec[i][j] = pow(dstSpecgram.spec[index][j], 1.0 - rate) * pow(dstSpecgram.spec[index+1][j], rate);
            }
        }
        for(int j = 0; j < dstSpecgram.fftLength; j++){
            dstTransSpecgram.ap[i][j] = dstSpecgram.ap[index][j];
        }
        dstTransSpecgram.f0[i] = dstSpecgram.f0[index];
    }

    delete[] tmp;
}

// 時間転写関数のセット
void analysisWindow::setTransformView(void)
{
    delete[] transFunction; delete[] transNormal;
    transFunction = new double[volumeLength];
    transNormal   = new double[volumeLength];

    ui->transformView->destroyGraphs();
    // 描画は登録順．
    ui->transformView->addGraph(transNormal, volumeLength);
    ui->transformView->addGraph(transFunction, volumeLength);
    for(int i = 0; i < volumeLength; i++){
        transNormal[i] = transFunction[i] = (double)i;
    }
    ui->transformView->setGraphStyle(QMyGraphWidget::linearGraph, 0, volumeLength);
}

// ベース波形の音量・アペンド波形の音量・時間転写後のアペンド波形の音量をセットする
void analysisWindow::setVolumeView(void)
{
    volumeLength = getSamplesForDIO(fs, min(dstWaveBuffer.size(), srcWaveBuffer.size()), framePeriod);
    double minValue = 1000.0, maxValue = -1000.0;
    delete[] srcVolume; delete[] dstVolume; delete[] dstTransVolume;
    ui->volumeView->destroyGraphs();
    srcVolume = new double[volumeLength];
    dstVolume = new double[volumeLength];
    dstTransVolume = new double[volumeLength];
    for(int i = 0; i < volumeLength; i++){
        int index = i * fs * framePeriod / 1000.0 - 1024;
        int c = 0;
        srcVolume[i] = dstVolume[i] = 0.0;
        for(int j = 0; j < 2049; j++, index++){
            if(index < 0){ continue; }
            if(index >= srcWaveBuffer.size() || index >= dstWaveBuffer.size()){ break; }
            srcVolume[i] += srcWaveBuffer[index] * srcWaveBuffer[index] * han[j];
            dstVolume[i] += dstWaveBuffer[index] * dstWaveBuffer[index] * han[j];
            c++;
        }
        if(c){
            srcVolume[i] = sqrt(srcVolume[i] / (double)c);
            dstVolume[i] = sqrt(dstVolume[i] / (double)c);
        }
        dstTransVolume[i] = dstVolume[i];
        minValue = (minValue > srcVolume[i])?srcVolume[i] : minValue;
        minValue = (minValue > dstVolume[i])?dstVolume[i] : minValue;
        maxValue = (maxValue < srcVolume[i])?srcVolume[i] : maxValue;
        maxValue = (maxValue < dstVolume[i])?dstVolume[i] : maxValue;
    }
    minValue = maxValue * 5.0e-2;
    ui->volumeView->addGraph(srcVolume, volumeLength);
    ui->volumeView->addGraph(dstVolume, volumeLength);
    ui->volumeView->addGraph(dstTransVolume, volumeLength);
    ui->volumeView->setGraphStyle(QMyGraphWidget::logGraph, minValue, maxValue);
}

void normalizeWaveBuffer(vector<double> &waveBuffer, QUtauParameters &param)
{
    int index;
    double sum1 = 0.0, sum2 = 0.0;
    index = fs * param.fixedLength / 1000.0;
    for(int i = index - 1024; 0 <= i && i < index + 1024 && i < waveBuffer.size(); i++){
        sum1 += waveBuffer[i] * waveBuffer[i];
    }
    for(int i = 0; i < 2048 && i < waveBuffer.size(); i++){
        sum2 += waveBuffer[i] * waveBuffer[i];
    }
    sum1 = (sum1 < sum2)? sum2 : sum1;
    sum1 = (0.06 / sqrt(sum1 / 2048.0)) / 2048.0;
    for(unsigned int i = 0; i < waveBuffer.size(); i++){
        waveBuffer[i] *= sum1;
    }
}

bool analysisWindow::readWaveFiles()
{
    bool ret = true;
    QByteArray byteArray;
    std::string tmpFileName;
    waveFileEx tmpWave;
    double phonemeLength = 0.0;

    byteArray = src.fileName.toLocal8Bit();
    tmpFileName = byteArray.data();
    ret &= (tmpWave.readWaveFile(tmpFileName) == 1);

    if(ret){
        tmpWave.getWaveBuffer(srcWaveBuffer, src.leftBlank, src.rightBlank);
        // 転写元の長さに合わせるための処理．
        if(src.rightBlank > 0.0){
            phonemeLength = (double)tmpWave.getWaveLength() / (double)fs * 1000.0 - src.leftBlank - src.rightBlank;
        }else{
            phonemeLength = -src.rightBlank;
        }
    }

    byteArray = dst.fileName.toLocal8Bit();
    tmpFileName = byteArray.data();
    ret &= (tmpWave.readWaveFile(tmpFileName) == 1);

    if(ret){
        // dst はここで設定を src に合わせる．長さが一定でないと後で困るための処理．
        double diffLength = src.preUtterance - dst.preUtterance;
        this->dst.leftBlank -= diffLength;
        this->dst.fixedLength += diffLength;
        this->dst.rightBlank = -phonemeLength;
        tmpWave.getWaveBuffer(dstWaveBuffer, dst.leftBlank, dst.rightBlank);
        normalizeWaveBuffer(srcWaveBuffer, src);
        normalizeWaveBuffer(dstWaveBuffer, dst);
    }
    return ret;
}

void analysisWindow::setParameters(QUtauParameters &src, QUtauParameters &dst, bool visible)
{
    this->setUpdatesEnabled(FALSE);
    this->src = src;
    this->dst = dst;

    // 波形を読む
    if(!readWaveFiles()){
        // 失敗したらダイアログを表示して関数を抜ける
        if(visible){
            QMessageBox::StandardButton dialogError = QMessageBox::information(this, this->windowTitle(), tr("Could not open wave file(s)."));
            close();
        }
        return;
    }

    // 時間がかかるのでダイアログを表示
    QDialog *dialog = NULL;
    QLabel *label = NULL;
    if(visible){
        dialog = new QDialog(this);
        label = new QLabel(dialog);
        dialog->resize(256, 64);
        label->resize(256, 64);
        label->setAlignment(Qt::AlignCenter);
        label->setText(tr("<font size=+1>now Analyzing...<br />This may take some time.</font>"));
        dialog->show();
        dialog->repaint();
        this->setUpdatesEnabled(TRUE);
    }

    // 各 Widget を設定する
    setVolumeView();
    setTransformView();
    delete[] transInverse;
    transInverse = new double[volumeLength];
    calculateMatching(transFunction, NULL, srcVolume, dstVolume, volumeLength, transInverse);
    for(int i = 0; i < volumeLength; i++){
        int index = transFunction[i];
        if(index >= volumeLength - 1){
            dstTransVolume[i] = dstVolume[volumeLength - 1];
        }else{
            dstTransVolume[i] = interpolateArray(transFunction[i], dstVolume);
        }
    }
    setSpecgramView();
    if(visible){
        ui->specgramView->setSpecgram(&srcSpecgram, &dstTransSpecgram);
        setSpectrumView();
        this->setUpdatesEnabled(TRUE);
        dialog->close();
        delete label;
        delete dialog;
    }
}

analysisWindow::analysisWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::analysisWindow)
{
    ui->setupUi(this);
    transInverse = transNormal = dstTransVolume = transFunction = srcVolume = dstVolume = NULL;
    srcSpectrum = dstSpectrum = morphSpectrum = NULL;
    volumeLength = 0;
    position = 0;
    for(int i = 0; i < 2049; i++){
        han[i] = 0.5 + 0.5 * cos(PI * (double)(i - 1024) / 1024.0);
    }
}

analysisWindow::~analysisWindow()
{
    delete ui;

    delete[] srcVolume;
    delete[] dstVolume;
    delete[] dstTransVolume;
    delete[] transFunction;
    delete[] transInverse;
    delete[] transNormal;
    delete[] morphSpectrum;
}

analysisWindow::specgram::specgram()
{
    f0 = t = NULL;
    ap = spec = NULL;
    timeLength = 0;
    fftLength = 0;
}

analysisWindow::specgram::~specgram()
{
    destroy();
}

void analysisWindow::specgram::create(int timeLength, int fftLength)
{
    if(timeLength <= 0 || fftLength <= 0){return;}
    destroy();
    this->timeLength = timeLength;
    this->fftLength = fftLength;
    f0 = new double[timeLength];
    t = new double[timeLength];
    spec = new double*[timeLength];
    ap   = new double*[timeLength];
    for(int i = 0; i < timeLength; i++){
        spec[i] = new double[fftLength];
        ap[i] = new double[fftLength];
    }
}

void analysisWindow::specgram::destroy()
{
    delete[] f0;
    delete[] t;
    if(spec){
        for(int i = 0; i < timeLength; i++){
            delete[] spec[i];
        }
        delete[] spec;
    }
    if(ap){
        for(int i = 0; i < timeLength; i++){
            delete[] ap[i];
        }
        delete[] ap;
    }
    f0 = t = NULL; timeLength = 0; fftLength = 0;
    ap = spec = NULL;
}
