#include "ModbusSEIMgr.h"
#include <QTcpSocket>
#include "ModbusCCDefs.h"
#include "ModbusSEIDefs.h"
#include "GlobalDefine.h"
#include <ZTLog.h>

CModbusSEIMgr::CModbusSEIMgr(CModbusRepository *SEIRepo, CModbusRepository *CCRepo, QHostAddress ServerIP, int ModbusPort, int DevID):
    CModbusBackend(SEIRepo)
{
   // connect(mModbusTCPSocketHandle,SIGNAL(readyRead()),this,SLOT(ModbusDataReady()));
    mModbusMode = MODBUS_MASTER_MODE;
    mModbusPort = ModbusPort;
    mDeviceID = DevID;
    mSEIIPAddress = ServerIP;

    mCCRepoHandle = CCRepo;

    mZTWatchdog = false;

    mConnectionTimer = new QTimer();
    mConnectionTimer->setInterval(SEI_CLIENT_RECONNECT_TIMEOUT);
    mConnectionTimer->setSingleShot(false);
    mConnectionTimer->stop();

    mSEIModbusUpdateTimer = new QTimer();
    mSEIModbusUpdateTimer->setInterval(1000);
    mSEIModbusUpdateTimer->setSingleShot(true);
    mSEIModbusUpdateTimer->stop();

    mSEIWatchdogTimer = new QTimer();
    mSEIWatchdogTimer->setInterval(SEI_MODBUS_WATCHDOG_TIMEOUT);
    mSEIWatchdogTimer->setSingleShot(true);
    mSEIWatchdogTimer->stop();


    mSEILinkState = false;
    mSEIWatchdogState = 0;


    mModbusTCPSocketHandle = new QTcpSocket();
    connect(mModbusTCPSocketHandle,SIGNAL(readyRead()),this,SLOT(ModbusDataReady()));
    connect(mModbusTCPSocketHandle,SIGNAL(disconnected()),this,SLOT(SocketDisconnected()));
    connect(mModbusTCPSocketHandle,SIGNAL(connected()),this,SLOT(SocketConnected()));
    connect(mConnectionTimer,SIGNAL(timeout()),this,SLOT(ConnectionTimerExpired()));
    connect(mSEIModbusUpdateTimer,SIGNAL(timeout()),this,SLOT(SEIModbusUpdateTimerExpired()));
    connect(mSEIWatchdogTimer,SIGNAL(timeout()),this,SLOT(SEIModbusWatchdogtimerExpired()));
}

CModbusSEIMgr::~CModbusSEIMgr()
{
    if(mModbusTCPSocketHandle->state() != QAbstractSocket::UnconnectedState)
    {
        mModbusTCPSocketHandle->disconnectFromHost();
        mModbusTCPSocketHandle->waitForDisconnected(1000);
    }
    delete mModbusTCPSocketHandle;

    if(mConnectionTimer)
        delete mConnectionTimer;
    if(mSEIModbusUpdateTimer)
        delete mSEIModbusUpdateTimer;
    if(mSEIWatchdogTimer)
        delete mSEIWatchdogTimer;
}

int CModbusSEIMgr::StartSEICommunication()
{
    mConnectionTimer->start();
    return RET_OK;
}

int CModbusSEIMgr::ConnectToSlave(QHostAddress SlaveIP, int SlavePort)
{
    if(mModbusTCPSocketHandle->state() != QAbstractSocket::UnconnectedState)
    {
        return RET_ERROR;
    }

    mModbusTCPSocketHandle->connectToHost(SlaveIP,SlavePort);

    return RET_OK;
}

int CModbusSEIMgr::DisconnectFromSlave()
{
    mModbusTCPSocketHandle->flush();
    mModbusTCPSocketHandle->disconnectFromHost();
    mConnectionTimer->start();

    return 1;
}

