// bytran -|- spectral calculations for portable devices using the HITRAN database
//
// Developed by Dzianis Pliutau (Denis Plutov) and Kostya Roslyakov
// www.bytran.org | bytran@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.



// Plot to display graphs and transmission spectra
#include "plot.h"
#include <QtGui>

#include <QMessageBox>
#include <QFontMetrics>
#include <QQuickItemGrabResult>
#include <QNativeGestureEvent>
#include <QApplication>
#include <QColor>
#include <QLocale>
#include <QImage>
#include "math.h"
#include <cmath>
#include <QFileInfo>
#include "auxiliary.h"

Plot::Plot(QQuickItem *parent)
   : QQuickPaintedItem(parent)
{
    // Padding of the plot
    m_xMinPadding = 0;
    m_yMinPadding = 0;
    m_xMaxPadding = 0;
    m_yMaxPadding = 0;
    appendTopString = false;
    m_plotInProgress = false;
}

Spectrum Plot::spectrum() const
{
    return m_spectrum;
}
void Plot::setSpectrum(const Spectrum &spectrum)
{
       m_spectrum = spectrum;
       if (m_currSpecIndex == -1)
       {
           spectra.resize(0);           
       }
       else if (m_currSpecIndex == -2)
       {
           drawPlot();  // Replotting the plot
       }
       else
       {
           spectra.append(m_spectrum); \
           emit spectrumChanged();
       }
}

// Methods to enable or disable the tracking rullers
bool Plot::enableRulers() const { return this->m_enableRulers; }
void Plot::setEnableRulers(const bool &enableRulers)
{
    if (enableRulers != m_enableRulers)
    {
        m_enableRulers = enableRulers;
        emit enableRulersChanged();
        this->update();
    }
}

// Methods to read the height of the plot
int Plot::xHeight() const
{
    return this->height() - m_xMinPadding - m_xMaxPadding - 1;
}


// Methods to set the font point size to use in the plot
qreal Plot::fontPointSize() const { return this->m_fontPointSize; }
void Plot::setFontPointSize(const qreal &fontPointSize)
{
    if (fontPointSize != m_fontPointSize)
    {
        m_fontPointSize = fontPointSize;
        emit fontPointSizeChanged();
    }
}

QVector<double> Plot::wnGrid() const { return this->m_wnGrid; }
void Plot::setWnGrid(const QVector<double> &wnGrid)
{
    m_wnGrid = wnGrid;
    emit wnGridChanged();
}

double Plot::minX() const { return m_minX; }
void Plot::setMinX(const double &minX)
{
    if (minX != m_minX)
    {
        m_minX = minX;
        minXdefault = m_minX;
        emit minXChanged();
    }
}
double Plot::maxX() const { return m_maxX; }
void Plot::setMaxX(const double &maxX)
{
    if (maxX != m_maxX)
    {
        m_maxX = maxX;
        maxXdefault = m_maxX;
        emit maxXChanged();
    }
}
double Plot::minY() const { return m_minY; }
void Plot::setMinY(const double &minY)
{
    if(minY != m_minY)
    {
        m_minY = minY;
        minYdefault = m_minY;
        emit minYChanged();
    }
}
double Plot::maxY() const { return m_maxY; }
void Plot::setMaxY(const double &maxY)
{
    if (maxY != m_maxY)
    {
        m_maxY = maxY;
        maxYdefault = m_maxY;
        emit maxYChanged();
    }    
}

// Plot padding (distance between the edges and the axes area)
double Plot::xMinPadding() const { return this->m_xMinPadding; }
void Plot::setXMinPadding(const double &xMinPadding)
{
    if (xMinPadding != m_xMinPadding)
    {
        m_xMinPadding = xMinPadding;
        emit xMinPaddingChanged();
    }
}
double Plot::xMaxPadding() const { return this->m_xMaxPadding; }
void Plot::setXMaxPadding(const double &xMaxPadding)
{
    if (xMaxPadding != m_xMaxPadding)
    {
        m_xMaxPadding = xMaxPadding;
        emit xMaxPaddingChanged();
    }
}
double Plot::yMinPadding() const { return this->m_yMinPadding; }
void Plot::setYMinPadding(const double &yMinPadding)
{
    if (yMinPadding != m_yMinPadding)
    {
        m_yMinPadding = yMinPadding;
        emit yMinPaddingChanged();
    }
}
double Plot::yMaxPadding() const { return this->m_yMaxPadding; }
void Plot::setYMaxPadding(const double &yMaxPadding)
{
    if (yMaxPadding != m_yMaxPadding)
    {
        m_yMaxPadding = yMaxPadding;
        emit yMaxPaddingChanged();
    }
}

