// bytran -|- spectral calculations for portable devices using the HITRAN database
//
// Developed by Dzianis Pliutau (Denis Plutov) and Kostya Roslyakov
// www.bytran.org | contact@bytran.org
// Copyright (c) 2016 Dzianis Pliutau
// Distributed under the MIT license
//
// The MIT License (MIT)
// Copyright (c) 2016 Dzianis Pliutau
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



// Object used to pass spectral results calculations to the plot from the core module and its components
#include "spectraresultsdataobject.h"
#include "auxiliary.h"
#include <QMessageBox>
#include <QApplication>
#include <QScreen>
#include <cmath>

SpectraResultsDataObject::SpectraResultsDataObject(QObject *parent, BytranCore *inCore) : QObject(parent)
{
    // Passing in the core object into the internal variable
    this->core = inCore;
    m_showTotal = core->getHorizSpectra()->getShowTotal();
    m_showPlainTotal = true;
    m_showInstrTotal = true;
    m_showRadiance = false;
    m_multiplier = 1;

    // Setting the total view to display both the total spectrum as well as the smoothed instrument function processed one
    totalView = 0;

    // Initializing the object to calculate the radiance
    radiance = new Radiance();

    // Creating connections
    createActions();
}

// Method to create connections
void SpectraResultsDataObject::createActions()
{
    connect(this->core, SIGNAL(updateSpectra(bool, bool)), this, SLOT(setUpdateSpectra(bool, bool)));
}
// Method returns the current spectrum
Spectrum SpectraResultsDataObject::spectrum() const
{
    return m_spectrum;
}

QVector<double> SpectraResultsDataObject::wnGrid() const { return this->m_wnGrid; }

// Color to use for plottin the spectrum
QString SpectraResultsDataObject::lineColor() const { return m_lineColor; }

// Methods to get and set the current spectrum index
int SpectraResultsDataObject::currSpecIndex() const { return this->m_currSpecIndex; }
void SpectraResultsDataObject::setCurrSpecIndex(const int currSpecIndex)
{
    if (currSpecIndex != m_currSpecIndex)
    {
        m_currSpecIndex = currSpecIndex;
        emit currSpecIndexChanged();
    }
}

// Methods to set the number of points along the vertical axis
int SpectraResultsDataObject::heightPoints() const { return this->m_heightPoints; }
void SpectraResultsDataObject::setHeightPoints(const int &heightPoints)
{
    if (heightPoints != m_heightPoints)
    {
        m_heightPoints = heightPoints;
        emit heightPointsChanged();
    }
}

// Method to set a variable indicating if the plain total spectrum is to be displayed
bool SpectraResultsDataObject::showPlainTotal() const { return this->m_showPlainTotal; }
void SpectraResultsDataObject::setShowPlainTotal(const bool &showPlainTotal)
{
    if (showPlainTotal != m_showPlainTotal)
    {
        m_showPlainTotal = showPlainTotal;
        emit showPlainTotalChanged();
        setUpdateSpectra(false, true);
    }
}

// Method to set a variable indicating if the instrument function processed spectrum is to be displayed
bool SpectraResultsDataObject::showInstrTotal() const { return this->m_showInstrTotal; }
void SpectraResultsDataObject::setShowInstrTotal(const bool &showInstrTotal)
{
    if (showInstrTotal != m_showInstrTotal)
    {
        m_showInstrTotal = showInstrTotal;
        emit showInstrTotalChanged();
        setUpdateSpectra(false, true);
    }
}

// Method gets and sets a variable indicating if radiance is to be displayed
bool SpectraResultsDataObject::showRadiance() const { return this->m_showRadiance; }
void SpectraResultsDataObject::setShowRadiance(const bool &showRadiance)
{
    if (showRadiance != m_showRadiance)
    {
        m_showRadiance = showRadiance;
        emit showRadianceChanged();
        setUpdateSpectra(false, true);
    }
}

double SpectraResultsDataObject::minX() const { return core->getWavelengths()->getStartWlCurrUnits(); }
double SpectraResultsDataObject::maxX() const { return core->getWavelengths()->getEndWlCurrUnits(); }
double SpectraResultsDataObject::minY() const { return core->getYAxis()->getMinYPlot(); }
double SpectraResultsDataObject::maxY() const { return core->getYAxis()->getMaxYPlot(); }