void CModbusSEIMgr::SocketConnected()
{
    emit ModbusMasterConnected(mModbusTCPSocketHandle->localAddress().toIPv4Address(),mModbusTCPSocketHandle->peerAddress().toIPv4Address());
   // qDebug("Master: Connection established with NetTrac");
  //  CZTLog::instance()->AddLogString("Connection Modbus (Ethernet) avec NetTrac établi",true);
    CZTLog::instance()->AddLogString(QString("Connection Modbus (Ethernet) avec la NetTrac établie. IP locale [%1] -  IP NetTrac [%2]").arg(mModbusTCPSocketHandle->localAddress().toString()).arg(mModbusTCPSocketHandle->peerAddress().toString()),true);
    mConnectionTimer->stop();
    mSEIModbusUpdateTimer->start();
    mSEIWatchdogTimer->start(SEI_MODBUS_WATCHDOG_TIMEOUT*2);    //allow twice the time for the first watchdog to come in
}

void CModbusSEIMgr::SocketDisconnected()
{
    ModbusLinkDisconnected();
    emit ModbusMasterDisconnected();
    emit SEIModbusLinkLost();
    mConnectionTimer->start();
    mSEIModbusUpdateTimer->stop();
    mSEILinkState = false;
    mSEIWatchdogState = 0xBEEF;
    CZTLog::instance()->AddLogString("Connection Modbus (Ethernet) avec NetTrac rompue",true);
}

int CModbusSEIMgr::ReadModbusRegisters()
{
    //return SendReadHoldingRegistersRequest(ZT_DATA_BASE_REGISTER_ADDRESS,4);    //Read all 3 registers from ZT (2000 - 2003)
    return RET_OK;
}

void CModbusSEIMgr::ConnectionTimerExpired()
{
    //Establish connection only if not connected
    if(mModbusTCPSocketHandle->state() == QAbstractSocket::UnconnectedState)
    {
        ConnectToSlave(mSEIIPAddress,mModbusPort);
    }
}

int CModbusSEIMgr::SEISettingsChanged(QHostAddress ServerIP, int ModbusPort, int DevID)
{
    if(DevID > 255)
    {
        return RET_ERROR;
    }


    mSEIIPAddress = ServerIP;
    mModbusPort = ModbusPort;
    mDeviceID = DevID;

    //Make shure we are disconnected.  Connection timer will do the reconnection.
    DisconnectFromSlave();

    return RET_OK;

}

void CModbusSEIMgr::SEIModbusUpdateTimerExpired()
{

    //Read the SEI data...
    SendReadHoldingRegistersRequest(SEI_MODBUS_SEI_DATA_BASE_REG,SEI_MODBUS_SEI_TABLE_DATA_SIZE);
}

int CModbusSEIMgr::SendZTRegistersToSEI()
{
    //The SEI Modbus table is updated on event, triggered by the TKTransport object (see ZTTKUpdated()).
    //Just update the watchdog and send the current table.
    mZTWatchdog = !mZTWatchdog;

    if(mZTWatchdog)
    {
        mModbusRepo->WriteSingleReg(SEI_MODBUS_ZT_WATCHDOG_REG,0x01);
    }
    else
    {
         mModbusRepo->WriteSingleReg(SEI_MODBUS_ZT_WATCHDOG_REG,0);
    }

    SendWriteHoldingRegistersRequest(SEI_MODBUS_ZT_DATA_BASE_REG,SEI_MODBUS_ZT_TABLE_DATA_SIZE);

    return RET_OK;
}


//This signal is emitted by TKTransport when a changed happened in the TK after the CC modbus table has been updated.
//We just need to copy the relevant info from the CC repo...
//As per specification, we must also send an update on change to the SEI
void CModbusSEIMgr::ZTTKUpdated()
{
    //Copy data from CC modbus table to SEI modbus table.
    mModbusRepo->WriteHRData(SEI_MODBUS_ZT_DATA_BASE_REG,SEI_MODBUS_ZT_TABLE_DATA_SIZE,mCCRepoHandle->GetHRData(MODBUS_ZT_DATA_BASE_REG,SEI_MODBUS_ZT_TABLE_DATA_SIZE));

    //Keep our actual watchdog value..
    if(mZTWatchdog)
    {
        mModbusRepo->WriteSingleReg(SEI_MODBUS_ZT_WATCHDOG_REG,0x01);
    }
    else
    {
         mModbusRepo->WriteSingleReg(SEI_MODBUS_ZT_WATCHDOG_REG,0);
    }

    //Send the data now to NetTrac
    SendWriteHoldingRegistersRequest(SEI_MODBUS_ZT_DATA_BASE_REG,SEI_MODBUS_ZT_TABLE_DATA_SIZE);

}