// Titles for the vertical and horizontal axes as well as the plot Title
QString Plot::xAxisTitle() const { return this->m_xAxisTitle; }
void Plot::setXAxisTitle(const QString &xAxisTitle)
{
    if (xAxisTitle != m_xAxisTitle)
    {
        this->m_xAxisTitle = xAxisTitle;
        emit xAxisTitleChanged();
    }
}
QString Plot::yAxisTitle() const { return this->m_yAxisTitle; }
void Plot::setYAxisTitle(const QString &yAxisTitle)
{
    if (yAxisTitle != m_yAxisTitle)
    {
        this->m_yAxisTitle = yAxisTitle;
        emit yAxisTitleChanged();
    }
}
QString Plot::plotTitle() const { return this->m_plotTitle; }
void Plot::setPlotTitle(const QString &plotTitle)
{
    if (plotTitle != m_plotTitle)
    {
        this->m_plotTitle = plotTitle;
        emit plotTitleChanged();
    }
}

// Color to use for plotting the spectrum
QString Plot::lineColor() const { return m_lineColor; }
void Plot::setLineColor(const QString &lineColor)
{
    this->m_lineColor = lineColor;
    if (m_currSpecIndex == -1)
    {
        colors.resize(0);
    }
    else
    {
        colors.append(m_lineColor);
        emit lineColorChanged();
    }
}

// Plot line thickness to use
int Plot::lineThickness() const { return this->m_lineThickness; }
void Plot::setLineThickness(const int &lineThickness)
{
    if (lineThickness != m_lineThickness)
    {
        m_lineThickness = lineThickness;
        emit lineThicknessChanged();
    }
}

// Variables to set the number of minor and major ticks along the horizontal and vertical axes
int Plot::numMinorXTicks() const { return this->m_numMinorXTicks; }
void Plot::setNumMinorXTicks(const int &numMinorXTicks)
{
    if (numMinorXTicks != m_numMinorXTicks)
    {
        this->m_numMinorXTicks = numMinorXTicks;
        emit numMinorXTicksChanged();
    }
}
int Plot::numMajorXTicks() const { return this->m_numMajorXTicks; }
void Plot::setNumMajorXTicks(const int &numMajorXTicks)
{
    if (numMajorXTicks != m_numMajorXTicks)
    {
        this->m_numMajorXTicks = numMajorXTicks;
        emit numMajorXTicksChanged();
    }
}
int Plot::numMinorYTicks() const { return this->m_numMinorYTicks; }
void Plot::setNumMinorYTicks(const int &numMinorYTicks)
{
    if (numMinorYTicks != m_numMinorYTicks)
    {
        this->m_numMinorYTicks = numMinorYTicks;
        emit numMinorYTicksChanged();
    }
}
int Plot::numMajorYTicks() const { return this->m_numMajorYTicks; }
void Plot::setNumMajorYTicks(const int &numMajorYTicks)
{
    if (numMajorYTicks != m_numMajorYTicks)
    {
        m_numMajorYTicks = numMajorYTicks;
        emit numMajorYTicksChanged();
    }
}

// Variable indicating if the plot is to be dipslayed inverted or direct
int Plot::plotInverted() const { return this->m_plotInverted; }
void Plot::setPlotInverted(const bool &plotInverted)
{
    if (plotInverted != m_plotInverted)
    {
        m_plotInverted = plotInverted;
        emit plotInvertedChanged();
    }
}

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

// Methods to get and set the save image path
QString Plot::saveImage() const
{
    return this->m_saveImage;
}
void Plot::setSaveImage(const QString &saveImage)
{
    if (saveImage != m_saveImage)
    {
        m_saveImage = saveImage;
        emit saveImageChanged();
    }
}

// Methods to get and set the top string with the information about the total pressure, pathlegth and temperature
QString Plot::topString() const { return this->m_topString; }
void Plot::setTopString(const QString &topString)
{
    if (topString != m_topString)
    {
        m_topString = topString;
        emit topStringChanged();
    }
}

// Methods to get and set the state when file writing is complete (this could be implemented in a different was as aprameter passing is not needed)
bool Plot::fileWritingComplete() const { return this->m_fileWritingComplete; }
void Plot::setFileWritingComplete(const bool &fileWritingComplete)
{
    m_fileWritingComplete = fileWritingComplete;
    emit fileWritingCompleteChanged();
}

qreal Plot::touchCenterX() const { return this->m_touchCenterX; }
void Plot::setTouchCenterX(const qreal &touchCenterX)
{
    if (touchCenterX != m_touchCenterX)
    {
        m_touchCenterX = touchCenterX;
        minXcurrent = m_minX;
        maxXcurrent = m_maxX;
        emit touchCenterXChanged();
    }
}
qreal Plot::touchCenterY() const { return this->m_touchCenterY; }
void Plot::setTouchCenterY(const qreal &touchCenterY)
{
    if (touchCenterY != m_touchCenterY)
    {
        m_touchCenterY = touchCenterY;
        minYcurrent = m_minY;        
        maxYcurrent = m_maxY;        
        emit touchCenterYChanged();
    }
}
qreal Plot::touchStartX() const { return this->m_touchStartX; }
void Plot::setTouchStartX(const qreal &touchStartX)
{
    if (touchStartX != m_touchStartX)
    {
        m_touchStartX = touchStartX;
        minXcurrent = m_minX;
        maxXcurrent = m_maxX;
        emit touchStartXChanged();
        findXAxisSetPanValues();
        this->update();
    }
}
qreal Plot::touchStartY() const { return this->m_touchStartY; }
void Plot::setTouchStartY(const qreal &touchStartY)
{
    if (touchStartY != m_touchStartY)
    {
        m_touchStartY = touchStartY;
        minYcurrent = m_minY;
        maxYcurrent = m_maxY;
        emit touchStartYChanged();
        findYAxisSetPanValues();
        this->update();
    }
}
qreal Plot::touchEndX() const { return this->m_touchEndX; }
void Plot::setTouchEndX(const qreal &touchEndX)
{
    if (touchEndX != m_touchEndX)
    {
        m_touchEndX = touchEndX;
        emit touchEndXChanged();
        findXAxisSetPanValues();
        this->update();
    }
}
qreal Plot::touchEndY() const { return this->m_touchCenterY; }
void Plot::setTouchEndY(const qreal &touchEndY)
{
    if (touchEndY != m_touchEndY)
    {
        m_touchEndY = touchEndY;
        emit touchEndYChanged();
        findYAxisSetPanValues();
        this->update();
    }
}
qreal Plot::scale() const { return this->m_scale; }
void Plot::setScale(const qreal &scale)
{
    if (scale != m_scale)
    {
        m_scale = scale;
        emit scaleChanged();
        findZoomPhysBoundaries();
        this->update();
    }
}

