#include "NetworkManager.h"
#include <QDebug>
#include "OutilZT.h"

//CNetworkManager CNetworkManager::mSingleton;

CNetworkManager::CNetworkManager(QObject *parent)
{
    mConfig = 0;
    mCurZTID = 0;
    mProgramHandle = 0;
    mZTUDPSocket = 0;
    mUDPServerAdress = QHostAddress::Null;

    mNetworkSMState = NETWORK_SM_STANDBY_STATE;
    mOnlineCheckNetworkSMState = NETWORK_ONLINE_CHK_SM_STANDBY_STATE;

 //   mZTTcpSocket.setSocketOption(QAbstractSocket::LowDelayOption,1);
    connect(&mZTTcpSocket,SIGNAL(connected()),this,SLOT(SocketConnected()));
    connect(&mZTTcpSocket,SIGNAL(disconnected()),this,SLOT(SocketDisconnected()));
    connect(&mZTTcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(SocketError(QAbstractSocket::SocketError)));
    connect(&mZTTcpSocket,SIGNAL(readyRead()),this,SLOT(SocketDataReady()));

    mZTOnlineCheckingSocket.setSocketOption(QAbstractSocket::LowDelayOption,1);
    connect(&mZTOnlineCheckingSocket,SIGNAL(connected()),this,SLOT(OnlineCheckSocketConnected()));
    connect(&mZTOnlineCheckingSocket,SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(OnlineCheckSocketError(QAbstractSocket::SocketError)));

}
CNetworkManager::~CNetworkManager()
{
    mZTTcpSocket.disconnectFromHost();
    mZTOnlineCheckingSocket.disconnectFromHost();
}

void CNetworkManager::BindPointers(COutilZT *ProgramHandle)
{
    mProgramHandle = ProgramHandle;
}

void CNetworkManager::OnlineCheckStateMachine(eNetworkSMEvent event)
{
    switch(mOnlineCheckNetworkSMState)
    {
        case NETWORK_ONLINE_CHK_SM_INIT_STATE:
        {
            break;
        }
        case NETWORK_ONLINE_CHK_SM_STANDBY_STATE:
        {
            break;
        }
        case NETWORK_ONLINE_CHK_SM_CHECK_CONNECTIONS_STATE:
        {
            switch(event)
            {
                case NETWORK_SM_SOCKET_CONNECTED_EVENT:
                {
//                    qDebug("%s Connected",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    emit NetworkZTConnectionStatus(mOnlineCheckZTID,ZT_NETWORK_ONLINE_STATUS);
                    break;
                }
                case NETWORK_SM_SOCKET_ERROR_EVENT:
                {
//                    qDebug("%s Not connected",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    emit NetworkZTConnectionStatus(mOnlineCheckZTID,ZT_NETWORK_OFFLINE_STATUS);
                    break;
                }
                case NETWORK_SM_SOCKET_DISCONNECTED_EVENT:
                {
                    //Nothing to do here but we need to manage it because it will be received each time we disconnect.
                    return;
                    break;
                }
                default:
                {
//                    qDebug("Unmanaged event in NETWORK_ONLINE_CHK_SM_CHECK_CONNECTIONS_STATE : %d",event);
                    return;
                    break;
                }
            }

            mOnlineCheckZTID++;
            if(mNetworkSMState != NETWORK_SM_STANDBY_STATE)
            {
                //Skip the connected ZT if there is one.
                if(mOnlineCheckZTID == mCurZTID)
                {
                    mOnlineCheckZTID++;
                }
            }
            if(mOnlineCheckZTID >= NB_ZT_ID)
            {
                mOnlineCheckNetworkSMState = NETWORK_ONLINE_CHK_SM_STANDBY_STATE;
                mZTOnlineCheckingSocket.disconnectFromHost();
            }
            else
            {
                mZTOnlineCheckingSocket.disconnectFromHost();
//                qDebug("Attempting to connect to %s",mConfig->mNetworkCfgList.at(mOnlineCheckZTID).mIPAdress.toAscii().data());
                emit NetworkZTConnectionStatus(mOnlineCheckZTID,ZT_NETWORK_SEARCHING_STATUS);
                mZTOnlineCheckingSocket.connectToHost(mConfig->mNetworkCfgList.at(mOnlineCheckZTID).mIPAdress,ZT_TCP_PORT);
            }
            break;
        }
    }
}