// Method to return the multiplier value (used for the radiance spectrum)
double SpectraResultsDataObject::multiplier() const { return this->m_multiplier; }

bool SpectraResultsDataObject::showTotal() const { return this->m_showTotal; }
void SpectraResultsDataObject::setShowTotal(const bool &showTotal)
{
    if (showTotal != m_showTotal)
    {
        m_showTotal = showTotal;
        core->getHorizSpectra()->setShowTotal(m_showTotal);
        emit showTotalChanged();
        updatePlotAllPreserveScale();
    }
}

// Title for the horizontal axis
QString SpectraResultsDataObject::xAxisTitle()
{
    return core->getWavelengths()->getAxisTitle();
}

// Method to get the Y axis title
QString SpectraResultsDataObject::yAxisTitle()
{
    return this->m_yAxisTitle;
}

// Slot called to update spectra
void SpectraResultsDataObject::setUpdateSpectra(bool updateScale, bool updateTotal)
{
    int i;  // Loop variable
    int j;  // Loop variable
    double optDepth;  // Auxiliary optical depth variable
    double maxCurUnits;  // Maximum in current units
    bool condition1;  // Condition 1
    bool condition2;  // Condition 2
    int power;        // Integer determining to what power another variable is to be raised


    // Updating the scale
    if (updateScale)
    {
        m_minY = core->getYAxis()->getMinYPlot();
        m_maxY = core->getYAxis()->getMaxYPlot();
        m_minX = core->getWavelengths()->getStartWlCurrUnits();
        m_maxX = core->getWavelengths()->getEndWlCurrUnits();
    }

    // Getting the axes titles based on the current settings
    if (this->m_showRadiance == true) { m_yAxisTitle = "W / ( sr * cm^2 * cm-1 )"; }
                                 else { m_yAxisTitle = core->getYAxis()->getYAxisTitle(); }
    m_xAxisTitle = core->getWavelengths()->getAxisTitle();

    // Setting the wavelnegth grid in current units
    m_wnGrid.resize(core->getWavelengths()->getWns()->count());
    for (i = 0; i < core->getWavelengths()->getWns()->count(); i++)
    {
        m_wnGrid[i] = core->getWavelengths()->fromDefaultToCurrent(core->getWavelengths()->getWnsAt(i));
    }

    emit wnGridChanged();
    emit yAxisTitleChanged();
    emit xAxisTitleChanged();

    // Updating the scale
    if (updateScale)
    {
        emit minXChanged();
        emit maxXChanged();
        emit minYChanged();
        emit maxYChanged();
    }


    // Choosing between the showing total spectra or the individual molecular components
    if (core->getHorizSpectra()->getShowTotal() == true)
    {
        if (updateTotal)  // No need to recalculate and resend the total spectrum to the plot because it does not depend on the vertical resolution of the screen
        {
            // Clearing the pushed data (for saving to a file)
            core->getResultsSaver()->clearPushedData();
            // Appending the wavenumbers array to the data to be saved
            core->getResultsSaver()->addData(*(core->getWavelengths()->getWns()), "Wavenumber, cm-1");
            // Appending the wavelength array to the data to be saved
            core->getResultsSaver()->addData(m_wnGrid, core->getWavelengths()->getAxisTitle());

            m_currSpecIndex = -1;  // Sending the command to accumulate the spectra to plot
            emit currSpecIndexChanged();
            emit lineColorChanged();
            emit spectrumChanged();

            // Filling the arrays with 0 for the composite spectrum
            core->getHorizSpectra()->getComp()->getY()->resize(core->getWavelengths()->getWns()->count());
            core->getHorizSpectra()->getComp()->getX()->resize(0);
            for (i = 0; i < core->getWavelengths()->getWns()->count(); i++)
            {
                 core->getHorizSpectra()->getComp()->setYAt(i, 0);
            }

            // Recalculating the total composite by taking into account the selected spectra
            // Forming the composite spectrum
            for (j = 0; j < core->getParams()->getMol()->count(); j++)
            {
                // Only adding the selected spectra to the plot
                if (core->getParams()->getMolAt(j)->getSelected() == true)
                {
                    // Summing spectra by wavelength
                    for (i = 0; i < core->getWavelengths()->getWns()->count(); i++)
                    {
                        optDepth = core->getHorizSpectra()->getComp()->getYAt(i) + core->getHorizSpectra()->getMolAt(j)->getSpec()->getYAt(i);
                        core->getHorizSpectra()->getComp()->setYAt(i, optDepth);
                    }
                }
            }

            // Calculating radiance
            if ((this->m_showRadiance) && (this->m_showPlainTotal))
            {
                // Calculating the radiance
                radiance->calculateRadiance(core->getHorizSpectra()->getComp(), core->getHorizSpectra()->getPlainRadiance(), core->getWavelengths()->getWns(), core->getParams()->getHorizParams()->getTemp());
                core->getHorizSpectra()->getPlainRadiance()->setMaxValue(Auxiliary::maximum(core->getHorizSpectra()->getPlainRadiance()->getY()));

                // Calculating the multiplier
                power = (int) (log10(core->getHorizSpectra()->getPlainRadiance()->getMaxValue() / core->getYAxis()->getMaxYPlot()));
                m_multiplier = pow(10, power);
                m_multiplier = 1 / m_multiplier;
                emit multiplierChanged();
            }

            // Forming the instrument function adjusted spectrum
            if ((core->getInstrFunct()->getInstrFunctIndex() != 0) && (m_showInstrTotal == true))
            {
                // Calculating the instrument function processed total spectrum
                core->getInstrFunct()->setWavelength(core->getWavelengths());
                core->getInstrFunct()->convolveSpectrum(core->getHorizSpectra()->getComp(), core->getHorizSpectra()->getInstrFunctTotal());
                core->getHorizSpectra()->getInstrFunctTotal()->setMaxValue(Auxiliary::maximum(core->getHorizSpectra()->getInstrFunctTotal()->getY()));

                // Calculating radiance
                if (this->m_showRadiance)
                {
                    // Calculating the radiance
                    radiance->calculateRadiance(core->getHorizSpectra()->getInstrFunctTotal(), core->getHorizSpectra()->getInstrFunctRadiance(), core->getWavelengths()->getWns(), core->getParams()->getHorizParams()->getTemp());
                    core->getHorizSpectra()->getInstrFunctRadiance()->setMaxValue(Auxiliary::maximum(core->getHorizSpectra()->getInstrFunctRadiance()->getY()));

                    // Calculating the multiplier (only if it hasn't been calculated for the plain spectrum)
                    if (this->m_showPlainTotal == false)
                    {
                        // Calculating the multiplier
                        power = (int) (log10(core->getHorizSpectra()->getPlainRadiance()->getMaxValue() / core->getYAxis()->getMaxYPlot()));
                        m_multiplier = pow(10, power);
                        m_multiplier = 1 / m_multiplier;
                        emit multiplierChanged();
                    }
                }
            }

            // Ploting unprocessed total or the corresponding radiance
            if (m_showPlainTotal == true)
            {
                if (this->m_showRadiance == true)  // Radiance
                {
                    // Setting the maximum spectrum value
                    core->getHorizSpectra()->getPlainRadiance()->setMaxValue(Auxiliary::maximum(core->getHorizSpectra()->getPlainRadiance()->getY()));

                    // Transfering the total spectrum to the variable used to transfer data over to the plotting object
                    m_spectrum.getY()->resize(core->getHorizSpectra()->getPlainRadiance()->getY()->count());
                    for (j = 0; j < core->getHorizSpectra()->getPlainRadiance()->getY()->count(); j++)
                    {
                        m_spectrum.setYAt(j,  core->getHorizSpectra()->getPlainRadiance()->getYAt(j));
                    }
                    m_spectrum.setMaxValue(core->getHorizSpectra()->getPlainRadiance()->getMaxValue());

                    m_lineColor = "black";
                    m_currSpecIndex = 0;
                    emit currSpecIndexChanged();
                    emit lineColorChanged();
                    emit spectrumChanged();

                    // Appending the radiance spectrum to the sectra to be saved
                    core->getResultsSaver()->addData(*(m_spectrum.getY()), "Radiance");
                }
                else  // Total spectrum
                {
                    // Filling arrays with 0 for the composite spectrum
                    core->getHorizSpectra()->getComp()->setMaxValue(Auxiliary::maximum(core->getHorizSpectra()->getComp()->getY()));

                    // Transfering the total spectrum to the variable used to transfer data over to the ploting object
                    m_spectrum.getY()->resize(core->getHorizSpectra()->getComp()->getY()->count());
                    for (j = 0; j < core->getHorizSpectra()->getComp()->getY()->count(); j++)
                    {
                        m_spectrum.setYAt(j,  core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getComp()->getYAt(j)));
                    }
                    m_spectrum.setMaxValue(core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getComp()->getMaxValue()));

                    m_lineColor = "black";
                    m_currSpecIndex = 0;
                    emit currSpecIndexChanged();
                    emit lineColorChanged();
                    emit spectrumChanged();

                    // Appending the total spectum to the spectra to be saved
                    core->getResultsSaver()->addData(*(m_spectrum.getY()), "Total");
                }
            }

            // Transfering the instrument function adjusted spectrum over to the plot (if the instrument function is selected)
            if ((core->getInstrFunct()->getInstrFunctIndex() != 0) && (m_showInstrTotal == true))
            {
                if (this->m_showRadiance == true)  //  Instrument function processed Radiance
                {
                    // Transfering the total instrument function adjusted spectrum to the variable used to transfer data over to the plotting object
                    m_spectrum.getY()->resize(core->getHorizSpectra()->getInstrFunctRadiance()->getY()->count());
                    for (j = 0; j < core->getHorizSpectra()->getInstrFunctRadiance()->getY()->count(); j++)
                    {
                        m_spectrum.setYAt(j,  core->getHorizSpectra()->getInstrFunctRadiance()->getYAt(j));
                    }
                    m_spectrum.setMaxValue(core->getHorizSpectra()->getInstrFunctRadiance()->getMaxValue());

                    if (m_showPlainTotal == false) { m_lineColor = "black"; }
                    else { m_lineColor = "red"; }

                    m_currSpecIndex = 1;
                    emit currSpecIndexChanged();
                    emit lineColorChanged();
                    emit spectrumChanged();

                    // Appending the instrument funciton processed radiance spectrum to be saved
                    core->getResultsSaver()->addData(*(m_spectrum.getY()), "Instr. funct. radiance");
                }
                else  // Instrument function total spectrum
                {
                    // Transfering the total instrument function adjusted spectrum to the variable used to transfer data over to the plotting object
                    m_spectrum.getY()->resize(core->getHorizSpectra()->getInstrFunctTotal()->getY()->count());
                    for (j = 0; j < core->getHorizSpectra()->getInstrFunctTotal()->getY()->count(); j++)
                    {
                        m_spectrum.setYAt(j,  core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getInstrFunctTotal()->getYAt(j)));
                    }
                    m_spectrum.setMaxValue(core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getInstrFunctTotal()->getMaxValue()));

                    if (m_showPlainTotal == false) { m_lineColor = "black"; }
                    else { m_lineColor = "red"; }

                    m_currSpecIndex = 1;
                    emit currSpecIndexChanged();
                    emit lineColorChanged();
                    emit spectrumChanged();

                    // Appending the insrument function spectrum to the spectra to be saved
                    core->getResultsSaver()->addData(*(m_spectrum.getY()), "Instr. funct. total");
                }
            }
        }

        // Setting the multiplier to be 1 by default
        if (this->m_showRadiance == false)
        {
            m_multiplier = 1;
            emit multiplierChanged();
        }
        else
        {
            m_yAxisTitle = m_yAxisTitle + "   times   " + QString::number(m_multiplier);
            emit yAxisTitleChanged();
        }
    }
    else
    {
        // Clearing the pushed data (for saving to a file)
        core->getResultsSaver()->clearPushedData();
        // Appending the wavenumbers array to the data to be saved
        core->getResultsSaver()->addData(*(core->getWavelengths()->getWns()), "Wavenumber, cm-1");
        // Appending the wavelength array to the data to be saved
        core->getResultsSaver()->addData(m_wnGrid, core->getWavelengths()->getAxisTitle());

        m_currSpecIndex = -1;  // Sending the command to accumulate spectra to plot
        emit currSpecIndexChanged();
        emit lineColorChanged();
        emit spectrumChanged();
        for (m_currSpecIndex = 0; m_currSpecIndex < core->getHorizSpectra()->getMol()->count(); m_currSpecIndex++)
        {
            if (core->getParams()->getMolAt(m_currSpecIndex)->getSelected() == true)
            {
                // Finding the maximum of the spectrum
                maxCurUnits = core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getMolAt(m_currSpecIndex)->getSpec()->getMaxValue());
                // If Transmission units are used the values are inveted with Optical depth resulting in incorrect handling of the threshold condition
                condition1 = (maxCurUnits > (core->getYAxis()->getMinYPlot() + (core->getYAxis()->getMaxYPlot() - core->getYAxis()->getMinYPlot()) / (m_heightPoints * 0.5))) && ((core->getYAxis()->getYPlotUnits() == 0) || (core->getYAxis()->getYPlotUnits() == 2));
                // Reconsider this condition it is too slow need to have a threshold case to replot fewer spectra
                condition2 = (maxCurUnits < (core->getYAxis()->getMaxYPlot() - (core->getYAxis()->getMaxYPlot() - core->getYAxis()->getMinYPlot()) / (m_heightPoints * 0.5))) && (core->getYAxis()->getYPlotUnits() == 1);

                if (condition1 || condition2)
                {
                    m_spectrum.getY()->resize(core->getHorizSpectra()->getMolAt(m_currSpecIndex)->getSpec()->getY()->count());
                    for (j = 0; j < core->getHorizSpectra()->getMolAt(m_currSpecIndex)->getSpec()->getY()->count(); j++)
                    {
                        m_spectrum.setYAt(j, core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getMolAt(m_currSpecIndex)->getSpec()->getYAt(j)));                        
                    }
                    m_spectrum.setMaxValue(core->getYAxis()->toCurrentFromDefault(core->getHorizSpectra()->getMolAt(m_currSpecIndex)->getSpec()->getMaxValue()));
                    m_lineColor = core->getParams()->getMolAt(m_currSpecIndex)->getColor();
                    emit currSpecIndexChanged();
                    emit lineColorChanged();
                    emit spectrumChanged();

                    // Transfering the molecule component to the spectra to be saved
                    core->getResultsSaver()->addData((*m_spectrum.getY()), core->getParams()->getMolAt(m_currSpecIndex)->getChemForm());
                }
            }
        }

        m_multiplier = 1;
        emit multiplierChanged();
    }

    // This condition was introduced to prevent an error when the spectrum is blank
    // When a blank spectum is passed to the plotting routine, then program crashes
    if (m_spectrum.getY()->count() > 3)
    {
        // Sending the command to plot the accumulated spectra
        m_currSpecIndex = -2;
        emit currSpecIndexChanged();
        emit lineColorChanged();
        emit spectrumChanged();
    }
}

