/*
  Copyright © 2018 Hasan Yavuz Özderya
  This file is part of serialplot.
  serialplot 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 3 of the License, or
  (at your option) any later version.
  serialplot 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 serialplot.  If not, see .
*/
#include "channelinfomodel.h"
#include "setting_defines.h"
#define NUMOF_COLORS  (32)
const QColor colors[NUMOF_COLORS] =
{
    QColor("#ff0056"),
    QColor("#7e2dd2"),
    QColor("#00ae7e"),
    QColor("#fe8900"),
    QColor("#ff937e"),
    QColor("#6a826c"),
    QColor("#ff029d"),
    QColor("#00b917"),
    QColor("#7a4782"),
    QColor("#85a900"),
    QColor("#a42400"),
    QColor("#683d3b"),
    QColor("#bdc6ff"),
    QColor("#263400"),
    QColor("#bdd393"),
    QColor("#d5ff00"),
    QColor("#9e008e"),
    QColor("#001544"),
    QColor("#c28c9f"),
    QColor("#ff74a3"),
    QColor("#01d0ff"),
    QColor("#004754"),
    QColor("#e56ffe"),
    QColor("#788231"),
    QColor("#0e4ca1"),
    QColor("#91d0cb"),
    QColor("#be9970"),
    QColor("#968ae8"),
    QColor("#bb8800"),
    QColor("#43002c"),
    QColor("#deff74"),
    QColor("#00ffc6")
};
ChannelInfoModel::ChannelInfoModel(unsigned numberOfChannels, QObject* parent) :
    QAbstractTableModel(parent)
{
    _numOfChannels = 0;
    setNumOfChannels(numberOfChannels);
}
ChannelInfoModel::ChannelInfoModel(const ChannelInfoModel& other) :
    ChannelInfoModel(other.rowCount(), other.parent())
{
    // TODO: why not set (copy) info list directly instead?
    for (int i = 0; i < other.rowCount(); i++)
    {
        setData(index(i, COLUMN_NAME),
                other.data(other.index(i, COLUMN_NAME), Qt::EditRole),
                Qt::EditRole);
        setData(index(i, COLUMN_NAME),
                other.data(other.index(i, COLUMN_NAME), Qt::ForegroundRole),
                Qt::ForegroundRole);
        setData(index(i, COLUMN_VISIBILITY),
                other.data(other.index(i, COLUMN_VISIBILITY), Qt::CheckStateRole),
                Qt::CheckStateRole);
        setData(index(i, COLUMN_GAIN),
                other.data(other.index(i, COLUMN_GAIN), Qt::CheckStateRole),
                Qt::CheckStateRole);
        setData(index(i, COLUMN_GAIN),
                other.data(other.index(i, COLUMN_GAIN), Qt::EditRole),
                Qt::EditRole);
        setData(index(i, COLUMN_OFFSET),
                other.data(other.index(i, COLUMN_OFFSET), Qt::CheckStateRole),
                Qt::CheckStateRole);
        setData(index(i, COLUMN_OFFSET),
                other.data(other.index(i, COLUMN_OFFSET), Qt::EditRole),
                Qt::EditRole);
    }
}
ChannelInfoModel::ChannelInfoModel(const QStringList& channelNames) :
    ChannelInfoModel(channelNames.length(), NULL)
{
    for (int i = 0; i < channelNames.length(); i++)
    {
        setData(index(i, COLUMN_NAME), channelNames[i], Qt::EditRole);
    }
}
ChannelInfoModel::ChannelInfo::ChannelInfo(unsigned index)
{
    name = tr("Channel %1").arg(index + 1);
    visibility = true;
    color = colors[index % NUMOF_COLORS];
    gain = 1.0;
    offset = 0.0;
    gainEn = false;
    offsetEn = false;
}
QString ChannelInfoModel::name(unsigned i) const
{
    return infos[i].name;
}
QColor ChannelInfoModel::color(unsigned i) const
{
    return infos[i].color;
}
bool ChannelInfoModel::isVisible(unsigned i) const
{
    return infos[i].visibility;
}
bool ChannelInfoModel::gainEn (unsigned i) const
{
    return infos[i].gainEn;
}
double ChannelInfoModel::gain (unsigned i) const
{
    return infos[i].gain;
}
bool ChannelInfoModel::offsetEn (unsigned i) const
{
    return infos[i].offsetEn;
}
double ChannelInfoModel::offset (unsigned i) const
{
    return infos[i].offset;
}
QStringList ChannelInfoModel::channelNames() const
{
    QStringList r;
    for (unsigned ci = 0; ci < _numOfChannels; ci++)
    {
        r << name(ci);
    }
    return r;
}
int ChannelInfoModel::rowCount(const QModelIndex &parent) const
{
    return _numOfChannels;
}
int ChannelInfoModel::columnCount(const QModelIndex & parent) const
{
    return COLUMN_COUNT;
}
Qt::ItemFlags ChannelInfoModel::flags(const QModelIndex &index) const
{
    if (index.column() == COLUMN_NAME)
    {
        return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
    }
    else if (index.column() == COLUMN_VISIBILITY)
    {
        return Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
    }
    else if (index.column() == COLUMN_GAIN || index.column() == COLUMN_OFFSET)
    {
        return Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
    }
    return Qt::NoItemFlags;
}
QVariant ChannelInfoModel::data(const QModelIndex &index, int role) const
{
    // check index
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
    {
        return QVariant();
    }
    auto &info = infos[index.row()];
    // get color
    if (role == Qt::ForegroundRole)
    {
        return info.color;
    }
    // name
    if (index.column() == COLUMN_NAME)
    {
        if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            return QVariant(info.name);
        }
    } // visibility
    else if (index.column() == COLUMN_VISIBILITY)
    {
        if (role == Qt::CheckStateRole)
        {
            bool visible = info.visibility;
            return visible ? Qt::Checked : Qt::Unchecked;
        }
    } // gain
    else if (index.column() == COLUMN_GAIN)
    {
        if (role == Qt::CheckStateRole)
        {
            return info.gainEn ? Qt::Checked : Qt::Unchecked;
        }
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            return QVariant(info.gain);
        }
    } // offset
    else if (index.column() == COLUMN_OFFSET)
    {
        if (role == Qt::CheckStateRole)
        {
            return info.offsetEn ? Qt::Checked : Qt::Unchecked;
        }
        else if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            return QVariant(info.offset);
        }
    }
    return QVariant();
}
QVariant ChannelInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal)
    {
        if (role == Qt::DisplayRole)
        {
            if (section == COLUMN_NAME)
            {
                return tr("Channel");
            }
            else if (section == COLUMN_VISIBILITY)
            {
                return tr("Visible");
            }
            else if (section == COLUMN_GAIN)
            {
                return tr("Gain");
            }
            else if (section == COLUMN_OFFSET)
            {
                return tr("Offset");
            }
        }
    }
    else                        // vertical
    {
        if (section < (int) _numOfChannels && role == Qt::DisplayRole)
        {
            return QString::number(section + 1);
        }
    }
    return QVariant();
}
bool ChannelInfoModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    // check index
    if (index.row() >= (int) _numOfChannels || index.row() < 0)
    {
        return false;
    }
    auto &info = infos[index.row()];
    // set color
    if (role == Qt::ForegroundRole)
    {
        info.color = value.value();
        emit dataChanged(index, index, QVector({Qt::ForegroundRole}));
        return true;
    }
    // set name
    bool r = false;
    if (index.column() == COLUMN_NAME)
    {
        if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            info.name = value.toString();
            r = true;
        }
    } // set visibility
    else if (index.column() == COLUMN_VISIBILITY)
    {
        if (role == Qt::CheckStateRole)
        {
            bool checked = value.toInt() == Qt::Checked;
            info.visibility = checked;
            r = true;
        }
    }
    else if (index.column() == COLUMN_GAIN)
    {
        if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            info.gain = value.toDouble();
            r = true;
        }
        else if (role == Qt::CheckStateRole)
        {
            bool checked = value.toInt() == Qt::Checked;
            info.gainEn = checked;
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
            r = true;
        }
    }
    else if (index.column() == COLUMN_OFFSET)
    {
        if (role == Qt::DisplayRole || role == Qt::EditRole)
        {
            info.offset = value.toDouble();
            r = true;
        }
        else if (role == Qt::CheckStateRole)
        {
            bool checked = value.toInt() == Qt::Checked;
            info.offsetEn = checked;
            if (_gainOrOffsetEn != checked) updateGainOrOffsetEn();
            r = true;
        }
    }
    if (r)
    {
        emit dataChanged(index, index, QVector({role}));
    }
    return r;
}
void ChannelInfoModel::setNumOfChannels(unsigned number)
{
    if (number == _numOfChannels) return;
    bool isInserting = number > _numOfChannels;
    if (isInserting)
    {
        beginInsertRows(QModelIndex(), _numOfChannels, number-1);
    }
    else
    {
        beginRemoveRows(QModelIndex(), number, _numOfChannels-1);
    }
    // we create channel info but never remove channel info to
    // remember user entered info
    if ((int) number > infos.length())
    {
        for (unsigned ci = infos.length(); ci < number; ci++)
        {
            infos.append(ChannelInfo(ci));
        }
    }
    // make sure newly available channels are visible, we don't
    // remember visibility option intentionally so that user doesn't
    // get confused
    if (number > _numOfChannels)
    {
        for (unsigned ci = _numOfChannels; ci < number; ci++)
        {
            infos[ci].visibility = true;
        }
    }
    _numOfChannels = number;
    updateGainOrOffsetEn();
    if (isInserting)
    {
        endInsertRows();
    }
    else
    {
        endRemoveRows();
    }
}
void ChannelInfoModel::resetInfos()
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        infos[ci] = ChannelInfo(ci);
    }
    endResetModel();
}
// TODO: fix repetitive code, ChannelInfoModel::reset* functions
void ChannelInfoModel::resetNames()
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        // TODO: do not create a full object every time (applies to other reset methods as well)
        infos[ci].name = ChannelInfo(ci).name;
    }
    endResetModel();
}
void ChannelInfoModel::resetColors()
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        infos[ci].color = ChannelInfo(ci).color;
    }
    endResetModel();
}
void ChannelInfoModel::resetVisibility(bool visible)
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        infos[ci].visibility = visible;
    }
    endResetModel();
}
void ChannelInfoModel::resetGains()
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        infos[ci].gain = ChannelInfo(ci).gain;
        infos[ci].gainEn = ChannelInfo(ci).gainEn;
    }
    updateGainOrOffsetEn();
    endResetModel();
}
void ChannelInfoModel::resetOffsets()
{
    beginResetModel();
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        infos[ci].offset = ChannelInfo(ci).offset;
        infos[ci].offsetEn = ChannelInfo(ci).offsetEn;
    }
    updateGainOrOffsetEn();
    endResetModel();
}
bool ChannelInfoModel::gainOrOffsetEn() const
{
    return _gainOrOffsetEn;
}
void ChannelInfoModel::updateGainOrOffsetEn()
{
    _gainOrOffsetEn = false;
    for (unsigned ci = 0; ci < _numOfChannels; ci++)
    {
        auto& info = infos[ci];
        _gainOrOffsetEn |= (info.gainEn || info.offsetEn);
    }
}
void ChannelInfoModel::saveSettings(QSettings* settings) const
{
    settings->beginGroup(SettingGroup_Channels);
    settings->beginWriteArray(SG_Channels_Channel);
    // save all channel information regardless of current number of channels
    for (unsigned ci = 0; (int) ci < infos.length(); ci++)
    {
        settings->setArrayIndex(ci);
        auto& info = infos[ci];
        settings->setValue(SG_Channels_Name, info.name);
        settings->setValue(SG_Channels_Color, info.color);
        settings->setValue(SG_Channels_Visible, info.visibility);
        settings->setValue(SG_Channels_Gain, info.gain);
        settings->setValue(SG_Channels_GainEn, info.gainEn);
        settings->setValue(SG_Channels_Offset, info.offset);
        settings->setValue(SG_Channels_OffsetEn, info.offsetEn);
    }
    settings->endArray();
    settings->endGroup();
}
void ChannelInfoModel::loadSettings(QSettings* settings)
{
    settings->beginGroup(SettingGroup_Channels);
    unsigned size = settings->beginReadArray(SG_Channels_Channel);
    for (unsigned ci = 0; ci < size; ci++)
    {
        settings->setArrayIndex(ci);
        ChannelInfo chanInfo(ci);
        chanInfo.name       = settings->value(SG_Channels_Name     , chanInfo.name).toString();
        chanInfo.color      = settings->value(SG_Channels_Color    , chanInfo.color).value();
        chanInfo.visibility = settings->value(SG_Channels_Visible  , chanInfo.visibility).toBool();
        chanInfo.gain       = settings->value(SG_Channels_Gain     , chanInfo.gain).toDouble();
        chanInfo.gainEn     = settings->value(SG_Channels_GainEn   , chanInfo.gainEn).toBool();
        chanInfo.offset     = settings->value(SG_Channels_Offset   , chanInfo.offset).toDouble();
        chanInfo.offsetEn   = settings->value(SG_Channels_OffsetEn , chanInfo.offsetEn).toBool();
        if ((int) ci < infos.size())
        {
            infos[ci] = chanInfo;
            if (ci < _numOfChannels)
            {
                auto roles = QVector({
                    Qt::DisplayRole, Qt::EditRole, Qt::ForegroundRole, Qt::CheckStateRole});
                emit dataChanged(index(ci, 0), index(ci, COLUMN_COUNT-1), roles);
            }
        }
        else
        {
            infos.append(chanInfo);
        }
    }
    updateGainOrOffsetEn();
    settings->endArray();
    settings->endGroup();
}