void CNetworkManager::NetworkStateMachine(eNetworkSMEvent event)
{
    switch(mNetworkSMState)
    {
        case NETWORK_SM_INIT_STATE:
        {
            break;
        }
        case NETWORK_SM_STANDBY_STATE:
        {
            break;
        }
        case NETWORK_SM_INITIATE_CLIENT_CONNECTION_STATE:
        {
            switch(event)
            {
                case NETWORK_SM_SOCKET_CONNECTED_EVENT:
                {
                    //qDebug("%s Connected",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    emit NetworkZTConnected(mCurZTID);
                    emit NetworkZTConnectionStatus(mCurZTID,ZT_NETWORK_CONNECTED_STATUS);

                    //RequestHeartbeat();
                    //mZTTcpSocket.write(GetStationNameReqPacket());
                    TCPTxRequest(GetStationNameReqPacket());
                    mNetworkSMState = NETWORK_SM_CLIENT_CONNECTED_STATE;
                    break;
                }
                case NETWORK_SM_SOCKET_ERROR_EVENT:
                {
//                    qDebug("%s Socket error",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    emit NetworkZTConnectionError(mCurZTID);
                    mNetworkSMState = NETWORK_SM_STANDBY_STATE;
                    break;
                }
                case NETWORK_SM_SOCKET_DISCONNECTED_EVENT:
                {
                    mNetworkSMState = NETWORK_SM_STANDBY_STATE;
                    emit NetworkZTConnectionStatus(mOnlineCheckZTID,ZT_NETWORK_UNKNOWN_STATUS);
                    emit NetworkZTConnectionError(mCurZTID);
                    break;
                }
                default:
                {
                    mNetworkSMState = NETWORK_SM_STANDBY_STATE;
                    qDebug("Invalid event in NETWORK_SM_INITIATE_CLIENT_CONNECTION_STATE : %d",event);
                    break;
                }
            }
            break;
        }
        case NETWORK_SM_CLIENT_CONNECTED_STATE:
        {
            switch(event)
            {
                case NETWORK_SM_SOCKET_ERROR_EVENT:
                {
//                    qDebug("%s Socket error",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    break;
                }
                case NETWORK_SM_SOCKET_DISCONNECTED_EVENT:
                {
                    emit NetworkZTDisconnected(mCurZTID);
                    emit NetworkZTConnectionStatus(mCurZTID,ZT_NETWORK_UNKNOWN_STATUS);
                    mNetworkSMState = NETWORK_SM_STANDBY_STATE;
                    //qDebug("%s Disconnected",mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());
                    qDebug("TCP Socket disconnected");
                    break;
                }
                default:
                {
                    qDebug("Unmanaged event in NETWORK_SM_CLIENT_CONNECTED_STATE : %d",event);
                    break;
                }
            }
            break;
        }
        default:
        {
            break;
        }
    }
}

unsigned int CNetworkManager::InitUDPConnection(int ZTID)
{
    mZTUDPSocket = new QUdpSocket(this);
    if(mZTUDPSocket->bind(QHostAddress::Any,ZT_UDP_PORT) == false)
        return RET_ERROR;

    mUDPServerAdress = QHostAddress(mConfig->mNetworkCfgList.at(ZTID).mIPAdress);

    connect(mZTUDPSocket,SIGNAL(readyRead()),this,SLOT(UDPSocketDataReady()));

    QByteArray packet = GetInitUDPConnectionPacket();
    mZTUDPSocket->writeDatagram(packet,mUDPServerAdress,ZT_UDP_PORT);

    return RET_OK;
}

unsigned int CNetworkManager::StopUDPStream()
{
    if(mZTUDPSocket == 0)
        return RET_ERROR;


    QByteArray packet = GetCloseUDPConnectionPacket();
    mZTUDPSocket->writeDatagram(packet,mUDPServerAdress,ZT_UDP_PORT);
    mZTUDPSocket->waitForBytesWritten();

    return RET_OK;
}