bool Plot::isHorizontalPinch() const { return this->m_isHorizontalPinch; }
void Plot::setIsHorizontalPinch(const bool &isHorizontalPinch)
{
    if (isHorizontalPinch != m_isHorizontalPinch)
    {
        m_isHorizontalPinch = isHorizontalPinch;
        emit isHorizontalPinchChanged();
    }
}

// Method to find the pan values along the X axis (to pan spectrum)
void Plot::findXAxisSetPanValues()
{
    double deltaXPhys;


    // Determining the shift in physical coordinates along the horizzontal and vertical exes
    deltaXPhys = getPhysX(m_touchEndX) - getPhysX(m_touchStartX);

    // Determining the new X-axis minimum and maximum scale values
    if (((m_minX - deltaXPhys) >= minXdefault) && ((m_maxX - deltaXPhys) <= maxXdefault))
    {
        m_minX = minXcurrent - deltaXPhys;
        m_maxX = maxXcurrent - deltaXPhys;
    }
    else
    {
//        if ((m_minX - deltaXPhys) < minXdefault)
//        {
//            m_minX = minXdefault;
//            m_maxX = m_minX + deltaXPhys;
//        }
//        if ((m_maxX - deltaXPhys) > maxXdefault)
//        {
//            m_maxX = maxXdefault;
//            m_minX = m_maxX - deltaXPhys;
//        }
    }
}
// Method to find the pan values along the Y axis (to pan spectrum)
void Plot::findYAxisSetPanValues()
{
    double deltaYPhys;


    // Determining the shift in physical coordinates along the horizzontal and vertical exes
    deltaYPhys = getPhysY(m_touchEndY) - getPhysY(m_touchStartY);

    // Determining the new Y-axis minimum and maximum scale values
    if (((m_minY - deltaYPhys) >= minYdefault) && ((m_maxY - deltaYPhys) <= maxYdefault))
    {
        m_minY = minYcurrent - deltaYPhys;
        m_maxY = maxYcurrent - deltaYPhys;
    }
    else
    {
    }
}

// Method to find the new horizontal and vertical scales depending on the magnification level
void Plot::findZoomPhysBoundaries()
{
    double xPhysCenter;
    double yPhysCenter;
    double minXzoom;
    double maxXzoom;
    double minYzoom;
    double maxYzoom;


    qDebug() << "Finding zoom boundaries.";

    if (!m_isHorizontalPinch)
    {
        yPhysCenter = getPhysY(m_touchCenterY);

        minYzoom = yPhysCenter - (yPhysCenter - minYcurrent) / (m_scale);
        if (minYzoom < minYdefault) { m_minY = minYdefault; }
        else { m_minY = minYzoom; }

        maxYzoom = yPhysCenter + (maxYcurrent - yPhysCenter) / (m_scale);
        if (maxYzoom > maxYdefault) { m_maxY = maxYdefault; }
        else { m_maxY = maxYzoom; }
    }
    else
    {
        // Need to find the physical coordinates coresponding to the touch center (X,Y) points
        xPhysCenter = getPhysX(m_touchCenterX);

        // Calculating the new X-axis scale range
        minXzoom = xPhysCenter - (xPhysCenter - minXcurrent) / m_scale;
        if (minXzoom < minXdefault) { m_minX = minXdefault; }
                               else { m_minX = minXzoom; }
        maxXzoom = xPhysCenter + (maxXcurrent - xPhysCenter) / m_scale;
        if (maxXzoom > maxXdefault) { m_maxX = maxXdefault; }
                               else { m_maxX = maxXzoom; }
    }

    // Calculating the new Y-axis scale range (it is better to only scale the horizontal axis) or scale the vertical and horizontal depending on the finger placement
    //    minYzoom = yPhysCenter - (yPhysCenter - minYcurrent) / m_scale;
    //    if (minYzoom < minYdefault) { m_minY = minYdefault; }
    //                           else { m_minY = minYzoom; }
    //    maxYzoom = yPhysCenter + (maxYcurrent - yPhysCenter) / m_scale;
    //    if (maxYzoom > maxYdefault) { m_maxY = maxYdefault; }
    //                           else { m_maxY = maxYzoom; }
}

