#include "LtrdConnection.h"
#include <QMetaType>
#include <QTime>
#include <QTimer>
#include <QTimerEvent>
#include <QElapsedTimer>
#include "StatisticPanel/statistic_params.h"


LtrdConnection::LtrdConnection(QHostAddress addr, bool autocon, quint16 port, QObject *parent) :
    QObject(parent), m_status(STATUS_OFFLINE), err(LTR_OK), m_addr(addr),
    m_crateAddr(QHostAddress((quint32)0)), m_crateMask(QHostAddress((quint32)0)),    
    m_vers(0), m_autocon(autocon), m_cur_restart_tout(restart_tout_init), m_restart_tmr_id(0), m_tmr_id(0) {
    LTR_Init(&hnd);
    LTRLOG_Init(&m_loghnd);
    hnd.cc = LTR_CC_CHNUM_CONTROL;
    strcpy(hnd.csn, LTR_CSN_SERVER_CONTROL);
    hnd.saddr = addr.toIPv4Address();
    hnd.sport = port;

    qRegisterMetaType<LtrdConnection::connStatus>("LtrdConnection::connStatus");
    qRegisterMetaType<LtrConEvent>("LtrConEvent");
    qRegisterMetaType<TLTR_CRATE_IP_ENTRY>("TLTR_CRATE_IP_ENTRY");
    qRegisterMetaType<QList<QSharedPointer<LtrSrvLogRec> > >("QList<QSharedPointer<LtrSrvLogRec> >");

    connect(this, SIGNAL(aboutToClose()), this, SLOT(privCloseConnection()));
    connect(this, SIGNAL(aboutToStart()), SLOT(privStartConnection()));



    moveToThread(&m_thread);
    m_thread.start();

}

LtrdConnection::~LtrdConnection() {
    if (LTR_IsOpened(&hnd)==LTR_OK) {
        LTR_Close(&hnd);
    }
    if (LTR_IsOpened(&m_loghnd.Channel)==LTR_OK) {
        LTRLOG_Close(&m_loghnd);
    }
}

void LtrdConnection::start() {
    if (!m_thread.isRunning())
        m_thread.start();
    Q_EMIT aboutToStart();
}

int LtrdConnection::cratesCount() {
    QMutexLocker lock(&m_mutex);
    return m_crateList.size();
}

LtrCrateInfo *LtrdConnection::crateInfo(int i) {
    QMutexLocker lock(&m_mutex);
    return i < m_crateList.size() ? m_crateList.at(i) : 0;
}

QList<TLTR_CRATE_IP_ENTRY> LtrdConnection::ipEntryList() {
    QMutexLocker lock(&m_mutex);
    return m_ipList;
}

bool LtrdConnection::statisticAviableForMid(WORD mid) const {
    return (mid != LTR_MID_EMPTY) && !((mid == LTR_MID_IDENTIFYING) && !supportIndentifyModuleStat());
}

int LtrdConnection::getLogLevel(unsigned* lvl) {
    QMutexLocker lock(&m_mutex);
    return LTR_GetLogLevel(&hnd, (INT*)lvl);
}

int LtrdConnection::setLogLevel(unsigned lvl) {
    QMutexLocker lock(&m_mutex);
    return LTR_SetLogLevel(&hnd, lvl, TRUE);
}

int LtrdConnection::getUintParam(int param, unsigned *val) {
    QMutexLocker lock(&m_mutex);
    DWORD size = sizeof(*val);
    return LTR_GetServerParameter(&hnd, param, val, &size);
}

int LtrdConnection::setUintParam(int param, unsigned val) {
    QMutexLocker lock(&m_mutex);
    DWORD size = sizeof(val);
    return LTR_SetServerParameter(&hnd, param, &val, size);
}



int LtrdConnection::moduleReset(LtrCrateInfo *info, int slot) {
    QMutexLocker lock(&m_mutex);
    return LTR_ResetModule(&hnd, info->crateInterface(), info->serial().toLatin1(),
                           slot+LTR_CC_CHNUM_MODULE1, 0);
}

void LtrdConnection::logLock() {
    m_logmutex.lock();
}

void LtrdConnection::logUnlock() {
    m_logmutex.unlock();
}

QString LtrdConnection::getErrorString(INT err) {
    return QSTRING_FROM_CSTR(LTR_GetErrorString(err));
}


void LtrdConnection::remove() {
    m_thread.exit();
    m_thread.wait();
}

void LtrdConnection::wait() {
    m_thread.wait();
}