void CModbusSEIMgr::SEIModbusWatchdogtimerExpired()
{
    CZTLog::instance()->AddLogString("Perte du lien de communication avec la NetTrac: Watchdog NetTrac Expiré",true);

    emit SEIModbusLinkLost();
    mSEILinkState = false;
    DisconnectFromSlave(); //JFM 2021-03-12.  Ajout suite aux essais site.  La connexion doit etre coupee manuellement pour les raisons obscures.
   // mSEIWatchdogState = 0;
}

void CModbusSEIMgr::RegistersDatabaseUpdated(quint16 StartAddress, quint16 Length)
{
    Q_UNUSED(StartAddress)
    Q_UNUSED(Length)
    static bool FirstUpdate = true;


    qint16 WD = mModbusRepo->GetSingleReg(SEI_MODBUS_SEI_WATCHDOG_REG);
    WD = (WD & SEI_MODBUS_SEI_WATCHDOG_MASK);
//    qDebug("WD = %d",WD);
    if(FirstUpdate)
    {
        mSEIWatchdogState = WD;
        FirstUpdate = false;
    }
    else if(WD != mSEIWatchdogState)
    {
        mSEIWatchdogTimer->start(SEI_MODBUS_WATCHDOG_TIMEOUT);  //The watchdog has toggled.  Kick the timer.
        mSEIWatchdogState = WD;
        if(mSEILinkState == false)
        {
            mSEILinkState = true;
            emit SEIModbusLinkRecovered();
            CZTLog::instance()->AddLogString("Lien de communication (Watchdog) avec NetTrac rétabli",true);
        }
    }

    if(mSEILinkState == true)   //only consider data when link is healthy
    {
        bool OK;
        qint16 AlarmsReg = mModbusRepo->GetSingleReg(SEI_MODBUS_SEI_ALARMS_RESET_REG,&OK);
        bool ZT1Clear, ZT2Clear;

        if(OK)
        {
            ZT1Clear = (AlarmsReg & SEI_MODBUS_SEI_ZT1_ALARM_RESET_MASK) != 0;

            ZT2Clear = (AlarmsReg & SEI_MODBUS_SEI_ZT2_ALARM_RESET_MASK) != 0;

            emit SEIAlarmClearUpdate(ZT1Clear,ZT2Clear);
        }
        else
        {
            CEngLog::instance()->AddLogString(qPrintable(QString("Erreur de logique dans ModbusSEIMgr::RegistersDatabaseUpdated, impossible de lire la table modbus à l'adresse %1").arg(SEI_MODBUS_SEI_ALARMS_RESET_REG)));
        }
    }

    SendZTRegistersToSEI();


}

void CModbusSEIMgr::ModbusResponseException(quint8 ExceptionCode, quint8 FctCode)
{
    qDebug("Modbus MASTER exception: code:%d  Fct:%d",ExceptionCode,FctCode);
}

void CModbusSEIMgr::ModbusWriteMultipleRegsSuccess()
{
    if(mModbusTCPSocketHandle->state() == QAbstractSocket::ConnectedState)
    {
        mSEIModbusUpdateTimer->start();
    }
}

void CModbusSEIMgr::ModbusWriteSingleRegsSuccess()
{
    if(mModbusTCPSocketHandle->state() == QAbstractSocket::ConnectedState)
    {
        mSEIModbusUpdateTimer->start();
    }
}

void CModbusSEIMgr::ModbusRequestMaxRetryReached()
{
    //The server did not respond to last request...  Retry...
    if(mModbusTCPSocketHandle->state() == QAbstractSocket::ConnectedState)
    {
        mSEIModbusUpdateTimer->start();
    }
}