// Invokable method used to update plot
void Plot::updatePlot()
{
    // This update includes the information string displayed above the plot
    appendTopString = false;
    this->update();
}
void Plot::addTopString()
{
    // This update includes the information string displayed above the plot
    appendTopString = true;
    this->update();
}

// Method to return a plot in progress variable value
bool Plot::plotInProgress() const { return this->m_plotInProgress; }

// Paint event to draw the spectra etc.
void Plot::paint(QPainter *painter)
{
    QFont font;  // The font to use for plotting

    if (m_plotInProgress == false)
    {
        // Setting a variable indicating tha the plot update is in progress
        m_plotInProgress = true;

        // Painting the spectra to be displayed in a QML component
        const qreal halfPenWidth = qMax(painter->pen().width() / 2.0, 1.0);
        this->plotRect = boundingRect();
        painter->fillRect(plotRect, Qt::white);
        this->plotRect.setX(this->plotRect.x() + m_xMinPadding);
        this->plotRect.setY(this->plotRect.y() + m_yMinPadding);
        this->plotRect.setWidth(this->plotRect.width() - m_xMaxPadding);
        this->plotRect.setHeight(this->plotRect.height() - m_yMaxPadding);
        this->plotRect.adjust(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth);
        painter->setPen(Qt::black);

        // Appending the top information to the plot
        if (this->appendTopString)
        {
            painter->setPen(Qt::black);
            font = painter->font();
            font.setPointSize(m_fontPointSize);
            painter->setFont(font);
            painter->drawText(this->xMinPadding(), (painter->fontMetrics().boundingRect(m_yAxisTitle).height() + this->yMinPadding()) * 0.5, m_topString);
            appendTopString = false;
        }

        // Drawing axes
        drawAxes(painter);

        // Drawing spectrum
        if (m_plotInverted == true)
        {
            // Ploting inverted spectrum
            drawSpectrumInverted(painter);
        }
        else
        {
            // Ploting direct spectrum
            drawSpectrumDirect(painter);
        }

        // Indicating that plotting has completed
        m_plotInProgress = false;
        emit plotInProgressChanged();
    }
}