unsigned int CNetworkManager::CloseUDPConnection()
{
    if(mZTUDPSocket == 0)
        return RET_ERROR;


//    QByteArray packet = GetCloseUDPConnectionPacket();
//    mZTUDPSocket->writeDatagram(packet,mUDPServerAdress,ZT_UDP_PORT);
//    mZTUDPSocket->flush();

    delete mZTUDPSocket;
    mZTUDPSocket = 0;
    mUDPServerAdress = QHostAddress::Null;

    return RET_OK;
}

int CNetworkManager::CheckZTConnectionsStatus()
{
    if(mOnlineCheckNetworkSMState != NETWORK_ONLINE_CHK_SM_STANDBY_STATE)
        return RET_ERROR;

    mOnlineCheckZTID = 0;
    mOnlineCheckNetworkSMState = NETWORK_ONLINE_CHK_SM_CHECK_CONNECTIONS_STATE;

    emit NetworkZTConnectionStatus(mOnlineCheckZTID,ZT_NETWORK_SEARCHING_STATUS);
//    qDebug("Attempting to connect to %s",mConfig->mNetworkCfgList.at(mOnlineCheckZTID).mIPAdress.toAscii().data());
    mZTOnlineCheckingSocket.connectToHost(mConfig->mNetworkCfgList.at(mOnlineCheckZTID).mIPAdress,ZT_TCP_PORT);

    return RET_OK;
}


unsigned int CNetworkManager::SetConfig(CSettingsData *Config)
{
    mConfig = Config;
    return RET_OK;
}

unsigned int CNetworkManager::ConnectToZT(int ZTID)
{
    if(ZTID >= NB_ZT_ID)
    {
        qDebug("Cannot connect: Invalid ZT ID");
        return RET_ERROR;
    }

    if(mNetworkSMState != NETWORK_SM_STANDBY_STATE)
    {
        qDebug("Cannot connect: Invalid State machine state : %d",mNetworkSMState);
        return RET_ERROR;
    }


    mCurZTID = ZTID;
    mNetworkSMState = NETWORK_SM_INITIATE_CLIENT_CONNECTION_STATE;
    mZTTcpSocket.connectToHost(mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress,ZT_TCP_PORT);
//    qDebug("Connecting to %s", mConfig->mNetworkCfgList.at(mCurZTID).mIPAdress.toAscii().data());

    return RET_OK;
}

unsigned int CNetworkManager::DisconnectZT()
{
//    if(mNetworkSMState == NETWORK_SM_STANDBY_STATE)
//        return RET_ERROR;

    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
    {
        qDebug("Aborting connection from ZT.  Socket state : %d",mZTTcpSocket.state());
        mZTTcpSocket.abort();
        NetworkStateMachine(NETWORK_SM_SOCKET_DISCONNECTED_EVENT);
    }
    else
    {
        qDebug("Disconnecting from ZT.  Socket state : %d",mZTTcpSocket.state());
        mZTTcpSocket.disconnectFromHost();
    }

    return RET_OK;
}

void CNetworkManager::TCPTxRequest(const QByteArray &request)
{
    qDebug() << "Tx request in Network Manager : " << request.size() << " bytes : " << request.toHex();
    emit NetworkTCPTx();
    mZTTcpSocket.write(request);
}

unsigned int CNetworkManager::RefreshRemoteZTStatus()
{
    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
        return RET_ERROR;

    TCPTxRequest(GetZTStatusReqPacket());
    return RET_OK;
}

unsigned int CNetworkManager::DownloadZTLog()
{
    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
        return RET_ERROR;

    TCPTxRequest(GetZTLogDownloadReqPacket());

    return RET_OK;
}

unsigned int CNetworkManager::DownloadTrainLogs()
{
    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
        return RET_ERROR;

    TCPTxRequest(GetTrainLogsDownloadReqPacket());

    return RET_OK;
}

unsigned int CNetworkManager::DeleteRemoteZTLog()
{
    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
        return RET_ERROR;
    TCPTxRequest(GetDeleteZTLogReqPacket());

    return RET_OK;
}

unsigned int CNetworkManager::SetZTFunctions(const CTCPZTFunctionsStatus &ZTFunctions)
{
    if(mNetworkSMState != NETWORK_SM_CLIENT_CONNECTED_STATE)
        return RET_ERROR;

    QByteArray data;
    QDataStream strm(&data,QIODevice::WriteOnly);
    strm.device()->seek(0);

    strm << ZTFunctions;

    TCPTxRequest(GetSetZTFunctionsPacket(data));

    return RET_OK;
}


