///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB                                   //
//                                                                               //
// 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 as 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 V3 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 "filesourcesource.h"
#include "filesourcereport.h"

#if (defined _WIN32_) || (defined _MSC_VER)
#include "windows_time.h"
#include <stdint.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif

#include <QDebug>

#include "dsp/dspcommands.h"
#include "dsp/devicesamplesink.h"
#include "dsp/hbfilterchainconverter.h"
#include "dsp/filerecord.h"
#include "dsp/wavfilerecord.h"
#include "util/db.h"

FileSourceSource::FileSourceSource() :
	m_fileName("..."),
	m_sampleSize(0),
	m_centerFrequency(0),
    m_frequencyOffset(0),
    m_fileSampleRate(0),
    m_samplesCount(0),
	m_sampleRate(0),
    m_deviceSampleRate(0),
	m_recordLengthMuSec(0),
    m_startingTimeStamp(0),
    m_running(false),
    m_guiMessageQueue(nullptr)
{
    m_linearGain = 1.0f;
	m_magsq = 0.0f;
	m_magsqSum = 0.0f;
	m_magsqPeak = 0.0f;
	m_magsqCount = 0;
}

FileSourceSource::~FileSourceSource()
{
}

void FileSourceSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
{
    std::for_each(
        begin,
        begin + nbSamples,
        [this](Sample& s) {
            pullOne(s);
        }
    );
}

void FileSourceSource::pullOne(Sample& sample)
{
    Real re;
    Real im;

    struct Sample16
    {
        int16_t real;
        int16_t imag;
    };

    struct Sample24
    {
        int32_t real;
        int32_t imag;
    };

    if (!m_running)
    {
        re = 0;
        im = 0;
    }
	else if (m_sampleSize == 16)
	{
        Sample16 sample16;
        m_ifstream.read(reinterpret_cast<char*>(&sample16), sizeof(Sample16));

        if (m_ifstream.eof()) {
            handleEOF();
        } else {
            m_samplesCount++;
        }

        // scale to +/-1.0
        re = (sample16.real * m_linearGain) / 32760.0f;
        im = (sample16.imag * m_linearGain) / 32760.0f;
    }
    else if (m_sampleSize == 24)
    {
        Sample24 sample24;
        m_ifstream.read(reinterpret_cast<char*>(&sample24), sizeof(Sample24));

        if (m_ifstream.eof()) {
            handleEOF();
        } else {
            m_samplesCount++;
        }

        // scale to +/-1.0
        re = (sample24.real * m_linearGain) / 8388608.0f;
        im = (sample24.imag * m_linearGain) / 8388608.0f;
    }
    else
    {
        re = 0;
        im = 0;
    }


    if (SDR_TX_SAMP_SZ == 16)
    {
        sample.setReal(re * 32768.0f);
        sample.setImag(im * 32768.0f);
    }
    else if (SDR_TX_SAMP_SZ == 24)
    {
        sample.setReal(re * 8388608.0f);
        sample.setImag(im * 8388608.0f);
    }
    else
    {
        sample.setReal(0);
        sample.setImag(0);
    }

    Real magsq = re*re + im*im;
    m_movingAverage(magsq);
    m_magsq = m_movingAverage.asDouble();
    m_magsqSum += magsq;

    if (magsq > m_magsqPeak) {
        m_magsqPeak = magsq;
    }

    m_magsqCount++;
}

