#include "StatisticModel.h"
#include <QSize>
#include <QString>
#include <QBrush>
#include "statistic_params.h"
#include <QDebug>
#include <QApplication>
#include <QPalette>

/* Индексом в моделе является простое число, которое состоит из трех полей:
   старшие 8 битов - id-группы параметров (параметры соедининя/крейта или модуля),
   16-бит в середине - номер параметра (нулевой параметр соответствует отображению
   названия группы параметров) и младшие 8 бит - столбец (0 - название параметра,
   1 - значение) */
#define ID_GET_GROUP(id)  (((id) & 0xFF000000) >> 24)
#define ID_GET_PROP(id)   (((id) & 0x00FFFF00) >> 8)
#define ID_GET_COLUMN(id) ((id) & 0x000000FF)
#define MAKE_ID(group, prop, column) (qint32)(((group) << 24) | ((prop) << 8) | (column))



StatisticModel::StatisticModel(QObject *parent) :
    QAbstractItemModel(parent), m_con(0), m_crate(0), m_slot(-1) {
    /* добавляем параметры, которые общие, независимо от типа крейта/модуля */
    m_srvParams.append(new LtrSrvParamStatus());
    m_srvParams.append(new LtrSrvParamVersion());
}

StatisticModel::~StatisticModel() {
    qDeleteAll(m_srvParams.begin(), m_srvParams.end());
    qDeleteAll(m_crateParams.begin(), m_crateParams.end());
    qDeleteAll(m_slotParams.begin(), m_slotParams.end());
}

QModelIndex StatisticModel::index(int row, int column, const QModelIndex &parent) const {
    QModelIndex ret;
    if (parent.isValid()) {
        ret = createIndex(row, column, MAKE_ID(ID_GET_GROUP(parent.internalId()), row+1, column));
    } else {
        ret = createIndex(row, column, MAKE_ID(row+group_id_con, 0, column));
    }
    return ret;
}

QModelIndex StatisticModel::parent(const QModelIndex &index) const {
    QModelIndex ret;
    int prop = ID_GET_PROP(index.internalId());
    if (prop) {
        int group = ID_GET_GROUP(index.internalId());
        ret = createIndex(group,0, MAKE_ID(group, 0, 0));
    }
    return ret;
}

int StatisticModel::rowCount(const QModelIndex &parent) const {
    int rowCnt = 0;
    if (!parent.isValid()) {
        if (!m_con)
            rowCnt = 0;
        else if (!m_crate)
            rowCnt = 1;
        else if (m_slot==-1)
            rowCnt = 2;
        else
            rowCnt = 3;
    } else {
        int prop = ID_GET_PROP(parent.internalId());
        if (prop == 0) {
            int id = ID_GET_GROUP(parent.internalId());
            switch (id) {
                case group_id_con:
                    rowCnt = m_srvParams.size();
                    break;
                case group_id_crate:
                    rowCnt = m_crateParams.size();
                    break;
                case group_id_slot:
                    rowCnt = getSlotParamCnt(&m_mstat);
                    break;
            }
        }
    }
    return rowCnt;
}

int StatisticModel::columnCount(const QModelIndex &parent) const {
    return 2;
}