void LtrdConnection::timerEvent(QTimerEvent *event) {
    if (event->timerId()==m_tmr_id) {
        if (m_status == STATUS_ONLINE) {
            checkCrates();
            readLog();
        }
    } else {
        killTimer(event->timerId());
        if (event->timerId()==m_restart_tmr_id) {
            m_restart_tmr_id = 0;
            restartConnection();
        }
    }
}

void LtrdConnection::clearConnection() {
    killTimer(m_tmr_id);
    m_tmr_id = 0;
    for (int i=m_crateList.length()-1; i >= 0; i--) {
        LtrCrateInfo* rem_info = m_crateList.at(i);
        /* при ошибке при работе с крейтом - удаляем его */
        m_crateList.removeAt(i);
        Q_EMIT crateChanged(rem_info, LTR_CONEVENT_REMOVE);
    }

    for (int i=m_ipList.length()-1; i >= 0; i--) {
        TLTR_CRATE_IP_ENTRY rem_ip = m_ipList.at(i);
        m_ipList.removeAt(i);
        Q_EMIT ipAddrStatusChanged(rem_ip, LTR_CONEVENT_REMOVE, i);
    }

    if (m_status != STATUS_ERROR)
        m_status = STATUS_OFFLINE;
    Q_EMIT statusChanged(m_status);

    if (LTR_IsOpened(&hnd)==LTR_OK) {
        LTR_Close(&hnd);
    }
    if (LTRLOG_IsOpened(&m_loghnd)==LTR_OK) {
        /* сперва закрываем соединение журнала со своей строны, затем ожидаем
           закрытия соединения со строны службы, чтобы не разрывать соедениение
           посередине передачи */
        INT ret = LTRLOG_Shutdown(&m_loghnd);
        TLTRLOG_MSG *msg = Q_NULLPTR;
        QElapsedTimer tmr;
        tmr.start();
        while ((ret == LTR_OK) && (tmr.elapsed() < log_shutdown_tout)) {
            ret = LTRLOG_GetNextMsg(&m_loghnd, &msg, 100);
            if (msg) {
                LTRLOG_FreeMsg(msg);
            }
        }
        LTRLOG_Close(&m_loghnd);
    }
}

void LtrdConnection::stopOnError() {
    m_status = STATUS_ERROR;
    clearConnection();
    scheduleRestart();
}


int LtrdConnection::findCrate(QString serial, int intf /*= CRATE_IFACE_UNKNOWN*/) {
    int fnd_index = -1;
    /* смотрим, есть ли этот крейт уже в нашем списке */
    for (int c=0; (fnd_index<0) && (c < m_crateList.size()); c++) {
        LtrCrateInfo* info = m_crateList.at(c);
        if ((info->m_serial == serial) &&
                ((info->crateInterface() == intf) || (intf==LTR_CRATE_IFACE_UNKNOWN))) {
            fnd_index = c;
        }
    }
    return fnd_index;
}

int LtrdConnection::processCrate(char *serial, int intf) {
    TLTR_CRATE_STATISTIC stat;    
    int crate_err = 0;
    int new_crate = 0;
    int  fnd_ind = 0;
    LtrCrateInfo* fnd_info = 0;

    memset(&stat, 0, sizeof(stat));

    fnd_ind = findCrate(serial, intf);
    if (fnd_ind >= 0) {
        fnd_info = m_crateList.at(fnd_ind);
        fnd_info->m_fnd = 1;
    }

    /* если не нашли => созадем новый */
    if (!fnd_info) {
        fnd_info = new LtrCrateInfo(intf, serial, this);
        new_crate = 1;
    }

    QMutexLocker lock(&m_mutex);

    /* получаем статистику по крейту */
    crate_err = LTR_GetCrateStatistic(&hnd, fnd_info->m_intface, fnd_info->m_serial,
                                      &stat, sizeof(stat));
    fnd_info->setStat(&stat);

    for (int slot=0; !crate_err && (slot < stat.modules_cnt); slot++) {
        TLTR_MODULE_STATISTIC mstat;
        memset(&mstat, 0, sizeof(mstat));
        mstat.mid = stat.mids[slot];
        if (statisticAviableForMid(mstat.mid)) {
            crate_err = LTR_GetModuleStatistic(&hnd, fnd_info->m_intface, fnd_info->m_serial,
                                               slot+LTR_CC_CHNUM_MODULE1,
                                               &mstat, sizeof(mstat));
            if (crate_err == LTR_ERROR_EMPTY_SLOT) {
                mstat.mid = LTR_MID_EMPTY;
                crate_err = 0;
            }
        }

        if (!crate_err) {
            fnd_info->setModStat(&mstat, slot);
        }
    }

    if (!crate_err) {
        /* если крейт новый, добавляем его в список */
        if (new_crate) {

            crate_err = LTR_GetCrateDescr(&hnd, fnd_info->m_intface,
                                          fnd_info->m_serial,
                                          &fnd_info->m_descr,
                                          sizeof(fnd_info->m_descr));
            if (!crate_err) {
                m_crateList << fnd_info;
            }
        }
        Q_EMIT crateChanged(fnd_info, new_crate ? LTR_CONEVENT_ADD :
                                                LTR_CONEVENT_DATA_CHANGED);
    } else if (!new_crate) {
        /* при ошибке при работе с крейтом - удаляем его */
        m_crateList.removeAt(fnd_ind);
        Q_EMIT crateChanged(fnd_info, LTR_CONEVENT_REMOVE);
    }
    return crate_err;
}