void FileSourceSource::openFileStream(const QString& fileName)
{
	m_fileName = fileName;

	if (m_ifstream.is_open()) {
		m_ifstream.close();
	}

#ifdef Q_OS_WIN
	m_ifstream.open(m_fileName.toStdWString().c_str(), std::ios::binary | std::ios::ate);
#else
	m_ifstream.open(m_fileName.toStdString().c_str(), std::ios::binary | std::ios::ate);
#endif
	quint64 fileSize = m_ifstream.tellg();
    m_samplesCount = 0;

    if (m_fileName.endsWith(".wav"))
    {
        WavFileRecord::Header header;
        m_ifstream.seekg(0, std::ios_base::beg);
        bool headerOK = WavFileRecord::readHeader(m_ifstream, header);
        m_fileSampleRate = header.m_sampleRate;
        if (header.m_auxiHeader.m_size > 0)
        {
            // Some WAV files written by SDR tools have auxi header
            m_centerFrequency = header.m_auxi.m_centerFreq;
            m_startingTimeStamp = header.getStartTime().toMSecsSinceEpoch() / 1000;
        }
        else
        {
            // Attempt to extract start time and frequency from filename
            QDateTime startTime;
            if (WavFileRecord::getStartTime(m_fileName, startTime)) {
                m_startingTimeStamp = startTime.toMSecsSinceEpoch() / 1000;
            }
            WavFileRecord::getCenterFrequency(m_fileName, m_centerFrequency);
        }
        m_sampleSize = header.m_bitsPerSample;

        if (headerOK && (m_fileSampleRate > 0) && (m_sampleSize > 0))
        {
            m_recordLengthMuSec = ((fileSize - m_ifstream.tellg()) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
        }
        else
        {
            qCritical("FileSourceSource::openFileStream: invalid .wav file");
            m_recordLengthMuSec = 0;
        }

        if (getMessageQueueToGUI())
        {
            FileSourceReport::MsgReportHeaderCRC *report = FileSourceReport::MsgReportHeaderCRC::create(headerOK);
            getMessageQueueToGUI()->push(report);
        }
    }
    else if (fileSize > sizeof(FileRecord::Header))
	{
	    FileRecord::Header header;
	    m_ifstream.seekg(0,std::ios_base::beg);
		bool crcOK = FileRecord::readHeader(m_ifstream, header);
		m_fileSampleRate = header.sampleRate;
		m_centerFrequency = header.centerFrequency;
		m_startingTimeStamp = header.startTimeStamp;
		m_sampleSize = header.sampleSize;
		QString crcHex = QString("%1").arg(header.crc32 , 0, 16);

	    if (crcOK)
	    {
	        qDebug("FileSourceSource::openFileStream: CRC32 OK for header: %s", qPrintable(crcHex));
	        m_recordLengthMuSec = ((fileSize - sizeof(FileRecord::Header)) * 1000000UL) / ((m_sampleSize == 24 ? 8 : 4) * m_fileSampleRate);
	    }
	    else
	    {
	        qCritical("FileSourceSource::openFileStream: bad CRC32 for header: %s", qPrintable(crcHex));
	        m_recordLengthMuSec = 0;
	    }

		if (getMessageQueueToGUI())
        {
			FileSourceReport::MsgReportHeaderCRC *report = FileSourceReport::MsgReportHeaderCRC::create(crcOK);
			getMessageQueueToGUI()->push(report);
		}
	}
	else
	{
		m_recordLengthMuSec = 0;
	}

	qDebug() << "FileSourceSource::openFileStream: " << m_fileName.toStdString().c_str()
			<< " fileSize: " << fileSize << " bytes"
			<< " length: " << m_recordLengthMuSec << " microseconds"
			<< " sample rate: " << m_fileSampleRate << " S/s"
			<< " center frequency: " << m_centerFrequency << " Hz"
			<< " sample size: " << m_sampleSize << " bits"
            << " starting TS: " << m_startingTimeStamp << "s";

	if (getMessageQueueToGUI())
    {
	    FileSourceReport::MsgReportFileSourceStreamData *report = FileSourceReport::MsgReportFileSourceStreamData::create(m_fileSampleRate,
	            m_sampleSize,
	            m_centerFrequency,
	            m_startingTimeStamp,
	            m_recordLengthMuSec); // file stream data
	    getMessageQueueToGUI()->push(report);
	}

	if (m_recordLengthMuSec == 0) {
	    m_ifstream.close();
	}
}

void FileSourceSource::seekFileStream(int seekMillis)
{
	if ((m_ifstream.is_open()) && !m_running)
	{
        quint64 seekPoint = ((m_recordLengthMuSec * seekMillis) / 1000) * m_fileSampleRate;
        seekPoint /= 1000000UL;
        m_samplesCount = seekPoint;
        seekPoint *= (m_sampleSize == 24 ? 8 : 4); // + sizeof(FileRecord::Header)
		m_ifstream.clear();
		m_ifstream.seekg(seekPoint + sizeof(FileRecord::Header), std::ios::beg);
	}
}

void FileSourceSource::handleEOF()
{
    if (!m_ifstream.is_open()) {
        return;
    }

    if (getMessageQueueToGUI())
    {
        FileSourceReport::MsgReportFileSourceStreamTiming *report = FileSourceReport::MsgReportFileSourceStreamTiming::create(getSamplesCount());
        getMessageQueueToGUI()->push(report);
    }

    if (m_settings.m_loop)
    {
        m_ifstream.clear();
        m_ifstream.seekg(0, std::ios::beg);
        m_samplesCount = 0;
    }
    else
    {
        if (getMessageQueueToGUI())
        {
            FileSourceReport::MsgPlayPause *report = FileSourceReport::MsgPlayPause::create(false);
            getMessageQueueToGUI()->push(report);
        }
    }
}

void FileSourceSource::applySettings(const FileSourceSettings& settings, bool force)
{
    qDebug() << "FileSourceSource::applySettings:"
        << "m_fileName:" << settings.m_fileName
        << "m_loop:" << settings.m_loop
        << "m_gainDB:" << settings.m_gainDB
        << "m_log2Interp:" << settings.m_log2Interp
        << "m_filterChainHash:" << settings.m_filterChainHash
        << " force: " << force;


    if ((m_settings.m_gainDB != settings.m_gainDB) || force) {
        m_linearGain = CalcDb::powerFromdB(settings.m_gainDB/2.0); // Divide by two for power gain to voltage gain conversion
    }

    m_settings = settings;
}