#include "ModbusCCMgr.h"
#include "ZTLog.h"
#include "EngLog.h"
#include "ModbusCCDefs.h"
#include <QDateTime>
#include "ZTData.h"
#include "GlobalDefine.h"

CModbusCCMgr::CModbusCCMgr(CModbusRepository *Repo, int ModbusPort, int DevID) :
    CModbusBackend(Repo)
{
    mModbusServer = new QTcpServer();
    mDeviceID = DevID;
    mModbusPort = ModbusPort;
    mZTWatchdogEnabled = true;

    connect(mModbusServer,SIGNAL(newConnection()),this,SLOT(NewModbusConnection()));
    mModbusMode = MODBUS_SLAVE_MODE;

    mZTWatchdogTimer = new QTimer;
    mZTWatchdogTimer->setSingleShot(false);
    mZTWatchdogTimer->setInterval(MODBUS_ZT_WATCHDOG_TIMEOUT);
    connect(mZTWatchdogTimer,SIGNAL(timeout()),this,SLOT(ModbusZTWatchdogTimeout()));

    mCCWatchdogTimer = new QTimer;
    mCCWatchdogTimer->setSingleShot(true);
    mCCWatchdogTimer->setInterval(MODBUS_CC_WATCHDOG_TIMEOUT);
    connect(mCCWatchdogTimer,SIGNAL(timeout()),this,SLOT(ModbusCCWatchdogTimeout()));

    mZTWatchdog = 0;
    mCCWatchdogState = 0;
    mCCLinkState = false;
    mLastDateTime = 0;

    ResetCCRepository();
}

CModbusCCMgr::~CModbusCCMgr()
{
    delete mModbusServer;

    delete mZTWatchdogTimer;
    delete mCCWatchdogTimer;
    if(mLastDateTime != 0)
    {
        delete mLastDateTime;
    }
}

int CModbusCCMgr::StartModbusCCServer()
{
    mModbusServer->listen(QHostAddress::Any,mModbusPort);
    CZTLog::instance()->AddLogString(QString("Serveur Modbus Commande Centralisée démarré sur le port %1").arg(mModbusPort),true);
    return 1;
}


bool CModbusCCMgr::CheckForCCPartner(const int msTimeout, const int port)
{
    QTcpServer *DummyServer = new QTcpServer();
    DummyServer->listen(QHostAddress::Any,port);

    if(DummyServer->waitForNewConnection(msTimeout) == true)
    {
        QTcpSocket* DummySocket = DummyServer->nextPendingConnection();
        DummySocket->disconnectFromHost();
        DummySocket->waitForDisconnected(1000);
        delete DummySocket;
        DummyServer->close();
        delete DummyServer;
        return true;
    }
    DummyServer->close();
    delete DummyServer;
    return false;
}

int CModbusCCMgr::CloseModbusCCServer()
{
    mModbusServer->close();
    if(mModbusTCPSocketHandle != 0)
    {
        mModbusTCPSocketHandle->flush();

        if(mModbusTCPSocketHandle->state() != QAbstractSocket::UnconnectedState)
        {
         //   qDebug("Disconnecting socket");
            mModbusTCPSocketHandle->disconnectFromHost();
            if(mModbusTCPSocketHandle->waitForDisconnected(1000) == false)
            {
           //     qDebug("Could not disconnect socket");
            }
        }
        mModbusTCPSocketHandle->close();
        delete mModbusTCPSocketHandle;
    }

    CZTLog::instance()->AddLogString(QString("Serveur Modbus Commande Centralisée fermé"),true);
    return 1;
}

