// 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.



// This is a "QAbstractItemModel" class used to display and change data in the QML molecules list view
#include "mollistviewmodel.h"
#include <QMessageBox>

MolListView::MolListView(const QString &index, const bool &checked, const QString &molName, const QString &pressValue, const QString &color)
     : m_index(index), m_checked(checked), m_molName(molName), m_pressValue(pressValue), m_color(color)
{
}

QString MolListView::index() const
{
    return m_index;
}

bool MolListView::checked() const
{ return m_checked; }
void MolListView::setChecked(const bool &checked)
{
    if (checked != m_checked)
    {
        m_checked = checked;
    }
}

QString MolListView::molName() const
{
    return m_molName;
}

QString MolListView::pressValue() const
{ return m_pressValue; }
void MolListView::setPressValue(const QString &pressValue)
{
    if (pressValue != m_pressValue)
    {
        m_pressValue = pressValue;
    }
}

QString MolListView::color() const
{ return m_color; }
void MolListView::setColor(const QString &color)
{
    if (color != m_color)
    {
        m_color = color;
    }
}

MolListViewModel::MolListViewModel(QObject *parent, BytranCore *inCore) : QAbstractListModel(parent)
{
    int i;  // Loop variable
    QString text;
    bool checked;
    QString molName;
    QString concStr = "0";
    QString color;
    double partPressMbar;  // Partial pressure, mbar
    double conc;  // Concentration value, %


    // Passing in the core module
    core = inCore;

    // So that commas are not inserted into numbers
    loc.setNumberOptions(QLocale::OmitGroupSeparator);

    // Setting the "Select All" checkbox and the displayed partial pressure units
    // Select All / Deselect All
    // selectAll(true);

    // Concentration units selection
    m_units = core->getConcPressUnitsConverter()->getCombinedUnits();

    // Populating the data list with the molecule elements initialized form the internally saved values or from an external file
    for (i = 0; i < core->getParams()->getMol()->count(); i++)
    {
        // Displaying the molecular infromation for each molecular record (not all parameters are displayed) as saved into the internal data structure
        text = QString::number(i + 1);
        if (text.count() < 2) { text = "  " + text; }
        checked = core->getParams()->getMolAt(i)->getSelected();
        molName = core->getParams()->getMolAt(i)->getChemForm();
        color = core->getParams()->getMolAt(i)->getColor();
        partPressMbar = core->getParams()->getHorizParams()->getPartPress(i);
        conc = core->getConcPressUnitsConverter()->fromDefaultToCurrentConcUnits(partPressMbar, core->getParams()->getHorizParams()->getTotalPress());
        concStr = loc.toString(conc, 'g', 5);  // QString::number(conc);
        this->addElement(MolListView(text, checked, molName, concStr, color));
    }

    // Connecting a signal to update the H2O concentration value if the sensor is used, also to update values if they have been modified through the remote connection
    connect(this->core->getSensors(), SIGNAL(humidityUpdated()), this, SLOT(updateH2OValue()));
    connect(this->core, SIGNAL(valuesUpdated()), this, SLOT(updateAllPartPressValues()));
    connect(this->core, SIGNAL(valuesUpdated()), this, SLOT(updateAllCheckedStates()));
    connect(this->core, SIGNAL(valuesUpdated()), this, SIGNAL(calcNotSelectedMolsChanged()));
    connect(this, SIGNAL(updateSpectra(bool,bool)), this->core, SIGNAL(updateSpectra(bool,bool)));
}

int MolListViewModel::units() const
{ return m_units; }
void MolListViewModel::setUnits(const int &units)
{
    QString concStr = "0";
    double partPressMbar;  // Partial pressure, mbar
    double conc;   // Concentration value, %
    int i;  // Loop variable


    if (units != m_units)
    {
        m_units = units;
        core->getConcPressUnitsConverter()->setCombinedUnits(m_units);

        // Updating the concentration values to be displayed
        for (i = 0; i < m_molListView.count(); i++)
        {
            partPressMbar = core->getParams()->getHorizParams()->getPartPress(i);
            conc = core->getConcPressUnitsConverter()->fromDefaultToCurrentConcUnits(partPressMbar, core->getParams()->getHorizParams()->getTotalPress());
            concStr = loc.toString(conc, 'g', 5);  // QString::number(conc);
            m_molListView[i].setPressValue(concStr);
        }

        emit unitsChanged();

        // Emit data changed from here to update the list when this control is modified
        emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(m_molListView.count() - 1, 0, QModelIndex()));
    }    
}

// Updating the H2O values if they are taken from the sensor
void MolListViewModel::updateH2OValue()
{
    QString concStr = "0";
    double partPressMbar;  // Partial pressure, mbar
    double conc;   // Concentration value, %


    // Updating the concentration values to be displayed for H2O
    partPressMbar = core->getParams()->getHorizParams()->getPartPress(0);
    conc = core->getConcPressUnitsConverter()->fromDefaultToCurrentConcUnits(partPressMbar, core->getParams()->getHorizParams()->getTotalPress());
    concStr = loc.toString(conc, 'g', 5);  // QString::number(conc);
    m_molListView[0].setPressValue(concStr);

    // Emit data changed from here to update the list when this control is modified
    emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(0, 0, QModelIndex()));
}

QString MolListViewModel::selectedColor() const { return this->m_selectedColor; }
void MolListViewModel::setSelectedColor(const QString &selectedColor)
{
    if (selectedColor != m_selectedColor)
    {
        m_selectedColor = selectedColor;
        emit selectedColorChanged();
    }
}

int MolListViewModel::activeIndex() const { return this->m_activeIndex; }
void MolListViewModel::setActiveIndex(const int activeIndex)
{
    if (activeIndex != m_activeIndex)
    {
        m_activeIndex = activeIndex;
        emit activeIndexChanged();
    }
}

