#include "LtrCratesModel.h"
#include <QBrush>

/* базовый элемент дерева, хранящий информацию о родителе и список потомков */
class TreeItem {
public:
    TreeItem(TreeItem* parent, void* intp) : m_parent(parent), m_internal(intp) {}
    ~TreeItem() {qDeleteAll(m_children);}

    /* номер элемента в списке детей его родителя */
    int row()                             {return m_parent->childIndex(this);}
    /* получение дочернего лемента по идексу */
    TreeItem* child(int i) const          {return m_children.at(i);}
    /* количество дочерних элементов */
    int childCount() const                {return m_children.size();}
    /* по дочернему элементу определение его номера */
    int childIndex(TreeItem* child) const {return m_children.indexOf(child);}
    /* добавление дочернего элемента */
    void apendChild(TreeItem* child)      {m_children.append(child);}

    TreeItem* fndChildByIntp(void* intp, int *ind);
    void* intp() {return m_internal;}

    TreeItem* parent() const              {return m_parent;}

    void removeChild(int ind) {m_children.removeAt(ind);}

    /*--- Функции, которые могут быт переопределены */
    virtual int columnCount() const           {return 1;}
    virtual QVariant data(int role, int column) {return QVariant();}

private:
    TreeItem* m_parent;
    QList<TreeItem*> m_children;
    void *m_internal;
} ;

/* узел дерева, хранящий информацию о соединении с ltrd */
class SrvConItem : public TreeItem {
public:
    SrvConItem(LtrdConnection *con, TreeItem* parent) : TreeItem(parent, con), m_con(con) {}

    LtrdConnection *connection() {return m_con;}

    virtual QVariant data(int role, int column);
private:
    LtrdConnection *m_con;
};

/* узел дерева, хранящий информацию о крейте */
class CrateInfoItem : public TreeItem {
public:
    CrateInfoItem(LtrCrateInfo *info, TreeItem* parent) :
        TreeItem(parent, info), m_info(info) {}

    LtrCrateInfo *info() {return m_info;}

    virtual QVariant data(int role, int column);
private:
    LtrCrateInfo *m_info;
};

/* узел дерева, хранящий информацию о модуле */
class CrateModuleItem : public TreeItem {
public:
    CrateModuleItem(LtrCrateInfo *info, int slot, int mid, TreeItem* parent) :
        TreeItem(parent, info), m_info(info), m_slot(slot), m_mid(mid) {}

    LtrCrateInfo *info() const {return m_info;}
    int slot() const {return m_slot;}
    int mid()  const {return m_mid;}

    void setMid(int mid) {m_mid = mid;}

    virtual QVariant data(int role, int column);
private:
    LtrCrateInfo *m_info;
    int m_slot;
    int m_mid;
};


TreeItem* TreeItem::fndChildByIntp(void* intp, int* ind) {
    TreeItem* ret = 0;
    for (int i=0; !ret && (i < m_children.size()); i++) {
        if (m_children.at(i)->intp()==intp)
            ret = m_children.at(i);
        if (ind)
            *ind = i;
    }
    return ret;
}

QVariant SrvConItem::data(int role, int column) {
    QVariant ret;
    if (column==0) {
        switch (role) {
            case Qt::DisplayRole:
                /* для соединения возвращаем строку адреса в качестве данных */
                ret = LTR_SERVICE_NAME " (" + m_con->addr().toString() + ")";
                break;
            case Qt::ForegroundRole:
                /* состояние подключения определяет цвет записи */
                switch (m_con->status()) {
                    case LtrdConnection::STATUS_CONNECTING:
                        ret = QBrush(Qt::darkBlue);
                        break;
                    case LtrdConnection::STATUS_ONLINE:
                        ret = QBrush(Qt::darkGreen);
                        break;
                    case LtrdConnection::STATUS_ERROR:
                        ret = QBrush(Qt::red);
                        break;
                    case LtrdConnection::STATUS_OFFLINE:
                        ret = QBrush(Qt::black);
                    default:
                        break;
                }
            default:
                break;
        }
    }
    return ret;
}

QVariant CrateInfoItem::data(int role, int column) {
    QVariant ret;
    if (column==0) {
        switch (role) {
            case Qt::DisplayRole:
                /* для соединения возвращаем строку адреса в качестве данных */
                ret = m_info->typeName() + " (" + m_info->serial() + ")";
                break;
            case Qt::DecorationRole: {
                    int iface = m_info->crateInterface();
                    if (iface==LTR_CRATE_IFACE_USB) {
                        ret = QIcon(":/icons/usb.png");
                    } else if (iface==LTR_CRATE_IFACE_TCPIP) {
                        ret = QIcon(":/icons/ipaddr.png");
                    }
                }
                break;

        }
    }
    return ret;
}