void CModbusCCMgr::NewModbusConnection()
{
    mModbusTCPSocketHandle = mModbusServer->nextPendingConnection();
    if(mModbusTCPSocketHandle != 0)
    {
        mDataLinkValid = true;
        connect(mModbusTCPSocketHandle,SIGNAL(readyRead()),this,SLOT(ModbusDataReady()));
        connect(mModbusTCPSocketHandle,SIGNAL(disconnected()),this,SLOT(ConnectionLost()));
        //qDebug("ModbusCC: Connection with CC established");
        CZTLog::instance()->AddLogString(QString("Connection Modbus avec la CC établie. IP locale [%1] -  IP HPC [%2]").arg(mModbusTCPSocketHandle->localAddress().toString()).arg(mModbusTCPSocketHandle->peerAddress().toString()),true);
        mZTWatchdogTimer->start();
        mCCWatchdogTimer->start(MODBUS_CC_WATCHDOG_TIMEOUT*2);  //Allow twice the time for the first watchdog reset.
        mCCLinkState = false;
        mCCWatchdogState = 0xBEEF;

        emit ModbusCCConnected(mModbusTCPSocketHandle->localAddress().toIPv4Address(),mModbusTCPSocketHandle->peerAddress().toIPv4Address());
        //emit ModbusCCLinkRecovered();
    }
}

void CModbusCCMgr::ConnectionLost()
{
    ModbusLinkDisconnected();
    mZTWatchdogTimer->stop();
    mCCWatchdogTimer->stop();
    emit ModbusCCDisconnected();
    emit ModbusCCLinkLost();
    ResetCCRepository();
    CZTLog::instance()->AddLogString(QString("Connection Modbus (Ethernet) avec la CC rompue."),true);
}

bool CModbusCCMgr::IsModbusConnected()
{
    return mDataLinkValid;
}

void CModbusCCMgr::RegistersDatabaseUpdated(quint16 StartAddress, quint16 Length)
{
    Q_UNUSED(StartAddress)
    Q_UNUSED(Length)


    qint16 WD = mModbusRepo->GetSingleReg(MODBUS_CC_WATCHDOG_BASE_REG_ADD);
  //  qDebug("WD = %d",WD);
    if(WD != mCCWatchdogState)
    {
        mCCWatchdogTimer->start(MODBUS_CC_WATCHDOG_TIMEOUT);  //The watchdog has toggled.  Kick the timer.
        mCCWatchdogState = WD;
        if(mCCLinkState == false)
        {
            mCCLinkState = true;
            emit ModbusCCLinkRecovered();
            CZTLog::instance()->AddLogString("Lien de communication (Watchdog) avec la CC rétabli",true);
        }
     }

    if(mCCLinkState == true) //Ignore incoming data if the link is not healty (Watchdog).
    {
        qint16 NTPWrite = mModbusRepo->GetSingleReg(MODBUS_CC_CLK_UPDATE_BASE_REG_ADD);
        if((NTPWrite & MODBUS_CC_CLK_UPDATE_FLAG_MASK) != 0)
        {
            //Update date & time...
            bool OK;
            QList<qint16> DateTime = mModbusRepo->GetRegs(MODUBS_CC_CLK_SEC_BASE_REG_ADD,4,&OK);
            if(OK)
            {
                qDebug("Registres: [%d][%d][%d][%d]",DateTime.at(0),DateTime.at(1),DateTime.at(2),DateTime.at(3));
                quint8 Secs, Minutes, Hours, Month, Day;
                quint8 temp;
                temp = (quint8)(DateTime.at(0) >> 8);
                Secs = BCDToDecByte(temp);

                temp = (quint8)(DateTime.at(1) & 0x00FF);
                Minutes = BCDToDecByte(temp);

                temp = (qint8)(DateTime.at(1) >> 8);
                Hours = BCDToDecByte(temp);

                temp = (quint8)(DateTime.at(2) & 0x00FF);
                Day = BCDToDecByte(temp);

                temp = (quint8)(DateTime.at(2) >> 8);
                Month = BCDToDecByte(temp);

                quint16 Year = DateTime.at(3);
                Year = BCDToDecWord(Year);

                qDebug("Secs [%d], Minutes[%d], Hours[%d], Month[%d], Day[%d], Year[%d]",Secs,Minutes,Hours,Month,Day,Year);

                QDateTime NetworkTime;
                NetworkTime.setTimeSpec(Qt::UTC);
                NetworkTime.setDate(QDate(Year,Month,Day));
                NetworkTime.setTime(QTime(Hours,Minutes,Secs));
               // QDateTime MyTime = NetworkTime.toLocalTime();

                if(mLastDateTime != 0)
                {
                    delete mLastDateTime;
                }
                mLastDateTime = new QDateTime(NetworkTime.toLocalTime());   //The QDateTime object will be deleted by the slot function...
                emit ModbusDateTimeReceived(mLastDateTime);

                qDebug("Date & Heure reçue du SACL: %s",mLastDateTime->toString("yyyy-MM-dd hh:mm:ss").toAscii().data());
                mModbusRepo->WriteSingleReg(MODBUS_CC_CLK_UPDATE_BASE_REG_ADD,0);
            }

        }


        emit RepoHasChanged();


#ifdef COMPILE_MODBUS_TEST_MODE
        static quint16 VehTypeZT1 = 1024, VehTypeZT2 = 1024;
        qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_ZT1_TRAIN_TYPE_REG_ADD);
        if(VehTypeZT1 != Reg)
        {
            VehTypeZT1 = Reg;
            qDebug("Type de train ZT1 = %d",VehTypeZT1);
        }
        Reg = mModbusRepo->GetSingleReg(MODBUS_CC_ZT2_TRAIN_TYPE_REG_ADD);
        if(VehTypeZT2 != Reg)
        {
            VehTypeZT2 = Reg;
            qDebug("Type de train ZT2 = %d",VehTypeZT2);
        }

#endif

    }


}