// Method to draw axes and other components (Maybe import QWT), a curve plot maybe would do (need to check and see what it looks like)
void Plot::drawAxes(QPainter *painter)
{
    int i;  // Loop variable
    int j;  // Loop variable
    QFont font;   // The font to use for plotting
    QLocale loc;  // Locale object
    QString str;  // String for number string representation
    double auxDbl;  // Auxiliary double value
    int x;  // x - coordinate
    int y;  // y - coordinate
    int xMinor;   // x coordinate of a minor tick along the horizontal axis
    int yMinor;   // y coordinate of a minor tick along the vertical axis
    QPen dashedLinePen;  // Pen to draw lines
    QPen linePen;   // Pen to draw lines
    double auxDbl2; // Second auxiliary double variable
    int power;      // Value to raise a number to a power of


    // Setting the style of the line to be used for the grid
    dashedLinePen.setStyle(Qt::DotLine);
    dashedLinePen.setColor(Qt::black);
    linePen.setStyle(Qt::SolidLine);
    linePen.setColor(Qt::black);
    painter->setPen(linePen);

    // Adjusting the font size
    font = painter->font();
    font.setPointSize(m_fontPointSize);
    painter->setFont(font);

    if (plotInverted())
    {
        // Drawing the surrounding rectangle
        painter->drawRect(this->plotRect);

        // Plotting the vertical axis title
        painter->rotate(-90);
        painter->drawText(-plotRect.bottom() + 0.5 * (plotRect.height() - painter->fontMetrics().boundingRect(m_yAxisTitle).width()), painter->fontMetrics().boundingRect(m_yAxisTitle).height() * 0.8, m_yAxisTitle);
        painter->rotate(90);

        // Plotting the horizontal axis title
        painter->drawText(plotRect.x() + 0.5 * (plotRect.width() - painter->fontMetrics().boundingRect(m_xAxisTitle).width()), boundingRect().height() - painter->fontMetrics().boundingRect(m_xAxisTitle).height() * 0.2, m_xAxisTitle);

        // Plotting the ticks and numbers along the horizontal axis
        auxDbl = m_minX;
        x = plotRect.left();
        for (i = 0; i < m_numMajorXTicks; i++)
        {
            x = plotRect.left() + i * plotRect.width() / (m_numMajorXTicks - 1);
            auxDbl = ((x - plotRect.left()) / plotRect.width()) * (m_maxY - m_minY) + m_minY;
            auxDbl = nicenum(auxDbl, false);
            str = QString::number(auxDbl);
            if ((i == 0) || (i == (m_numMajorXTicks - 1) ))
            {
                painter->drawText(x - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 1.3, str);
            }
            else
            {
                painter->drawText(x - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 1.3, str);
            }
            painter->drawLine(x, plotRect.bottom(), x, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 0.5);
            painter->setPen(dashedLinePen);
            painter->drawLine(x, plotRect.bottom(), x, plotRect.top());
            painter->setPen(linePen);
            if (i < (m_numMajorXTicks - 1))
            {
                for (j = 0; j < m_numMinorXTicks; j++)
                {
                    xMinor = x + j * plotRect.width() / (m_numMinorXTicks * (m_numMajorXTicks - 1));
                    painter->drawLine(xMinor, plotRect.bottom(), xMinor, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 0.3);
                    painter->setPen(dashedLinePen);
                    painter->drawLine(xMinor, plotRect.bottom(), xMinor, plotRect.top());
                    painter->setPen(linePen);
                }
            }
        }

        // Plotting the ticks and numbers along the vertical axis
        auxDbl = m_minY;
        y = plotRect.bottom();
        for (i = 0; i < m_numMajorYTicks; i++)
        {
            y = plotRect.bottom() - i * plotRect.height() / (m_numMajorYTicks - 1);
            auxDbl = ((plotRect.bottom() - y) / plotRect.height()) * (m_maxX - m_minX) + m_minX;
            auxDbl = round(auxDbl);
            str = QString::number(auxDbl);
            painter->rotate(-90);
            painter->drawText(- y - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.8, str);
            painter->rotate(90);
            painter->drawLine(plotRect.left(), y, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.5, y);
            painter->setPen(dashedLinePen);
            painter->drawLine(plotRect.left(), y, plotRect.right(), y);
            painter->setPen(linePen);
            if (i < (m_numMajorYTicks - 1))
            {
                for (j = 0; j < m_numMinorYTicks; j++)
                {
                    yMinor = y - j * plotRect.height() / (m_numMinorYTicks * (m_numMajorYTicks - 1));
                    painter->drawLine(plotRect.left(), yMinor, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.3, yMinor);
                    painter->setPen(dashedLinePen);
                    painter->drawLine(plotRect.left(), yMinor, plotRect.right(), yMinor);
                    painter->setPen(linePen);
                }
            }
        }
    }
    else
    {
        // Drawing the surrounding rectangle
        painter->drawRect(this->plotRect);

        // Plotting the vertical axis title
        painter->rotate(-90);
        painter->drawText(- plotRect.bottom() + 0.5 * (plotRect.height() - painter->fontMetrics().boundingRect(m_yAxisTitle).width()), painter->fontMetrics().boundingRect(m_yAxisTitle).height() * 0.8, m_yAxisTitle);
        painter->rotate(90);

        // Plotting the horizontal axis title
        painter->drawText(plotRect.x() + 0.5 * (plotRect.width() - painter->fontMetrics().boundingRect(m_xAxisTitle).width()), boundingRect().height() - painter->fontMetrics().boundingRect(m_xAxisTitle).height() * 0.2, m_xAxisTitle);

        // Plotting the ticks and numbers along the horizontal axis
        auxDbl = m_minX;
        x = plotRect.left();
        for (i = 0; i < m_numMajorXTicks; i++)
        {
            x = plotRect.left() + i * plotRect.width() / (m_numMajorXTicks - 1);

            // auxDbl = ((x - plotRect.left()) / (plotRect.width() - 1)) * (m_maxX - m_minX) + m_minX;
            auxDbl = ((m_maxX - m_minX) / (m_numMajorXTicks - 1)) * i + m_minX;
            // Rounding the value depending on the current resolution
            auxDbl2 = fabs((m_maxX - m_minX) / (plotRect.width() - 1));
            power = (int) (log10(auxDbl2));
            auxDbl = round((auxDbl / pow(10, power)));
            auxDbl = auxDbl * pow(10, power);
            str = loc.toString(auxDbl, 'g', 7);
            if ((i == 0) || (i == (m_numMajorXTicks - 1) ))
            {
                painter->drawText(x - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 1.3, str);
            }
            else
            {
                painter->drawText(x - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 1.3, str);
            }
            painter->drawLine(x, plotRect.bottom(), x, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 0.5);
            if (i < (m_numMajorXTicks - 1))
            {
                for (j = 0; j < m_numMinorXTicks; j++)
                {
                    xMinor = x + j * plotRect.width() / (m_numMinorXTicks * (m_numMajorXTicks - 1));
                    painter->drawLine(xMinor, plotRect.bottom(), xMinor, plotRect.bottom() + painter->fontMetrics().boundingRect(str).height() * 0.3);
                }
            }
        }

        // Plotting the ticks and numbers along the vertical axis
        auxDbl = m_minY;
        y = plotRect.bottom();
        for (i = 0; i < m_numMajorYTicks; i++)
        {
            y = plotRect.bottom() - i * plotRect.height() / (m_numMajorYTicks - 1);
            // auxDbl = ((m_maxY - m_minY) / (plotRect.height() - 1)) * (plotRect.bottom() - y)  + m_minY;
            auxDbl = ((m_maxY - m_minY) / (m_numMajorYTicks - 1)) * i  + m_minY;
            auxDbl2 = fabs((m_maxY - m_minY) / (plotRect.height() - 1));
            power = (int) (log10(auxDbl2));
            auxDbl = round((auxDbl / pow(10, power)));
            auxDbl = auxDbl * pow(10, power);
            str = loc.toString(auxDbl, 'g', 3);
            painter->rotate(-90);
            painter->drawText(- y - painter->fontMetrics().boundingRect(str).width() * 0.5, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.8, str);
            painter->rotate(90);
            painter->drawLine(plotRect.left(), y, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.5, y);
            if (i < (m_numMajorYTicks - 1))
            {
                for (j = 0; j < m_numMinorYTicks; j++)
                {
                    yMinor = y - j * plotRect.height() / (m_numMinorYTicks * (m_numMajorYTicks - 1));
                    painter->drawLine(plotRect.left(), yMinor, plotRect.left() - painter->fontMetrics().boundingRect(str).height() * 0.3, yMinor);
                }
            }
        }
    }
}