QVariant StatisticModel::data(const QModelIndex &index, int role) const {
    QVariant ret;
    int group = ID_GET_GROUP(index.internalId());
    int prop = ID_GET_PROP(index.internalId());
    int column = ID_GET_COLUMN(index.internalId());

    switch (group) {
        case group_id_con:
            switch (prop) {
                case 0:
                    if (column==0) {
                        if (role==Qt::DisplayRole)
                            ret = tr("%1 connection").arg(LTR_SERVICE_NAME);
                    }
                    break;
                default:
                    if (prop <= m_srvParams.size()) {
                        if (role==Qt::DisplayRole) {
                            ret = column ? m_srvParams[prop-1]->value(m_con) :
                                     m_srvParams[prop-1]->name();
                        } else if (role==Qt::ToolTipRole) {
                            ret = m_srvParams[prop-1]->tooltip();
                        } else if (role==Qt::StatusTipRole) {
                            ret = m_srvParams[prop-1]->statustip();
                        } else if (role==Qt::WhatsThisRole) {
                            ret = m_srvParams[prop-1]->whatthis();
                        } else if ((role==Qt::TextColorRole) && column) {
                            QColor col = m_srvParams[prop-1]->textColor(m_con);
                            if (col.isValid())
                                ret = col;
                        }
                    }
                    break;
            }
            break;
        case group_id_crate:
            switch (prop) {
                case 0:
                    if (column==0) {
                        if (role==Qt::DisplayRole)
                            ret = tr("Crate parameters");
                    }
                    break;
                default:
                    if (prop <= m_crateParams.size()) {
                        if (role==Qt::DisplayRole) {
                            ret = column ? m_crateParams[prop-1]->value(m_crate, &m_stat) :
                                     m_crateParams[prop-1]->name();
                        } else if (role==Qt::ToolTipRole) {
                            ret = m_crateParams[prop-1]->tooltip();
                        } else if (role==Qt::StatusTipRole) {
                            ret = m_crateParams[prop-1]->statustip();
                        } else if (role==Qt::WhatsThisRole) {
                            ret = m_crateParams[prop-1]->whatthis();
                        } else if ((role==Qt::TextColorRole) && column) {
                            QColor col = m_crateParams[prop-1]->textColor(m_crate, &m_stat);
                            if (col.isValid())
                                ret = col;
                        }
                    }
                    break;
            }
            break;
        case group_id_slot:
            switch (prop) {
                case 0:
                    if ((column==0) && (role==Qt::DisplayRole))
                        ret = tr("Slot %1 paramters").arg(QString::number(m_slot+1));
                    break;
                default:
                    if (prop <= m_slotParams.size()) {
                        if (role==Qt::DisplayRole) {
                            ret = column ? m_slotParams[prop-1]->value(m_crate, &m_mstat, m_slot) :
                                     m_slotParams[prop-1]->name();
                        } else if ((role==Qt::TextColorRole) && column) {
                            QColor col = m_slotParams[prop-1]->textColor(m_crate, &m_mstat, m_slot);
                            if (col.isValid())
                                ret = col;
                        } else if (role==Qt::ToolTipRole) {
                            ret = m_slotParams[prop-1]->tooltip();
                        } else if (role==Qt::StatusTipRole) {
                            ret = m_slotParams[prop-1]->statustip();
                        } else if (role==Qt::WhatsThisRole) {
                            ret = m_slotParams[prop-1]->whatthis();
                        }
                    }
                    break;
            }
            break;
    }

    if (role==Qt::BackgroundRole) {
        if (prop==0)
            ret = QApplication::palette().alternateBase();
    }
    return ret;
}

Qt::ItemFlags StatisticModel::flags(const QModelIndex &index) const {
    if (!index.isValid())
        return 0;

    return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}

QVariant StatisticModel::headerData(int section, Qt::Orientation orientation, int role) const {
    QVariant ret;
    if ((role == Qt::DisplayRole) && (orientation==Qt::Horizontal)) {
        if (section==0) {
            ret = tr("Parameter");
        } else if (section==1) {
            ret = tr("Value");
        }
    }
    return ret;
}

QSize StatisticModel::span(const QModelIndex &index) const {
    QSize size;
    if (index.isValid()) {
        int prop = ID_GET_PROP(index.internalId());
        if (prop==0) {
            size.setWidth(2);
        } else {
            size.setWidth(1);
        }
    }
    return size;
}