quint8 CModbusCCMgr::BCDToDecByte(const quint8 byte)
{
    quint8 out = 0;
    quint8 temp = 0;

    out += byte & 0x0F;
    temp = (byte >> 4) & 0x0F;
    out += (temp * 10);

    return out;

}

quint16 CModbusCCMgr::BCDToDecWord(const quint16 word)
{
    quint16 out = 0;
    quint16 temp = 0;

    out += word & 0x000F;
    temp = (word >> 4) & 0x000F;
    out += (temp * 10);
    temp = (word >> 8) & 0x000F;
    out += (temp * 100);
    temp = (word >> 12) & 0x000F;
    out += (temp * 1000);

    return out;

}

void CModbusCCMgr::ModbusRequestException(quint8 ExceptionCode, quint8 FctCode)
{
    //qDebug("Modbus CC: Request exception occured.  ExceptCode: [%d], FctCode: [%d]",ExceptionCode,FctCode);
    CEngLog::instance()->AddLogString(QString("Modbus CC: Exception de requête.  ExceptionCode[%1], FctCode[%2]").arg(ExceptionCode).arg(FctCode));
}

void CModbusCCMgr::ModbusCCWatchdogTimeout()
{
    CZTLog::instance()->AddLogString("Perte du lien de communication avec la CC: Watchdog CC Expiré",true);
    ResetCCRepository();
//    emit ModbusCCDisconnected();
    emit ModbusCCLinkLost();
    mCCLinkState = false;
    mCCWatchdogState = 0xBEEF;
}

void CModbusCCMgr::ModbusZTWatchdogTimeout()
{

    if(mZTWatchdogEnabled)
    {
        quint16 WD = mZTWatchdog;
      //  qDebug("WD = 0x%x",WD);
        mModbusRepo->WriteSingleReg(MODBUS_ZT_WATCHDOG_REG_ADD,WD);
        if(mZTWatchdog == ZT_WATCHDOG_ON_VALUE)
        {
            mZTWatchdog = ZT_WATCHDOG_OFF_VALUE;
        }
        else
        {
            mZTWatchdog = ZT_WATCHDOG_ON_VALUE;
        }
    }
}