// Method to draw plots (Maybe import QWT), a curve plot maybe would do (need to check and see what it looks like)
void Plot::drawPlot() { update(); }
void Plot::drawSpectrumInverted(QPainter *painter)
{
    int i;   // Loop variable
    int j;   // Loop variable
    int x1;  // X coordinate 1
    int x2;  // X coordinate 2
    int y1;  // Y coordinate 1
    int y2;  // Y coordinate 2
    QPen pen;  // Pen to draw spectra


    for (i = 0; i < spectra.count(); i++)
    {
        if (spectra[i].getX()->count() >= 3)
        {
            pen.setWidth(this->m_lineThickness);
            pen.setColor(QColor(colors[i]));
            painter->setPen(pen);
            x1 = plotRect.bottom() - (spectra[i].getXAt(0)- m_minX) * plotRect.height() / (m_maxX - m_minX);
            y1 = plotRect.left() + (spectra[i].getYAt(0)- m_minY) * plotRect.width() / (m_maxY - m_minY);
            if (x1 > plotRect.bottom())  {x1 = plotRect.bottom(); }
            if (x1 < plotRect.top()) { x1 = plotRect.top(); }
            if (y1 > plotRect.right()) {y1 = plotRect.right(); }
            if (y1 < plotRect.left()) {y1 = plotRect.left(); }
            for (j = 1; j < m_spectrum.getY()->count(); j++)
            {
                x2 = plotRect.bottom() - (spectra[i].getXAt(j) - m_minY) * plotRect.height() / (m_maxX - m_minX);
                y2 = plotRect.left() + (spectra[i].getYAt(j) - m_minY) * plotRect.width() / (m_maxY - m_minY);
                if (x1 > plotRect.bottom())  {x1 = plotRect.bottom(); }
                if (x1 < plotRect.top()) { x1 = plotRect.top(); }
                if (y1 > plotRect.right()) {y1 = plotRect.right(); }
                if (y1 < plotRect.left()) {y1 = plotRect.left(); }
                painter->drawLine(y1, x1, y2, x2);
                x1 = x2;
                y1 = y2;
            }
        }
        else
        {
            for (i = 0; i < spectra.count(); i++)
            {
                pen.setWidth(this->m_lineThickness);
                pen.setColor(QColor(colors[i]));
                painter->setPen(pen);
                x1 = plotRect.bottom() - (m_wnGrid[0] - m_minX) * plotRect.height() / (m_maxX - m_minX);
                y1 = plotRect.left() + (spectra[i].getYAt(0) - m_minY) * plotRect.width() / (m_maxY - m_minY);
                if (x1 > plotRect.bottom())  {x1 = plotRect.bottom(); }
                if (x1 < plotRect.top()) { x1 = plotRect.top(); }
                if (y1 > plotRect.right()) {y1 = plotRect.right(); }
                if (y1 < plotRect.left()) {y1 = plotRect.left(); }
                for (j = 1; j < m_spectrum.getY()->count(); j++)
                {
                    x2 = plotRect.bottom() - (m_wnGrid[j] - m_minX) * plotRect.height() / (m_maxX - m_minX);
                    y2 = plotRect.left() + (spectra[i].getYAt(j) - m_minY) * plotRect.width() / (m_maxY - m_minY);
                    if (x1 > plotRect.bottom())  {x1 = plotRect.bottom(); }
                    if (x1 < plotRect.top()) { x1 = plotRect.top(); }
                    if (y1 > plotRect.right()) {y1 = plotRect.right(); }
                    if (y1 < plotRect.left()) {y1 = plotRect.left(); }
                    painter->drawLine(y1, x1, y2, x2);
                    x1 = x2;
                    y1 = y2;
                }
            }
        }
    }
}