void StatisticModel::setItem(LtrdConnection *con, LtrCrateInfo *crate, int slot) {
    bool crate_changed = false;
    /* проверяем, изменилось ли текущее соединение */
    if (m_con!=con) {
        if (m_con) {
            disconnect(m_con, SIGNAL(statusChanged(LtrdConnection::connStatus)),
                       this, SLOT(srvConStatusChanged()));
            disconnect(con, SIGNAL(crateChanged(LtrCrateInfo*,LtrConEvent)),
                    this, SLOT(crateStateChanged(LtrCrateInfo*,LtrConEvent)));
        }
        if (con) {
            connect(con, SIGNAL(statusChanged(LtrdConnection::connStatus)),
                    SLOT(srvConStatusChanged()));
            connect(con, SIGNAL(crateChanged(LtrCrateInfo*,LtrConEvent)),
                    SLOT(crateStateChanged(LtrCrateInfo*,LtrConEvent)));
        }

        if (m_con && con) {
            m_con = con;
            srvConStatusChanged();
        } else if (!m_con && con) {
            beginInsertRows(QModelIndex(), 0, 0);
            m_con = con;
            endInsertRows();
        }
    }

    if (m_crate!=crate) {
        if (m_crate && crate) {
            m_crate = crate;
            crateSelectChanged();
        } else if (!m_crate && crate) {
            beginInsertRows(QModelIndex(), 1, 1);
            m_crate = crate;
            m_stat = crate->stat();
            endInsertRows();
        }
        crate_changed = true;
    }

    if ((m_slot != slot) || crate_changed) {
        if ((m_slot!=invalidSlot) && (slot!=invalidSlot)) {
            m_slot = slot;
            slotSelectChanged();
        } else if ((m_slot==invalidSlot) && (slot!=invalidSlot)) {
            beginInsertRows(QModelIndex(), 2, 2);
            m_slot = slot;
            m_mstat = crate->mstat(slot);
            endInsertRows();
        }
    }


    /* удаление идет снизу вверж (от слота к соединению), чтобы не менялись
       индексы идущих ниже строк */
    if ((m_slot!=invalidSlot) && (slot==invalidSlot)) {
        beginRemoveRows(QModelIndex(), 2, 2);
        m_slot = slot;
        endRemoveRows();
    }
    if (m_crate && !crate) {
        beginRemoveRows(QModelIndex(), 1, 1);
        m_crate = crate;
        endRemoveRows();
    }
    if (m_con && !con) {
        beginRemoveRows(QModelIndex(), 0, 0);
        m_con = con;
        endRemoveRows();
    }
}

void StatisticModel::srvConStatusChanged() {
    emit dataChanged(createIndex(0,0, MAKE_ID(group_id_con,1,0)),
                     createIndex(m_srvParams.size()-1,1, MAKE_ID(group_id_con,
                                                        m_srvParams.size(),1)));
}

void StatisticModel::crateSelectChanged() {
    QList<LtrCrateParam *> newParams, oldParams = m_crateParams;
    TLTR_CRATE_DESCR descr = m_crate->descr();
    m_stat = m_crate->stat();

    newParams.append(new LtrCrateParamType());
    newParams.append(new LtrCrateParamSerial());
    newParams.append(new LtrCrateParamInterface());
    newParams.append(new LtrCrateParamMode());
    newParams.append(new LtrCrateParamFirmVer());
    newParams.append(new LtrCrateParamBootloaderVer());
    newParams.append(new LtrCrateParamFpgaVer());
    newParams.append(new LtrCrateParamBoardRevision());
    if (descr.protocol_ver_major || descr.protocol_ver_minor)     {
        newParams.append(new LtrCrateParamProtocolVersion());
        newParams.append(new LtrCrateParamSlotsConfigVersion());
    }
    newParams.append(new LtrCrateParamConTime());
    newParams.append(new LtrCrateParamClientCnt());
    newParams.append(new LtrCrateParamCtlClientCnt());
    newParams.append(new LtrCrateParamSentWrds());
    newParams.append(new LtrCrateParamRecvWrds());
    newParams.append(new LtrCrateParamSentBandwidth());
    newParams.append(new LtrCrateParamRecvBandwidth());
    newParams.append(new LtrCrateParamStartMarkCnt());
    newParams.append(new LtrCrateParamSecondMarkCnt());
    newParams.append(new LtrCrateParamUnixtime());
    newParams.append(new LtrCrateParamThermL());
    newParams.append(new LtrCrateParamThermH());

    if (oldParams.size() > newParams.size()) {
        beginRemoveRows(createIndex(2,0, MAKE_ID(group_id_crate,0,0)),
                        newParams.size(), oldParams.size());
        m_crateParams = newParams;
        endRemoveRows();
    } else if (newParams.size() > oldParams.size()) {
        beginInsertRows(createIndex(2,0, MAKE_ID(group_id_crate,0,0)),
                        oldParams.size(), newParams.size());
        m_crateParams = newParams;
        endInsertRows();
    } else {
        m_crateParams = newParams;
    }

    emit dataChanged(createIndex(0,0, MAKE_ID(group_id_crate,0,0)),
                     createIndex(newParams.size()-1,1, MAKE_ID(group_id_crate,0,0)));

    qDeleteAll(oldParams.begin(), oldParams.end());
}