QVariant CrateModuleItem::data(int role, int column) {
    QVariant ret;
    if (column==0) {
        switch (role) {
            case Qt::DisplayRole:
                int slot_num = parent()->childIndex(this);
                QString slot_str = QString("%1: ").arg(QString::number(slot_num+1),2,' ');
                /* для соединения возвращаем строку адреса в качестве данных */
                if (m_mid==LTR_MID_EMPTY) {
                    slot_str += "---";
                } else if (m_mid==LTR_MID_IDENTIFYING) {
                    slot_str += LtrCratesModel::tr("Initilization");
                } else if ((m_mid&0xFF)==((m_mid>>8)&0xFF)) {
                    slot_str += m_info->moduleName(slot_num);
                } else {
                    slot_str += LtrCratesModel::tr("Invalid module ID (!)");
                }

                ret = slot_str;
                break;                
        }
    }
    return ret;
}





LtrCratesModel::LtrCratesModel(QObject *parent) :
    QAbstractItemModel(parent) {
    m_root = new TreeItem(0,0);
}

LtrCratesModel::~LtrCratesModel() {
    delete m_root;
}



void LtrCratesModel::addNewSrvCon(LtrdConnection* con) {

    connect(con, SIGNAL(statusChanged(LtrdConnection::connStatus)),
            SLOT(ltrdStatusChanged(LtrdConnection::connStatus)));
    connect(con, SIGNAL(crateChanged(LtrCrateInfo*,LtrConEvent)),
            SLOT(crateInfoChanged(LtrCrateInfo*,LtrConEvent)));

    beginInsertRows(QModelIndex(), m_root->childCount(), m_root->childCount());
    m_root->apendChild(new SrvConItem(con, m_root));
    endInsertRows();   
}

void LtrCratesModel::removeSrvCon(LtrdConnection *con) {
    int row;
    SrvConItem *item = foundSrvConItem(con, &row);
    if (item) {
        disconnect(con, SIGNAL(statusChanged(LtrdConnection::connStatus)), this,
                SLOT(ltrdStatusChanged(LtrdConnection::connStatus)));
        disconnect(con, SIGNAL(crateChanged(LtrCrateInfo*,LtrConEvent)), this,
                SLOT(crateInfoChanged(LtrCrateInfo*,LtrConEvent)));

        beginRemoveRows(QModelIndex(), row, row);
        m_root->removeChild(row);
        endRemoveRows();
    }

}

int LtrCratesModel::cratesCnt() const {
    int cratesCnt=0;
    for (int i=0; i < m_root->childCount(); i++) {
        cratesCnt+=static_cast<SrvConItem*>(m_root->child(i))->childCount();
    }
    return cratesCnt;
}



void LtrCratesModel::getInfo(QModelIndex ind, LtrdConnection **con, LtrCrateInfo **info, int* slot) const {
    /* определяем, какого уровня элемент выбран */
    TreeItem* tree= static_cast<TreeItem*>(ind.internalPointer());
    CrateModuleItem *module = dynamic_cast<CrateModuleItem *>(tree);
    /* выбран модуль => возвращаем действительными все параметры */
    if (module) {
        if (slot)
            *slot = module->slot();
        if (info)
            *info = module->info();
        if (con)
            *con = dynamic_cast<SrvConItem*>(module->parent()->parent())->connection();
    } else {
        if (slot)
            *slot = -1;
        CrateInfoItem *crate = dynamic_cast<CrateInfoItem *>(tree);
        if (crate) {
            if (info)
                *info = crate->info();
            if (con)
                *con = dynamic_cast<SrvConItem*>(crate->parent())->connection();
        } else {
            if (info)
                *info = 0;
            if (con) {
                SrvConItem* con_item = dynamic_cast<SrvConItem*>(tree);
                *con = con_item ? con_item->connection() : 0;
            }
        }
    }
}

QModelIndex LtrCratesModel::index(int row, int column, const QModelIndex &parent) const {
    QModelIndex ret_ind;
    if (this->hasIndex(row, column, parent)) {
        TreeItem* parItem= parent.isValid() ?
                    static_cast<TreeItem*>(parent.internalPointer()) : m_root;
        ret_ind = createIndex(row, column, parItem->child(row));
    }
    return ret_ind;
}

QModelIndex LtrCratesModel::parent(const QModelIndex &index) const {
    QModelIndex ret_ind;
    if (index.isValid()) {
        TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
        TreeItem* parItem = item->parent();
        if (parItem && (parItem!=m_root))
            ret_ind = createIndex(parItem->row(), 0, parItem);
    }
    return ret_ind;
}

int LtrCratesModel::rowCount(const QModelIndex &parent) const {
    TreeItem* parItem= parent.isValid() ?
                static_cast<TreeItem*>(parent.internalPointer()) : m_root;
    return parItem->childCount();
}