// Drawing direct plot
void Plot::drawSpectrumDirect(QPainter *painter)
{
    int i;   // Loop variable
    int j;   // Loop variable
    int x1;  // X coordinate 1
    int x2;  // X coordinate 2
    int y1;  // Y coordinate 1
    int y2;  // Y coordinate 2
    QPen pen;  // Pen to use for ploting the spectra
    double yAxisONePixelDistance;
    QVector<int> xAxisPixelValues;  // Calculated X-axis pixel values corresponding to the wavelength or wavenumber grid
    int jStart;   // Starting index to carry out spectrum plotting from along the Horizontal axis
    int jEnd;     // Ending index to carry out plotting from along the horizontal axis
    int jCenter;  // Central wavenumber index in the spectra to plot
    double term;  // Multiplication term for the vertical axis
    bool yCondition;  // Condition along the Y-axis when spectrum plotting needs to be done
    QPen dashedLinePen;  // Pen to draw lines
    int specPixelAmplitude;   // Spectrum aplitude in pixels (along the vertical axis)
    int pixelAplitudeThresh;  // Threshold pixel altitude value, the spectra with pixel amplitude below this value will not be plotted
    double auxDouble;  // Auxiliary double variable


    // Finding the one pixel distances for the horizontal and vertical axes
    yAxisONePixelDistance = getOnePixelYResolution() * 0.5;

    // Carrying out plotting
    i = 0;  // This is not the best way to find out about the spectra format (only the first one is tested but will work for this initial implementation)
    if (spectra[i].getX()->count() >= 3)  // X-axis values provided
    {
        for (i = 0; i < spectra.count(); i++)
        {
            // Finding the starting and ending indexes in the wavenumber array
            // This was updated without checking if this works, needs to be verified if ever in use
            jStart = 0;
            while ((spectra[i].getXAt(jStart) <= m_minX) && (jStart < spectra[i].getX()->count())) { jStart++; }
            jEnd = jStart;
            while ((spectra[i].getXAt(jEnd) <= m_maxX) && (jEnd < spectra[i].getX()->count())) { jEnd++; }

            pen.setWidth(this->m_lineThickness);
            pen.setColor(QColor(colors[i]));
            painter->setPen(pen);
            x1 = plotRect.left() + (spectra[i].getXAt(0) - m_minX) * plotRect.width() / (m_maxX - m_minX);
            y1 = plotRect.bottom() - (spectra[i].getYAt(0) - m_minY) * plotRect.height() / (m_maxY - m_minY);
            if (x1 > plotRect.right())  {x1 = plotRect.right(); }
            if (x1 < plotRect.left()) { x1 = plotRect.left(); }
            if (y1 > plotRect.bottom()) {y1 = plotRect.bottom(); }
            if (y1 < plotRect.top()) {y1 = plotRect.top(); }
            for (j = jStart; j < jEnd; j++)
            {
                x2 = plotRect.left() + (spectra[i].getXAt(j) - m_minX) * plotRect.width() / (m_maxX - m_minX);
                y2 = plotRect.bottom() - (spectra[i].getYAt(j) - m_minY) * plotRect.height() / (m_maxY - m_minY);
                if (x2 > plotRect.right())  {x2 = plotRect.right(); }
                if (x2 < plotRect.left()) { x2 = plotRect.left(); }
                if (y2 > plotRect.bottom()) {y2 = plotRect.bottom(); }
                if (y2 < plotRect.top()) {y2 = plotRect.top(); }
                painter->drawLine(x1, y1, x2, y2);
                x1 = x2;
                y1 = y2;
            }
        }
    }
    else
    {
        // Finding the starting and ending indexes in the wavenumber array
        // For individual spectral lines this would have to be modified
        if (m_wnGrid[0] < m_wnGrid[m_wnGrid.count() - 1])  // Wavenumber and Hz units, probably need to use divide and conqure or a similar algorithm
        {
            jStart = 0;
            while ((m_wnGrid[jStart] <= m_minX) && (jStart < (m_wnGrid.count() - 1))) { jStart++; }
            jEnd = m_wnGrid.count() - 1;
            while ((m_wnGrid[jEnd] >= m_maxX) && (jEnd > 0)) { jEnd--; }
        }
        else  // Micron and nanometers units (does not work, fix this), probably need to use divide and conquer or a similar algorithm
        {
            jStart = 0;
            while ((m_wnGrid[jStart] >= m_maxX) && (jStart < (m_wnGrid.count() - 1))) { jStart++; }
            jEnd = m_wnGrid.count() - 1;
            while ((m_wnGrid[jEnd] <= m_minX) && (jEnd > 0)) { jEnd--; }
        }

        // Calculating the X values along the X axis (in pixels), done this way because they are the same for the common grid
        // For individual lines this would have to be modified
        xAxisPixelValues.resize(m_wnGrid.count());
        for (i = 0; i < m_wnGrid.count(); i++)
        {
            xAxisPixelValues[i] = plotRect.left() + (m_wnGrid[i] - m_minX) * (plotRect.width() - 1) / (m_maxX - m_minX);
        }

        // Multiplication term
        term = (plotRect.height() - 1) / (m_maxY - m_minY);

        // Setting the pen width
        pen.setWidth(this->m_lineThickness);

        // Plotting the spectra
        for (i = 0; i < spectra.count(); i++)
        {
            pen.setColor(QColor(colors[i]));
            painter->setPen(pen);
            y1 = plotRect.bottom() - (spectra[i].getYAt(jStart) - m_minY) * term;
            for (j = jStart; j < jEnd; j++)
            {
                yCondition = fabs(spectra[i].getYAt(j)- spectra[i].getYAt(j + 1)) > yAxisONePixelDistance;
                if ((xAxisPixelValues[j] != xAxisPixelValues[j + 1]) || yCondition)
                {
                    y2 = plotRect.bottom() - (spectra[i].getYAt(j + 1) - m_minY) * term;
                    if (y2 > plotRect.bottom()) {y2 = plotRect.bottom(); }
                    if (y2 < plotRect.top()) {y2 = plotRect.top(); }
                    painter->drawLine(xAxisPixelValues[j], y1, xAxisPixelValues[j + 1], y2);
                    y1 = y2;
                }
            }
        }
    }

    // The code below is used to plot the rulers
    // Finding the central wavenumber point in the spectrum array
    if (m_enableRulers)
    {
        if ((m_minX > minXdefault) || (m_maxX < maxXdefault))
        {
            // Finding the central point along the wavenumber axis
            auxDouble = (double)(jEnd + jStart) * 0.5;
            jCenter = (int)floor(auxDouble);

            // Plotting the tracking rulers
            dashedLinePen.setStyle(Qt::DotLine);
            dashedLinePen.setColor(Qt::black);
            painter->setPen(dashedLinePen);            
            // Can't just use jCenter instead of x1 here because the ruler does not match the spectrum position then (there is a slight shift to the left along the X-axis)
            if (spectra.count() > 0)
            {
                if (spectra[0].getX()->count() >= 3) { // X-axis values provided
                    x1 = plotRect.left() + (spectra[0].getXAt(jCenter) - m_minX) * plotRect.width() / (m_maxX - m_minX);
                }
                else {
                    x1 = xAxisPixelValues[jCenter];
                    //x1 = plotRect.left() + (m_wnGrid[jCenter] - m_minX) * plotRect.width() / (m_maxX - m_minX);
                }
            }
            qDebug() << x1;
            painter->drawLine(x1, plotRect.bottom(), x1, plotRect.top());
            pixelAplitudeThresh = (plotRect.height() * 0.5) / (m_numMinorYTicks * m_numMajorYTicks);
            for (i = 0; i < spectra.count(); i++)
            {
                dashedLinePen.setStyle(Qt::DotLine);
                dashedLinePen.setColor(QColor(colors[i]));
                painter->setPen(dashedLinePen);
                specPixelAmplitude = (spectra[i].getYAt(jCenter) - m_minY) * plotRect.height() / (m_maxY - m_minY);
                if (specPixelAmplitude > pixelAplitudeThresh)  // Anything above 0.5 of the minimum division along the vertical axis will get plotted
                {
                    y1 = plotRect.bottom() - specPixelAmplitude;
                    if (y1 > plotRect.top()) { painter->drawLine(plotRect.left(), y1, plotRect.right(), y1); }
                }
            }
        }
    }
}

