/*******************************************************************************/
/*
    Description:
    Classe qui implémente le protocole Modbus.
    Cette classe est héritée par la classe serveur (CModbusCCMgr) ou client (CModbusMaster).
    Elle sert à interpréter ou formatter des paquets Modbus provenant ou destinés au partenaire.
    Elle peut être héritée soit un parent qui implémente une instance maître ou client mais pas les deux à la fois.
    sur le réseau.

*/

/* ************************************************************************** */
/* Revision:
### YYYMMDD JFM
    Verision d'origine.

### YYYYMMDD Description du besoin ou du bug
    Description du changement.
 */

/* ************************************************************************** */

#ifndef CMODBUSBACKEND_H
#define CMODBUSBACKEND_H
#include <QObject>
#include <QTcpSocket>
#include "ModbusRepository.h"
#include <QTimer>


#define MODBUS_EXCEPTION_FCT_MASK 0x80
#define MODBUS_RETRY_DELAY 2000 //millisecs
#define MODBUS_RETRY_MAX_COUNT 2    //resend an unanswered request this many times
#define MODBUS_MAX_NB_REGISTERS 0x7D

enum eModbusFunctions
{
    MODBUS_FCT_READ_HOLDING_REGISTERS = 3,
    MODBUS_WRITE_SINGLE_REGISTER = 6,
    MODBUS_FCT_WRITE_MULTIPLE_REGISTERS = 16
};

enum eModbusExceptionCodes
{
    MODBUS_EXCEPTION_ILLEGAL_FCT = 1,
    MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2,
    MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE = 3,
    MODBUS_EXCEPTION_SERVER_DEVICE_FAILURE = 4,
    MODBUS_EXCEPTION_ACKNOWLEDGE = 5,
    MODBUS_EXCEPTION_SERVER_DEVICE_BUSY = 6,
    MODBUS_EXCEPTION_MEMORY_PARITY_ERROR = 8,
    MODBUS_EXCEPTION_GATEWAY_PATH_UNAVAILABLE = 10,
    MODBUS_EXCEPTION_GATEWAY_TARGET_DEV_NOT_RESPONDING = 11
};

//Énumération des différents modes possibles
enum eModbusModes
{
    MODBUS_MASTER_MODE,
    MODBUS_SLAVE_MODE,
    MODBUS_INVALID_MODE
};

class CModbusException
{
public:

};

class CModbusPDU
{
public:
    qint8 mFunctionCode; //Type de transaction (voir structure eModbusFunctions) pour les fonctions implémentées par le présent module
    QByteArray mData;   //Les données de la transaction
};

class CModbusHeader //Objet contenant les données d'un header Modbus (selon la spécification)
{
public:
    qint16 mTransactionID;
    qint16 mProtocolID;
    qint16 mMessageLength;
    qint8 mUnitID;
};

class CModbusTransaction    //Objet contenant les données d'une transaction Modbus
{
public:
    CModbusHeader mHeader;  //Le header de la transaction
    CModbusPDU mPDU;    //Le PDU (fonction & données) de la transaction (voir classe CModbusPDU)
};

class CModbusRequest : public CModbusTransaction    //Objet représentant une transaction (mode maître)
{
public:
    CModbusRequest();
    ~CModbusRequest();
    int mRetries;       //nombre de fois que la transaction a été retransmise
    QTimer *mRequestTimer;  //Timer servant à retransmettre la requête si aucune réponse ne survient
    quint16 mStartAddress, mNbRegisters;    //For convinience...
};

class CModbusBackend : public QObject
{
    Q_OBJECT



private:
//enum eModbusMasterSMStates
//{
//    MODBUS_SM_WAIT_FOR__STATE
//};

//enum eModbusSlaveSMStates
//{

//};

public:
    CModbusBackend(CModbusRepository *Repo);    //Le pointeur vers le registre des données est obligatoire.
    virtual ~CModbusBackend();
    QTcpSocket *mModbusTCPSocketHandle;     //Pointeur vers le Socket TCP du réseau Modbus.
    CModbusRepository *mModbusRepo;         //Pointeur vers le registre de données Modbus

    bool mDataLinkValid;    //Flag indiquant si le lien de communication est en santé
    int mModbusMode;        //Indique si le lien modbus de cette instance est exploitée en "slave" (serveur) ou un "master" (client) (voir struct eModbusModes ci-haut).
    qint8 mDeviceID;        //Le device ID Modbus de cette instance.
    void ModbusLinkDisconnected();  //Fonction appelée par le parent lors d'une perte de la connection.
    void SetDeviceID(qint8 ID){mDeviceID = ID;}