void StatisticModel::slotSelectChanged() {
    TLTR_MODULE_STATISTIC mstat;
    QList<LtrSlotParam *> newParams, oldParams = m_slotParams;

    mstat.mid = LTR_MID_EMPTY;
    if (m_slot!=invalidSlot)
        mstat = m_crate->mstat(m_slot);

    newParams.append(new LtrSlotParamState());

    if (m_con->statisticAviableForMid(mstat.mid)) {
        newParams.append(new LtrSlotParamSpeed());
        newParams.append(new LtrSlotParamClientCnt());
        newParams.append(new LtrSlotParamSentWrds());
        newParams.append(new LtrSlotParamRecvWrds());
        newParams.append(new LtrSlotParamSendBandwidth());
        newParams.append(new LtrSlotParamRecvBandwidth());
        newParams.append(new LtrSlotParamSendSrvBufFull());
        newParams.append(new LtrSlotParamRecvSrvBufFull());
        newParams.append(new LtrSlotParamSendSrvBufFullMax());
        newParams.append(new LtrSlotParamRecvSrvBufFullMax());
        newParams.append(new LtrSlotParamRBufOvs());
        newParams.append(new LtrSlotParamDropWrds());

        if (mstat.flags & LTR_MODULE_FLAGS_USE_SYNC_MARK) {
            newParams.append(new LtrSlotParamStartMarkCnt());
            newParams.append(new LtrSlotParamSecondMarkCnt());
        }

        if (mstat.flags & LTR_MODULE_FLAGS_USE_HARD_SEND_FIFO) {
            newParams.append(new LtrSlotParamSndFifoSize());
            newParams.append(new LtrSlotParamSndFifoFullSize());
            newParams.append(new LtrSlotParamSndFifoUnderrun());
            newParams.append(new LtrSlotParamSndFifoOverrun());
            newParams.append(new LtrSlotParamSndFifoInternal());
        }
    }



    if (oldParams.size() > newParams.size()) {
        beginRemoveRows(createIndex(2,0, MAKE_ID(group_id_slot,0,0)),
                        newParams.size(), oldParams.size());
        m_slotParams = newParams;
        m_mstat = mstat;
        endRemoveRows();
    } else if (newParams.size() > oldParams.size()) {
        beginInsertRows(createIndex(2,0, MAKE_ID(group_id_slot,0,0)),
                        oldParams.size(), newParams.size());
        m_slotParams = newParams;
        m_mstat = mstat;
        endInsertRows();
    } else {
        m_slotParams = newParams;
        m_mstat = mstat;
    }

    emit dataChanged(createIndex(0,0, MAKE_ID(group_id_slot,0,0)),
                     createIndex(newParams.size()-1,1, MAKE_ID(group_id_slot,0,0)));


    qDeleteAll(oldParams.begin(), oldParams.end());
}

void StatisticModel::slotParamsChanged() {
    TLTR_MODULE_STATISTIC mstat;

    mstat.mid = LTR_MID_EMPTY;
    if (m_slot!=invalidSlot)
        mstat = m_crate->mstat(m_slot);

    if (mstat.mid != m_mstat.mid)
        slotSelectChanged();
}

void StatisticModel::crateStateChanged(LtrCrateInfo *crate, LtrConEvent evt) {
    if (crate==m_crate) {
        if (evt == LTR_CONEVENT_DATA_CHANGED) {
            crateSelectChanged();
            slotSelectChanged();
        }
        if (evt == LTR_CONEVENT_REMOVE) {
            setItem(m_con, 0, invalidSlot);
        }
    }
}

int StatisticModel::getSlotParamCnt(const TLTR_MODULE_STATISTIC *stat) const {
    return m_slotParams.size();
}