// Method to update the partial pressures displayed
void MolListViewModel::updatePartPress() { updateAllPartPressValues(); }
void MolListViewModel::updateAllPartPressValues()
{
    QString concStr = "0";
    double partPressMbar;  // Partial pressure, mbar
    double conc;   // Concentration value, %
    bool checked;  // Variable indicating the checked state for a molecule
    int i;   // Loop variable

    qDebug() << "Updating the concentrations in the molecules list.";

    // Updating the concentration values to be displayed
    for (i = 0; i < m_molListView.count(); i++)
    {
        partPressMbar = core->getParams()->getHorizParams()->getPartPress(i);
        qDebug() << partPressMbar;

        checked = core->getParams()->getMolAt(i)->getSelected();
        conc = core->getConcPressUnitsConverter()->fromDefaultToCurrentConcUnits(partPressMbar, core->getParams()->getHorizParams()->getTotalPress());
        concStr = loc.toString(conc, 'g', 5);  // QString::number(conc);
        m_molListView[i].setChecked(checked);
        m_molListView[i].setPressValue(concStr);
    }

    // Emit data changed from here to update the list when this control is modified
    emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(m_molListView.count() - 1, 0, QModelIndex()));
}
// Method to update all checked states
void MolListViewModel::updateAllCheckedStates()
{
    bool checked;  // Variable indicating the checked state for a molecule
    int i;   // Loop variable


    // Updating the concentration values to be displayed
    for (i = 0; i < m_molListView.count(); i++)
    {
        checked = core->getParams()->getMolAt(i)->getSelected();
        m_molListView[i].setChecked(checked);
    }

    // Emit data changed from here to update the list when this control is modified
    emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(m_molListView.count() - 1, 0, QModelIndex()));

    // Updating the plotted spectra
    emit updateSpectra(false, true);
}

// Method to update the colors (used to update the selected color)
void MolListViewModel::updateActiveColor()
{
    core->getParams()->getMolAt(this->m_activeIndex - 1)->setColor(this->selectedColor());
    // Emit data changed from here to update the list when this control is modified
    m_molListView[this->m_activeIndex - 1].setColor(this->m_selectedColor);
    emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(m_molListView.count() - 1, 0, QModelIndex()));
}

// Method to update the select all and deselect all checked states
void MolListViewModel::selectAll(bool selected)
{
    int i;  // Loop variable


    // Currently the program does not monitor the selection of all moleulces manually (if all are unchecked
    // or checked manually) then the select/deselect all will not update, should be taken into account later

    // Updating the concentration values to be displayed
    m_allSelected = selected;
    for (i = 0; i < m_molListView.count(); i++)
    {
        core->getParams()->getMolAt(i)->setSelected(selected);
        m_molListView[i].setChecked(selected);
    }
    emit dataChanged(MolListViewModel::index(0, 0, QModelIndex()), MolListViewModel::index(m_molListView.count() - 1, 0, QModelIndex()));
}

bool MolListViewModel::allSelected() const { return this->m_allSelected; }
void MolListViewModel::setAllSelected(const bool allSelected)
{
    if (allSelected != m_allSelected)
    {
        m_allSelected = allSelected;
        emit allSelectedChanged();
    }
}


bool MolListViewModel::calcNotSelectedMols() const { return this->core->getParams()->getCalcNotSelectedMols(); }
void MolListViewModel::setCalcNotSelectedMols(const bool calcNotSelectedMols)
{
    if (calcNotSelectedMols != m_calcNotSelectedMols)
    {
        m_calcNotSelectedMols = calcNotSelectedMols;
        core->getParams()->setCalcNotSelectedMols(m_calcNotSelectedMols);
        emit calcNotSelectedMolsChanged();
    }
}

void MolListViewModel::addElement(const MolListView &molListView)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    m_molListView << molListView;
    endInsertRows();
}

int MolListViewModel::rowCount(const QModelIndex & parent) const {
    Q_UNUSED(parent);
    return m_molListView.count();
}

QVariant MolListViewModel::data(const QModelIndex & index, int role) const {
    if ((index.row() < 0) || (index.row() >= m_molListView.count()))
        return QVariant();

    const MolListView &molListView = m_molListView[index.row()];
    if (role == indexRole)
        return molListView.index();
    else if (role == checkedRole)
        return molListView.checked();
    else if (role == molNameRole)
        return molListView.molName();
    else if (role == pressValueRole)
        return molListView.pressValue();
    else if (role == colorRole)
        return molListView.color();
    return QVariant();
}

// Method to set the data when the values in the model are modified
bool MolListViewModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
    double partPressMbar;  // Partial pressure in millibar units

    switch (role)
    {
        case pressValueRole:
            m_molListView[index.row()].setPressValue(value.toString());
            partPressMbar = core->getConcPressUnitsConverter()->fromCurrentToDefaultConcUnits(loc.toDouble(value.toString()), core->getParams()->getHorizParams()->getTotalPress());
            core->getParams()->getHorizParams()->setPartPress(index.row(), partPressMbar);
            break;
        case checkedRole:
            m_molListView[index.row()].setChecked(value.toBool());
            core->getParams()->getMolAt(index.row())->setSelected(value.toBool());
            break;
        case colorRole:
            m_molListView[index.row()].setColor(value.toString());
            break;
    }
    emit dataChanged(index, index);
    return true;
}

//![0]
QHash<int, QByteArray> MolListViewModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[indexRole] = "index";
    roles[checkedRole] = "checked";
    roles[molNameRole] = "molName";
    roles[pressValueRole] = "pressValue";
    roles[colorRole] = "color";
    return roles;
}
//![0]

// Need to update the model and synch data etc.