void LtrdConnection::checkCrates(void){
    for (int c=0; c< m_crateList.size(); c++) {
        m_crateList.at(c)->m_fnd=0;
    }


    if (m_vers <= 0x2000100) {
        /* проверяем состояние подключенных крейтов */
        char crates[LTR_CRATES_MAX][LTR_CRATE_SERIAL_SIZE];
        m_mutex.lock();
        err = LTR_GetCrates(&hnd, (BYTE*)crates);
        m_mutex.unlock();

        for (int i=0; i < LTR_CRATES_MAX; i++) {
            if (crates[i][0]!=0) {
                processCrate(crates[i]);
            }
        }
    } else {
        TLTR_CRATE_INFO *info_list=0;
        char (*serial_list)[LTR_CRATE_SERIAL_SIZE]=0;
        DWORD fnd = 0;
        m_mutex.lock();
        err = LTR_GetCratesEx(&hnd, 0, 0, &fnd, NULL, NULL, NULL);
        m_mutex.unlock();
        if (!err && fnd) {
            serial_list = (char(*)[LTR_CRATE_SERIAL_SIZE]) malloc(LTR_CRATE_SERIAL_SIZE*fnd);
            info_list = (TLTR_CRATE_INFO *)malloc(sizeof(info_list[0])*fnd);
            if ((serial_list==NULL) || (info_list==NULL)) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                m_mutex.lock();
                err = LTR_GetCratesEx(&hnd, fnd, 0, NULL, &fnd, serial_list, info_list);
                m_mutex.unlock();
            }

            if (!err) {
                for (DWORD i=0; i < fnd; i++)
                    processCrate(serial_list[i], info_list[i].CrateInterface);
            }
        }

        free(info_list);
        free(serial_list);
    }

    if (err==LTR_OK) {
        /* удаляем крейты, которые не нашли при новом опросе */
        for (int c=m_crateList.size()-1; c>=0; c--) {
            LtrCrateInfo* info = m_crateList.at(c);
            if (!info->m_fnd) {
                QMutexLocker lock(&m_mutex);
                m_crateList.removeAt(c);
                Q_EMIT crateChanged(info, LTR_CONEVENT_REMOVE);
            }
        }
    }

    if (err==LTR_OK) {
        DWORD fnd_cnt = 0;
        int list_size = m_ipList.size();
        int new_cnt = 0;
        TLTR_CRATE_IP_ENTRY *ip_list = 0;

        /* узнаем, сколько записей есть */
        m_mutex.lock();
        err = LTR_GetListOfIPCrates(&hnd, 0, m_crateAddr.toIPv4Address(),
                                    m_crateMask.toIPv4Address(), &fnd_cnt, 0, 0);
        m_mutex.unlock();
        if ((err==LTR_OK) && fnd_cnt) {
            ip_list = (TLTR_CRATE_IP_ENTRY *)malloc(sizeof(TLTR_CRATE_IP_ENTRY)*fnd_cnt);
            if (!ip_list) {
                err = LTR_ERROR_MEMORY_ALLOC;
            } else {
                QMutexLocker lock(&m_mutex);
                err = LTR_GetListOfIPCrates(&hnd, fnd_cnt, m_crateAddr.toIPv4Address(),
                                            m_crateMask.toIPv4Address(), 0, &fnd_cnt, ip_list);
            }

            if (err == LTR_OK) {
                for (unsigned i=0; i < fnd_cnt; i++) {
                    /* смотрим, есть ли уже эта запись в нашем списке */
                    int fnd = 0;
                    for (int j=0; !fnd && (j < list_size); j++) {
                        if (m_ipList.at(j).ip_addr==ip_list[i].ip_addr) {
                            /* если есть - то проверяем, что не было изменений */
                            fnd = 1;

                            TLTR_CRATE_IP_ENTRY ipFromList = m_ipList.at(j);
                            if ((ipFromList.flags!=ip_list[i].flags) ||
                                    (ipFromList.status!=ip_list[i].status) ||
                                    strcmp(ipFromList.serial_number, ip_list[i].serial_number)) {
                                QMutexLocker lock(&m_mutex);
                                m_ipList.replace(j, ip_list[i]);
                                Q_EMIT ipAddrStatusChanged(ip_list[i], LTR_CONEVENT_DATA_CHANGED, j);
                            }
                        }
                    }
                    /* добавляем новую запись */
                    if (!fnd) {
                        QMutexLocker lock(&m_mutex);
                        m_ipList.append(ip_list[i]);
                        Q_EMIT ipAddrStatusChanged(ip_list[i], LTR_CONEVENT_ADD,
                                                 list_size+new_cnt);
                        new_cnt++;
                    }
                }
            }
        }

        if (err==LTR_OK) {
            /* если не все адреса были в списке, то ищем, какие адреса
              исчезли */
            if (fnd_cnt != (new_cnt + list_size)) {
                for (int j=0; j < list_size; j++) {
                    int fnd = 0;
                    for (unsigned i=0; !fnd && (i<fnd_cnt); i++) {
                        if (m_ipList.at(j).ip_addr==ip_list[i].ip_addr)
                            fnd = 1;
                    } if (!fnd) {
                        QMutexLocker lock(&m_mutex);
                        TLTR_CRATE_IP_ENTRY rem_ip = m_ipList.at(j);
                        m_ipList.removeAt(j);
                        Q_EMIT ipAddrStatusChanged(rem_ip, LTR_CONEVENT_REMOVE, j);
                        list_size--;
                    }
                }
            }
        }
        free(ip_list);
    }

    /* при ошибке выполнения запроса к серверу - переводим соединение в
       состояние ошибки и удаляем все крейты */
    if (err!=LTR_OK) {
        QMutexLocker lock(&m_mutex);
        stopOnError();
    }
}