int CModbusCCMgr::ResetCCRepository()
{
    //Clear read registers
    mModbusRepo->WriteSingleReg(MODBUS_CC_WATCHDOG_BASE_REG_ADD,0);
    mModbusRepo->WriteSingleReg(MODBUS_CC_AN_BASE_REG_ADD,0);
    mModbusRepo->WriteSingleReg(MODBUS_CC_ZT1_TRAIN_TYPE_REG_ADD, MODBUS_CC_TRAIN_TYPE_INVALID_SACL_OFFLINE);
    mModbusRepo->WriteSingleReg(MODBUS_CC_ZT2_TRAIN_TYPE_REG_ADD, MODBUS_CC_TRAIN_TYPE_INVALID_SACL_OFFLINE);
    QList<qint16> ResetValues;
    ResetValues << 0 << 0 << 0  //Train compo ZT1
                << 0 << 0 << 0  //Train compo ZT2
                << 0 << 0 << 0 << 0 << 0;   //Date & Time update registers

    mModbusRepo->WriteMultipleRegs(MODBUS_CC_ZT1_TRAIN_ID_1_REG_ADD,ResetValues);

    //Do not clear the TK (write) regisers, they will clear themselves when needed.
    return RET_OK;
}

bool CModbusCCMgr::GetZT1InhibitionFlag()
{
    bool OK = false;

    if(mCCLinkState == false)//Ignore registers data if link is with SACL is not healthy
    {
        return false;
    }

    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_AN_BASE_REG_ADD,&OK);

    if(OK)
    {
        if((Reg & MODBUS_CC_INHIBIT_ZT1_FLAG_MASK) != 0)
        {
            return true;
        }
    }

    return false;
}

int CModbusCCMgr::ClearZT1InhibitionFlag()
{
    bool OK = false;
    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_AN_BASE_REG_ADD,&OK);

    if(OK)
    {
        Reg &= ~MODBUS_CC_INHIBIT_ZT1_FLAG_MASK;
        mModbusRepo->WriteSingleReg(MODBUS_CC_AN_BASE_REG_ADD,Reg);
        return RET_OK;

    }
    return RET_ERROR;
}

bool CModbusCCMgr::GetZT2InhibitionFlag()
{
    bool OK = false;

    if(mCCLinkState == false)//Ignore registers data if link is with SACL is not healthy
    {
        return false;
    }

    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_AN_BASE_REG_ADD,&OK);

    if(OK)
    {
        if((Reg & MODBUS_CC_INHIBIT_ZT2_FLAG_MASK) != 0)
        {
            return true;
        }
    }

    return false;
}
int CModbusCCMgr::ClearZT2InhibitionFlag()
{
    bool OK = false;
    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_AN_BASE_REG_ADD,&OK);

    if(OK)
    {
        Reg &= ~MODBUS_CC_INHIBIT_ZT2_FLAG_MASK;
        mModbusRepo->WriteSingleReg(MODBUS_CC_AN_BASE_REG_ADD,Reg);
        return RET_OK;

    }
    return RET_ERROR;
}

QList<qint16> CModbusCCMgr::GetZT1TrainComposition()
{
    bool OK = false;
    QList<qint16> Compo;
    Compo = mModbusRepo->GetRegs(MODBUS_CC_ZT1_TRAIN_ID_1_REG_ADD,3,&OK);

    if(OK == false || mCCLinkState == false) //Ignore registers data if link is with SACL is not healthy
    {
        Compo << 0 << 0 << 0;
    }

    return Compo;
}

QList<qint16> CModbusCCMgr::GetZT2TrainComposition()
{
    bool OK = false;
    QList<qint16> Compo;
    Compo = mModbusRepo->GetRegs(MODBUS_CC_ZT2_TRAIN_ID_1_REG_ADD,3,&OK);

    if(OK == false || mCCLinkState == false)//Ignore registers data if link is with SACL is not healthy
    {
        Compo << 0 << 0 << 0;
    }

    return Compo;
}