    //Master (client)
    int SendReadHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount);   //Fonction servant à transmettre la fonction modbus "Read HR" au partenaire
    int SendWriteHoldingRegistersRequest(quint16 StartAddress, quint16 RegisterCount);  //Fonction servant à transmettre la fonction modbus "Write HR" au partenaire
    int SendWriteSingleRegisterRequest(quint16 Address);    //Fonction servant à transmettre la fonction modbus "Write Single Register" au partenaire




    virtual void RegistersDatabaseUpdated(quint16 StartAdderss, quint16 Length) = 0;        //Fonction virtuelle à être implémentée par la classe dérivée
                                                                                            //Appelée lorsque de nouvelles données ont été reçues du réseau Modbus et écrites dans le registre



    //Master virtual functions
    virtual void ModbusResponseException(quint8 ExceptionCode, quint8 FctCode); //Fonction virtuelle à être implémentée par la classe dérivée
    virtual void ModbusWriteMultipleRegsSuccess();          //Fonction virtuelle à être implémentée par la classe dérivée (Modbus Master seulement).  Appelée suite à la réception d'une réponse à une requête d'écriture multiple.
    virtual void ModbusWriteSingleRegsSuccess();          //Fonction virtuelle à être implémentée par la classe dérivée  (Modbus Master seulement).  Appelée suite à la réception d'une réponse à une requête d'écriture simple.
    virtual void ModbusRequestMaxRetryReached();        //Fonction virtuelle à être implémentée par la classe dérivée  (Modbus Master seulement).  Appelée lors de l'atteinte du nombre maximal d'essai de transmission d'une requête.
                                                                                //Sert à informer la classe dérivée  lorsqu'une exception se produit dans la communication Modbus (réponse)


    //Slave virtual function
    virtual void ModbusRequestException(quint8 ExceptionCode, quint8 FctCode);  //Fonction virtuelle à être implémentée par la classe dérivée
                                                                                //Sert à informer le parent lorsqu'une exception se produit dans la communication Modbus (requête)


private:


    //Sur un réseau Modbus, l'eslcave (slave) est un serveur et le maître (master) est un client.


    //Slave (server)
    int AnalyzeModbusRequest(CModbusTransaction Transaction);       //Exécute l'analyse des données reçues d'une requête Modbus
    int SendModbusResponse(CModbusTransaction RequestTransaction, QByteArray Data); //Fonction servant à composer et transmettre une réponse à une requête.
    int SendErrorResponse(CModbusTransaction RequestTransaction, quint8 ErrorCode); //Fonction servant à composer et transmettre une réponse d'erreur à une requête.

    //Master (client)
    QList<CModbusRequest*> mRequestsList;       //Liste des requêtes courantes en attente d'une réponse du serveur
    int SendModbusRequest(CModbusRequest *Request); //Construit et transmet une nouvelle requête Modbus au partenaire.
    quint16 GetNewTransactionID();      //Fonction (helper) servant à générer un nouvel ID de transaction nécessaire pour transmettre une nouvelle requête.
    int AnalyzeModbusResponse(CModbusTransaction Transaction);  //Exécute l'analyse des données reçues d'une réponse Modbus

    quint16 mTransactionIDCounter;  //Compteur interne des ID de transactions Modbus
    int mModbusRequestTimeout;      //Valeur du délai d'attente maximal avant de recevoir une réponse à une requête.  À l'expiration du timer, la requête est renvoyée.
    int mModbusMaxRetry;        //Valeur du nombre maximal de renvoi d'une requête avant d'invalider la requête.

signals:

//    void RegistersDatabaseUpdated(quint16 StartAdderss, quint16 Length);

//    //Master signals
//    void ModbusResponseException(quint8 ExceptionCode, quint8 FctCode);

////    //Slave signals
//    void ModbusRequestException(quint8 ExceptionCode, quint8 FctCode);



public slots:
    void ModbusDataReady(); //Slot indiquant au module que des données sont arrivées et sont attente dans le Socket.

    void RequestTimerExpired();     //Slot indiquant qu'un timer de requête a expiré.


};


QDataStream &operator<<(QDataStream &out, const CModbusHeader &source);
QDataStream &operator>>(QDataStream &in, CModbusHeader &dest);

#endif // CMODBUSBACKEND_H