int LtrCratesModel::columnCount(const QModelIndex &parent) const {
    TreeItem* parItem= parent.isValid() ?
                static_cast<TreeItem*>(parent.internalPointer()) : m_root;
    return parItem->columnCount();
}

QVariant LtrCratesModel::data(const QModelIndex &index, int role) const {
    QVariant ret;
    if (index.isValid()) {
        TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
        ret = item->data(role, index.column());
    }
    return ret;
}

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

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

QList<LtrdConnection *> LtrCratesModel::connectionList() const {
    QList<LtrdConnection *> list;
    for (int i=0; i < m_root->childCount(); i++) {
        list.append(static_cast<SrvConItem*>(m_root->child(i))->connection());
    }
    return list;
}

SrvConItem *LtrCratesModel::foundSrvConItem(LtrdConnection *con, int *row) {
    SrvConItem* item = 0;
    for (int i=0; !item && i < m_root->childCount(); i++) {
        SrvConItem* srv_item =  static_cast<SrvConItem*>(m_root->child(i));
        if (srv_item->connection() == con) {
            item = srv_item;
            if (row)
                *row = i;
        }
    }
    return item;
}

void LtrCratesModel::ltrdStatusChanged(LtrdConnection::connStatus status) {
    LtrdConnection* con = qobject_cast<LtrdConnection*>(sender());
    int row;
    m_root->fndChildByIntp(con, &row);

    QModelIndex ind = index(row, 0, QModelIndex());
    emit dataChanged(ind, ind);
}

void LtrCratesModel::crateInfoChanged(LtrCrateInfo *info, LtrConEvent event) {
    LtrdConnection* con = qobject_cast<LtrdConnection*>(sender());
    int row;
    SrvConItem* srv_item = dynamic_cast<SrvConItem*>(m_root->fndChildByIntp(con, &row));
    if (srv_item) {
        QModelIndex ind = index(row, 0, QModelIndex());

        switch (event) {
            case LTR_CONEVENT_ADD: {
                    TLTR_CRATE_STATISTIC stat = info->stat();
                    CrateInfoItem *crate_info = new CrateInfoItem(info, srv_item);
                    beginInsertRows(ind, srv_item->childCount(), srv_item->childCount());
                    srv_item->apendChild(crate_info);
                    endInsertRows();


                    QModelIndex crate_ind = index(srv_item->childCount()-1, 0, ind);

                    beginInsertRows(crate_ind, 0, stat.modules_cnt-1);
                    for (int slot = 0; slot < stat.modules_cnt; slot++) {
                        crate_info->apendChild(new CrateModuleItem(info, slot, stat.mids[slot],
                                                                   crate_info));
                    }
                    endInsertRows();
                }
                break;
            case LTR_CONEVENT_REMOVE: {
                    int crate_ind;
                    CrateInfoItem *crate_info = dynamic_cast<CrateInfoItem*>
                                                (srv_item->fndChildByIntp(info, &crate_ind));
                    if (crate_info) {
                        beginRemoveRows(ind, crate_ind, crate_ind);
                        srv_item->removeChild(crate_ind);
                        endRemoveRows();
                        delete crate_info;
                    }
                }
                break;
            case LTR_CONEVENT_DATA_CHANGED: {
                    int crate_num;
                    CrateInfoItem *crate_info = dynamic_cast<CrateInfoItem*>
                                                (srv_item->fndChildByIntp(info, &crate_num));
                    QModelIndex crate_ind = index(crate_num, 0, ind);

                    if (crate_info) {
                        TLTR_CRATE_STATISTIC stat = crate_info->info()->stat();
                        int slot_cnt = stat.modules_cnt;
                        int old_slot_cnt = crate_info->childCount();

                        /* обслуживаем ситуацию, когда изменилось количество
                           слотов в крейте (например поменялся режим) */
                        if (slot_cnt > old_slot_cnt) {
                            beginInsertRows(crate_ind, old_slot_cnt, slot_cnt-1);
                            for (int slot = old_slot_cnt; slot < slot_cnt; slot++) {
                                crate_info->apendChild(new CrateModuleItem(info, slot, stat.mids[slot],
                                                                           crate_info));
                            }
                            endInsertRows();
                        } else if (slot_cnt < old_slot_cnt) {
                            beginRemoveRows(crate_ind, slot_cnt, old_slot_cnt-1);
                            for (int slot = old_slot_cnt-1; slot >= slot_cnt; slot--) {
                                crate_info->removeChild(slot);
                            }
                            endRemoveRows();
                        }


                        for (int i=0; i < slot_cnt; i++) {
                            CrateModuleItem* module = dynamic_cast<CrateModuleItem*>(crate_info->child(i));
                            if (module->mid() != stat.mids[i]) {
                                QModelIndex ind = createIndex(i,0, module);
                                module->setMid(stat.mids[i]);
                                emit dataChanged(ind, ind);
                            }
                        }
                    }
                }
                break;
        }
    }
}