qint16 CModbusCCMgr::GetZT1TrainType()
{
    bool OK = false;
    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_ZT1_TRAIN_TYPE_REG_ADD,&OK);
    int Type = TRAIN_TYPE_UNKNOWN;
    if(OK)
    {
        if(Reg == MODBUS_CC_TRAIN_TYPE_MR63)
        {
            Type = TRAIN_TYPE_MR63;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MR73)
        {
            Type = TRAIN_TYPE_MR73;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MPM10)
        {
            Type = TRAIN_TYPE_MPM10;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MAINTENANCE)
        {
            Type = TRAIN_TYPE_MAINTENANCE_VEHICLE;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_INVALID_SACL_OFFLINE)
        {
           Type = TRAIN_TYPE_UNKNOWN;
        }
        else
        {
            CEngLog::instance()->AddLogString(QString("MODBUS: Réception d'un type de train ZT1 invalide du SACL [%1]").arg(Reg));
            Type = TRAIN_TYPE_UNKNOWN;
        }
    }
    if(mCCLinkState == false)//Ignore registers data if link is with SACL is not healthy
    {
        Type = TRAIN_TYPE_UNKNOWN;
    }
    return Type;
}

qint16 CModbusCCMgr::GetZT1RawTrainType()
{
    bool OK = false;
    return mModbusRepo->GetSingleReg(MODBUS_CC_ZT1_TRAIN_TYPE_REG_ADD,&OK);
}
qint16 CModbusCCMgr::GetZT2RawTrainType()
{
    bool OK = false;
    return mModbusRepo->GetSingleReg(MODBUS_CC_ZT2_TRAIN_TYPE_REG_ADD,&OK);
}

qint16 CModbusCCMgr::GetZT2TrainType()
{
    bool OK = false;
    qint16 Reg = mModbusRepo->GetSingleReg(MODBUS_CC_ZT2_TRAIN_TYPE_REG_ADD,&OK);
    int Type = TRAIN_TYPE_UNKNOWN;
    if(OK)
    {
        if(Reg == MODBUS_CC_TRAIN_TYPE_MR63)
        {
            Type = TRAIN_TYPE_MR63;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MR73)
        {
            Type = TRAIN_TYPE_MR73;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MPM10)
        {
            Type = TRAIN_TYPE_MPM10;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_MAINTENANCE)
        {
            Type = TRAIN_TYPE_MAINTENANCE_VEHICLE;
        }
        else if(Reg == MODBUS_CC_TRAIN_TYPE_INVALID_SACL_OFFLINE)
        {
           Type = TRAIN_TYPE_UNKNOWN;
        }
        else
        {
            CEngLog::instance()->AddLogString(QString("MODBUS: Réception d'un type de train ZT2 invalide du SACL [%1]").arg(Reg));
            Type = TRAIN_TYPE_UNKNOWN;
        }
    }

    if(mCCLinkState == false)   //Ignore registers data if link is with SACL is not healthy
    {
        Type = TRAIN_TYPE_UNKNOWN;
    }
    return Type;
}

int CModbusCCMgr::SetZTWatchdogEnabled(bool Enabled)
{
    mZTWatchdogEnabled = Enabled;
    return RET_OK;
}

int CModbusCCMgr::SetActivatedITI(int ITI)
{
    bool OK = false;
    quint16 Reg = mModbusRepo->GetSingleReg(MODBUS_MISC_DATA_BASE_REG_ADD,&OK);
    if(OK == true)
    {
        if(ITI == ZT_SECONDARY_ITI)
        {
            Reg |= ZT_ACTIVATED_ITI_FLAG_MASK;
        }
        else
        {
            Reg &= ~ZT_ACTIVATED_ITI_FLAG_MASK;
        }

        return mModbusRepo->WriteSingleReg(MODBUS_MISC_DATA_BASE_REG_ADD,Reg);
    }
    else
    {
        return RET_ERROR;
    }
}