//slots
void CNetworkManager::SocketConnected()
{
    qDebug("Socket connected slot");
    NetworkStateMachine(NETWORK_SM_SOCKET_CONNECTED_EVENT);
}
void CNetworkManager::SocketDisconnected()
{
    qDebug("Socket disconnected slot");
    NetworkStateMachine(NETWORK_SM_SOCKET_DISCONNECTED_EVENT);
}
void CNetworkManager::SocketError(QAbstractSocket::SocketError socketError)
{
   qDebug("Socket error slot. Error:[%d]",socketError);
    NetworkStateMachine(NETWORK_SM_SOCKET_ERROR_EVENT);
}
void CNetworkManager::SocketDataReady()
{
//    qDebug() << "Socket data ready:" << mZTTcpSocket.bytesAvailable();
    emit NetworkTCPRx();
    AnalyzeNewData(mZTTcpSocket.readAll());
}
void CNetworkManager::OnlineCheckSocketConnected()
{
    OnlineCheckStateMachine(NETWORK_SM_SOCKET_CONNECTED_EVENT);
}
void CNetworkManager::OnlineCheckSocketError(QAbstractSocket::SocketError socketError)
{
    OnlineCheckStateMachine(NETWORK_SM_SOCKET_ERROR_EVENT);
}

void CNetworkManager::TCPRxHeartbeat()
{
    qDebug() << "TCP: Received Heartbeat";
}

void CNetworkManager::TCPRxStationName(QString Name)
{
    qDebug() << "TCP: Received station name:" << Name;
    mProgramHandle->RxStationName(Name);

    mZTTcpSocket.write(GetZTStatusReqPacket());
}

void CNetworkManager::TCPRxZTStatus(const QByteArray &data)
{

    CTCPZTStatus status;

    QDataStream stream(data);
    stream.device()->seek(0);

    stream >> status;

    qDebug() << "TCP: Received ZT Status";

    mProgramHandle->RxZTStatus(status);
}

void CNetworkManager::TCPRxZTLog(QString ZTLog)
{
    mProgramHandle->RxZTLog(ZTLog);
}

void CNetworkManager::TCPRxTrainLogsDownladAck(qint32 NbFiles)
{
    qDebug("TCP: Received downoad train files ack : %d",NbFiles);
    mProgramHandle->DownloadTrainLogsBegin(NbFiles);


    mZTTcpSocket.write(GetTrainLogsFileDataAckPacket((qint32)TCP_PROTOCOL_ACK));
}

void CNetworkManager::TCPRxTrainLogFileData(const QByteArray &TrainFileData)
{
    QDataStream strm(TrainFileData);
    strm.device()->seek(0);

    QString LogFilePathName;
    strm >> LogFilePathName;

    LogFilePathName.prepend(mProgramHandle->GetTrainLogsTempPath());

    QFile* BinaryLogFile = new QFile(LogFilePathName);
    if(BinaryLogFile)
    {
        if(BinaryLogFile->open(QIODevice::WriteOnly) == false)
        {
            qDebug("Could not create log file : %s",LogFilePathName.toLatin1().data());
            delete BinaryLogFile;
            return;
        }
    }

    BinaryLogFile->write(strm.device()->readAll());

    BinaryLogFile->close();
    delete BinaryLogFile;

    mProgramHandle->RxTrainLogData(LogFilePathName);

    mZTTcpSocket.write(GetTrainLogsFileDataAckPacket((qint32)TCP_PROTOCOL_ACK));
}

void CNetworkManager::TCPRxTrainLogsDownloadFinished()
{
    mProgramHandle->RxTrainLogsDownloadFinished();
}

void CNetworkManager::TCPRxDeleteZTLogAck(bool success)
{
    mProgramHandle->RemoteZTLogDeleted(success);
}

void CNetworkManager::TCPRxSetZTFunctionsAck(bool ack)
{
    mProgramHandle->SetZTFunctionsSatusDone(ack);
}

void CNetworkManager::UDPSocketDataReady()
{

}