// Methods to update plot with the axes preserved
void SpectraResultsDataObject::updatePlotAllPreserveScale()
{
    setUpdateSpectra(false, true);
}
// Methods to get and set the update plot variable
void SpectraResultsDataObject::updatePlot()
{
    setUpdateSpectra(true, true);
}
// Method is used to upate the plot and keep the vertical and horizontal axis scale (this will also prevent the update of the total spectrum)
void SpectraResultsDataObject::updatePlotMolsOnlyPreserveScale()
{
    setUpdateSpectra(false, false);
}

// Updating the X and Y axis scale
void SpectraResultsDataObject::updateXAxis()
{
    m_minX = core->getWavelengths()->getStartWlCurrUnits();
    m_maxX = core->getWavelengths()->getEndWlCurrUnits();
    m_xAxisTitle = core->getWavelengths()->getAxisTitle();
    emit minXChanged();
    emit maxXChanged();
    emit xAxisTitleChanged();

    m_currSpecIndex = -2;  // Sending the command to plot the accumulated spectra
    emit currSpecIndexChanged();
    emit lineColorChanged();
    emit spectrumChanged();
}
void SpectraResultsDataObject::updateYAxis()
{
    m_minY = core->getYAxis()->getMinYPlot();
    m_maxY = core->getYAxis()->getMaxYPlot();
    if (this->m_showRadiance) { m_yAxisTitle = "W / ( sr * cm^2 * cm )"; }
                         else { m_yAxisTitle = core->getYAxis()->getYAxisTitle(); }
    emit minYChanged();
    emit maxYChanged();
    emit yAxisTitleChanged();

    m_currSpecIndex = -2;  // Sending the command to plot the accumulated spectra
    emit currSpecIndexChanged();
    emit lineColorChanged();
    emit spectrumChanged();
}