int LtrdConnection::readLog() {
    int err = LTR_OK;
    QMutexLocker locker(&m_logmutex);
    if (supportLog()) {
        QDateTime checkDateTime;
        /* открываем соединение чтения журнала, если оно не было открыто на данный момент */
        if (LTRLOG_IsOpened(&m_loghnd) != LTR_OK) {
            err = LTRLOG_Open(&m_loghnd, hnd.saddr, hnd.sport);
            /* при повторной инициализации соединения запоминаем
             * время последней считанной записи, чтобы избежать дублирования
             * прочитанных записей с прошлым соединением */
            if (!m_logrecs.isEmpty())
                checkDateTime = m_logrecs.last()->time();
        }

        if (LTRLOG_IsOpened(&m_loghnd) == LTR_OK) {
            QList< QSharedPointer<LtrSrvLogRec> > recs;
            bool has_msg = false;

            do {
                TLTRLOG_MSG *msg = Q_NULLPTR;
                err = LTRLOG_GetNextMsg(&m_loghnd, &msg, 2);
                has_msg = msg != Q_NULLPTR;
                if (msg) {
                    QSharedPointer<LtrSrvLogRec> rec = QSharedPointer<LtrSrvLogRec>(
                                new LtrSrvLogRec(msg));
                    if (!checkDateTime.isValid() || (rec->time() > checkDateTime)) {
                        recs.append(rec);
                        m_logrecs.append(rec);
                    }
                    LTRLOG_FreeMsg(msg);
                }
            } while (has_msg && (err==LTR_OK));

            if (!recs.isEmpty()) {
                Q_EMIT newLogRecords(recs);

                QList< QSharedPointer<LtrSrvLogRec> > remRecs;
                while (m_logrecs.size() >  log_max_recs) {
                    remRecs.append(m_logrecs.takeFirst());
                }
                Q_EMIT oldLogRecordsRemove(remRecs);
            }
        }

        /* если произошла ошибка при чтении журнала, возможно мы его не успевали
         * вычитывать и это не критическая ошибка.
         * закрываем соединение, чтобы при следующей попытке чтения журнала
         * его заново проинициализировать */
        if (err!=LTR_OK) {
            LTRLOG_Close(&m_loghnd);
        }
    }


    return err;
}