// Methods to get the coordinates from the physical values
int Plot::getXFromPhys(double xPhys)
{
    if (plotInverted())
    {
        // return
    }

    // This is the case of direct units
    return plotRect.left() + (xPhys - m_minX) * plotRect.width() / (m_maxX - m_minX);
}
int Plot::getYFromPhys(double yPhys)
{
    if (plotInverted())
    {
        // return
    }

    // This is the case of direct units
    return plotRect.bottom() - (yPhys - m_minY) * plotRect.height() / (m_maxY - m_minY);
}

// Methods to get the Physical values from the coordinates
double Plot::getPhysX(int x)
{
    if (plotInverted())
    {
        // return
    }

    // This is the case of direct units
    return (x - plotRect.left()) * (m_maxX - m_minX) / plotRect.width() + m_minX;
}
double Plot::getPhysY(int y)
{
    if (plotInverted())
    {
        // return
    }

    // This is the case of direct units
    return (plotRect.bottom() - y) * (m_maxY - m_minY) / plotRect.height() + m_minY;
}

// Method to get a rounded value to be displayed
double Plot::nicenum(double x, bool round)
{
    // Nice numbers algorithm is converted from:
    /*
     * Nice Numbers for Graph Labels
     * by Paul Heckbert
     * from "Graphics Gems", Academic Press, 1990
     *
     */

    /*
     * nicenum: find a "nice" number approximately equal to x.
     * Round the number if round = true, take ceiling if round = false
     *
     */

    int expv;	// Exponent of x
    double f;	// Fractional part of x
    double nf;	// Nice, rounded fraction

    expv = (int)floor(log10(x));
    f = x / pow(10.0, (double) expv);		// between 1 and 10
    if (round)
        if (f < 1.5) nf = 1.;
        else if (f < 3.) nf = 2.;
        else if (f < 7.) nf = 5.;
        else nf = 10.;
    else
        nf = 0.05;
        while (f > nf)
        {
            nf = nf + 0.05;
        }

    return nf * pow(10.0, (double) expv);
}

// Method to get X-axis resolution  equivalent to one pixel
double Plot::getOnePixelXResolution()
{
    return (fabs(m_maxX - m_minX) / abs((int) (getXFromPhys(m_maxX) - getXFromPhys(m_minX))));
}
// Method to get the Y-axis resolution equivalent to one pixel
double Plot::getOnePixelYResolution()
{
    return (fabs(m_maxY - m_minY) / abs((int) (getYFromPhys(m_maxY) - getYFromPhys(m_minY))));
}
