/*
    This file is part of EqualizerAPO, a system-wide equalizer.
    Copyright (C) 2015  Jonas Thedering

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <QElapsedTimer>

#include "FilterEngine.h"
#include "AnalysisThread.h"

AnalysisThread::AnalysisThread()
{

}

AnalysisThread::~AnalysisThread()
{
	mutex.lock();
	quit = true;
	condition.wakeAll();
	mutex.unlock();

	wait();

	if (resultFreqData != NULL)
		fftwf_free(resultFreqData);

	if (buf != NULL)
		delete buf;
	if (buf2 != NULL)
		delete buf2;
	if (timeData != NULL)
		fftwf_free(timeData);
	if (freqData != NULL)
		fftwf_free(freqData);
	if (planForward != NULL)
		fftwf_destroy_plan(planForward);
}

void AnalysisThread::setParameters(DeviceAPOInfo* device, int channelMask, int channelIndex, QString configPath, int frameCount)
{
	QMutexLocker mutexLocker(&mutex);
	this->device = *device;
	this->channelMask = channelMask;
	this->channelIndex = channelIndex;
	this->configPath = configPath;
	this->frameCount = frameCount;

	condition.wakeAll();
}

void AnalysisThread::beginGetResult()
{
	mutex.lock();
}

void AnalysisThread::endGetResult()
{
	mutex.unlock();
}

fftwf_complex* AnalysisThread::getFreqData() const
{
	return resultFreqData;
}

int AnalysisThread::getFreqDataLength() const
{
	return freqDataLength;
}

int AnalysisThread::getFreqDataSampleRate() const
{
	return freqDataSampleRate;
}

double AnalysisThread::getPeakGain() const
{
	return peakGain;
}

int AnalysisThread::getLatency() const
{
	return latency;
}

double AnalysisThread::getInitializationTime() const
{
	return initializationTime;
}

double AnalysisThread::getProcessingTime() const
{
	return processingTime;
}

int AnalysisThread::getProcessedFrames() const
{
	return processedFrames;
}

void AnalysisThread::run()
{
	while (true)
	{
		mutex.lock();
		if (!quit && this->frameCount == 0)
			condition.wait(&mutex);
		if (quit)
		{
			mutex.unlock();
			break;
		}

		DeviceAPOInfo device = this->device;
		int channelMask = this->channelMask;
		int channelIndex = this->channelIndex;
		QString configPath = this->configPath;
		int frameCount = this->frameCount;
		this->frameCount = 0;
		mutex.unlock();

		QElapsedTimer timer;
		timer.start();

		unsigned channelCount = device.channelCount;
		if (channelMask != 0 && channelMask != device.channelMask)
		{
			channelCount = 0;
			for (int i = 0; i < 31; i++)
			{
				int channelPos = 1 << i;
				if (channelMask & channelPos)
					channelCount++;
			}
		}

		unsigned sampleRate = device.sampleRate;

		qint64 startTime = timer.nsecsElapsed();
		FilterEngine engine;
		engine.setDeviceInfo(device.isInput, true, device.deviceName, device.connectionName, device.deviceGuid);
		engine.initialize(sampleRate, channelCount, channelCount, channelCount, channelMask, frameCount, configPath.toStdWString());
		double initializationTime = (timer.nsecsElapsed() - startTime) / 1e6;

		if (frameCount != lastFrameCount || channelCount != lastChannelCount)
		{
			if (buf != NULL)
				delete buf;
			buf = new float[frameCount * channelCount];
			memset(buf, 0, frameCount * channelCount * sizeof(float));

			if (buf2 != NULL)
				delete buf2;
			buf2 = new float[frameCount * channelCount];
		}
		for (unsigned i = 0; i < channelCount; i++)
			buf[i] = 1.0f;

		if (frameCount != lastFrameCount)
		{
			if (timeData != NULL)
				fftwf_free(timeData);
			timeData = fftwf_alloc_real(frameCount);

			if (freqData != NULL)
				fftwf_free(freqData);
			freqData = fftwf_alloc_complex(frameCount);

			if (planForward != NULL)
				fftwf_destroy_plan(planForward);
			planForward = fftwf_plan_dft_r2c_1d(frameCount, timeData, freqData, FFTW_ESTIMATE);
		}

		lastFrameCount = frameCount;
		lastChannelCount = channelCount;

		int latency = 0;
		int startFrame = -1;
		double processingTime = 0.0;
		int processedFrames = 0;
		while (true)
		{
			qint64 startTime = timer.nsecsElapsed();
			engine.process(buf2, buf, frameCount);
			processingTime += (timer.nsecsElapsed() - startTime) / 1e6;
			processedFrames += frameCount;

			if (startFrame != -1)
			{
				for (int i = 0; i < startFrame; i++)
				{
					timeData[frameCount - startFrame + i] = buf2[i * channelCount + channelIndex];
				}
				break;
			}

			for (int i = 0; i < frameCount; i++)
			{
				float s = buf2[i * channelCount + channelIndex];
				if (abs(s) > 1e-5f)
				{
					startFrame = i;
					break;
				}
			}

			if (startFrame != -1)
			{
				for (int i = 0; i < frameCount - startFrame; i++)
				{
					timeData[i] = buf2[(startFrame + i) * channelCount + channelIndex];
				}

				if (startFrame == 0)
					break;
			}

			if (latency == 0)
			{
				for (unsigned i = 0; i < channelCount; i++)
					buf[i] = 0.0f;
			}

			if (startFrame == -1)
				latency += frameCount;
		}
		latency += startFrame;

		fftwf_execute(planForward);

		double peakGain = -DBL_MAX;

		for (int i = 0; i < frameCount; i++)
		{
			float sqrGain = freqData[i][0] * freqData[i][0] + freqData[i][1] * freqData[i][1];
			if (sqrGain > peakGain)
				peakGain = sqrGain;
		}
		peakGain = sqrt(peakGain);
		peakGain = log10(peakGain) * 20.0;

		mutex.lock();
		if (this->freqDataLength != frameCount)
		{
			if (resultFreqData != NULL)
				fftwf_free(resultFreqData);
			resultFreqData = fftwf_alloc_complex(frameCount);
		}
		memcpy(resultFreqData, freqData, frameCount * sizeof(fftwf_complex));
		this->freqDataLength = frameCount;
		this->freqDataSampleRate = sampleRate;
		this->latency = latency;
		this->peakGain = peakGain;
		this->initializationTime = initializationTime;
		this->processingTime = processingTime;
		this->processedFrames = processedFrames;
		mutex.unlock();

		qDebug("Analysis took %.1f ms", timer.nsecsElapsed() / 1e6);

		emit analysisFinished();
	}
}