int LtrdConnection::ipEntryAdd(qint32 addr, qint32 flags, bool perm) {
    QMutexLocker lock(&m_mutex);
    return LTR_AddIPCrate(&hnd, addr, flags, perm ? 1 : 0 );
}

int LtrdConnection::ipEntryRem(qint32 addr, bool perm) {
    QMutexLocker lock(&m_mutex);
    return LTR_DeleteIPCrate(&hnd, addr, perm ? 1 : 0 );
}

int LtrdConnection::ipEntryFlagsChange(qint32 addr, qint32 flags, bool perm) {
    QMutexLocker lock(&m_mutex);
    return LTR_SetIPCrateFlags(&hnd, addr, flags, perm ? 1 : 0 );
}

int LtrdConnection::ipConnect(qint32 addr) {
    QMutexLocker lock(&m_mutex);
    return LTR_ConnectIPCrate(&hnd, addr);
}

int LtrdConnection::ipDisconnect(qint32 addr) {
    QMutexLocker lock(&m_mutex);
    return LTR_DisconnectIPCrate(&hnd, addr);
}

int LtrdConnection::ipConnectAuto() {
    QMutexLocker lock(&m_mutex);
    return LTR_ConnectAllAutoIPCrates(&hnd);
}

int LtrdConnection::ipDisconnectAll() {
    QMutexLocker lock(&m_mutex);
    return LTR_DisconnectAllIPCrates(&hnd);
}


void LtrdConnection::privCloseConnection() {
    QMutexLocker lock(&m_mutex);
    clearConnection();
    m_thread.quit();
}

void LtrdConnection::privStartConnection(bool restart) {


    if ((m_status != STATUS_CONNECTING) &&
            (m_status != STATUS_ONLINE)) {

        /* если был запланирован перезапуск - останавливаем его */
        if (m_restart_tmr_id) {
            killTimer(m_restart_tmr_id);
            m_restart_tmr_id = 0;
        }

        /* при автоматическом перезапуске не меняем статус - только
         * если пользователь перезапустил вручную */
        if (!restart) {
            m_status = STATUS_CONNECTING;
            Q_EMIT statusChanged(m_status);
        }
        err = LTR_Open(&hnd);
        if (err==LTR_OK)
            err = LTR_GetServerVersion(&hnd, (DWORD*)&m_vers);

        m_status = err==LTR_OK ? STATUS_ONLINE : STATUS_ERROR;
        Q_EMIT statusChanged(m_status);


        if (err==LTR_OK) {
            m_tmr_id = startTimer(200);
            /* при удачном открытии сбрасываем будущий таймаут на перезапуск
             * снова на мин. значение */
            m_cur_restart_tout = restart_tout_init;
            checkCrates();
            if (m_status == STATUS_ONLINE)
                readLog();
        } else {
            /* при ошибке планируем перезапуск через текущий таймаут */
            scheduleRestart();
        }
    }
}

void LtrdConnection::restartConnection() {
    /** @note сейчас использую аддитивную добавку, чтобы таймаут не увеличивался
     * слишком быстро. Возможно стоит использовать аддитивную для локального
     * соединения, а мультипликативную - для удаленного */

    if (m_cur_restart_tout < restart_tout_max) {
        m_cur_restart_tout += restart_tout_init;
    }

    privStartConnection(true);
}

void LtrdConnection::scheduleRestart() {
    /* перезапуск планируем только при ошибке соединения и
     * если он не был запланирован ранее */
    if ((m_status==STATUS_ERROR) && (m_restart_tmr_id==0)) {
        m_restart_tmr_id = startTimer(m_cur_restart_tout);
    }
}

void LtrdConnection::closeConnection() {
    Q_EMIT aboutToClose();    
}

void LtrdConnection::clearLog() {
    QMutexLocker locker(&m_logmutex);
    m_logrecs.clear();
}

LtrSrvLogRec::LtrSrvLogRec(const TLTRLOG_MSG *msg) {
    m_err = msg->Err;
    m_lvl = (en_LTR_LogLevel)msg->Lvl;
    m_text = QString::fromLocal8Bit(msg->Msg);
#if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
    m_time = QDateTime::fromMSecsSinceEpoch(msg->Time.QuadPart*1000);
#else
    m_time = QDateTime::fromTime_t((uint)msg->Time.QuadPart);
 #endif
}
