// Copyright 2016 CERN and GSI
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#ifndef _SILECS_CONNECTION_H_
#define _SILECS_CONNECTION_H_

#include <silecs-communication/interface/core/SilecsService.h>
#include <silecs-communication/interface/utility/Mutex.h>
#include <silecs-communication/interface/utility/TimeStamp.h>
#include <silecs-communication/protocol/core/silecs.h>

namespace Silecs
{
	class PLC;

	// Time to wait (second) before next reconnection attempt
	typedef enum
	{  UrgentConnection=2, ShortTermConnection=20, LongTermConnection=60
	} ConnectionDelay;

#ifdef __x86_64__
    typedef long ChannelID;  //pointer is 64bits word
#else
    typedef int ChannelID;     //pointer is 32bits word
#endif

    class Lock
    {

	public:

    	Lock(Mutex* mutex): mutex_(mutex)
    	{
    		mutex_->lock();
    	}

    	~Lock()
    	{
    		mutex_->unlock();
    	}
    	Mutex* mutex_;
    };

	class Connection
	{

	public:
		Connection(PLC* thePLC);
		virtual ~Connection();

		virtual int readData(PLC* thePLC,
		                     unsigned long address,
							 unsigned long offset,
							 unsigned long size,
							 unsigned char* buffer) = 0;

		virtual int writeData(PLC* thePLC,
		                     unsigned long address,
							 unsigned long offset,
							 unsigned long size,
							 unsigned char* buffer) = 0;

        /*!
         * \fn enable/disable
		 * \brief The client can suspend the data transmission by disabling the connection if required.
		 * enable/disable methods are used from the high-level Cluster API: connect/disconnect.
		 * \param connectNow can be used to force immediate connection (true)
         */
		virtual bool enable(PLC* thePLC, bool connectNow);
        void disable(PLC* thePLC);
        bool isEnabled();

		static inline bool isAlive() { return isAlive_; }
        bool isConnected();

        virtual int readUnitCode(PLC* thePLC, UnitCodeType& dataStruct);
        virtual int readUnitStatus(PLC* thePLC, UnitStatusType& dataStruct);
        virtual int readCPUInfo(PLC* thePLC, CPUInfoType& dataStruct);
        virtual int readCPInfo(PLC* thePLC, CPInfoType& dataStruct);

        // true if the "recvUnitStatus" is RUN, false otherwise. Throws exception on failure
        virtual bool isRunning(PLC* thePLC);

        //SET PLC COLD RESTART
        virtual int coldRestart(PLC* thePLC);
        virtual int plcStop(PLC* thePLC);

	protected:
		friend class Cluster;
        friend class PLC;
        friend class CNVRecvDeviceMode;
        friend class CNVSendDeviceMode;

        /*!
         * \fn doOpen
		 * \brief This method check the current state of the connection and open it if required.
		 * doOpen that is called on each transaction is reponsible of the automatic reconnection mechanism.
		 */
		virtual bool doOpen(PLC* thePLC);
		void doClose(PLC* thePLC, bool withLock);
		bool reOpen(PLC* thePLC);

		virtual bool open(PLC* thePLC) = 0; 	//open the connection for a particular PLC brand
		virtual bool close(PLC* thePLC) = 0;   //close the connection for a particular PLC brand

        /*!
         * \fn checkError
		 * \brief This method attempts to reconnect the PLC in case of "expected" transaction failure.
		 * If the returned value is true the request must be repeated.
		 * The retry flag defines whether the transaction will be repeated or not
		 * IE_CONNECT_ERROR - TCP/IP connect() failed: IeOpen() (PLC is off or the max. number of open connection is reached)
		 * Retry required:
		 * IE_DISCONNECT_ERROR -  Explicit/unexpected disconnect request (when the connection has been unactive for some time)
		 * IE_EPIPE_ERROR - TCP/IP send/recv failed (when one of the channel (get/set) has been unactive for some time or PLC is off)
		 * IE_TIMEOUT_ERROR - PLC is connected but does not reply (In mean time the PLC gets off or data is lost)
         */
		virtual bool checkError(PLC* thePLC, int err, bool retry);

        /*!
         * \fn updatePLCStatus
		 * \brief Responsible to update the diagnostic variables each time PLC states have changed
         */
		void updateStatus(PLC* thePLC);

		void logError(PLC* thePLC);

        /*!
         * \fn isTimeToReconnect
		 * \brief In order to not overload the network the reconnection attempts are gradually slowed
		 * down from second scale to several minutes. This method checks the elapsed time since connection.
		 * \return true if it's time to try to reconnect (time unit is second).
         */
		bool isTimeToReconnect();

	 	// flag used to enable/disable the transactions independently from the scheduling
		bool isEnabled_;

		/* . read-channel and write channel are independent and can be accessed in parallel.
		 * . Each channel must be protected against respective concurrent access.
		 * . The global action (open,close,etc.) must be protected from any concurrent access.
		 */
		Mutex* readMux_; 	//Mutex used to protect the PLC read-channel resource
		Mutex* writeMux_; 	//Mutex used to protect the PLC write-channel resource
		Mutex* connMux_; 	//Mutex used to protect the global PLC connection resource (for open/close, etc.)

		// Time counter to manage the automatic reconnection
		TsCounter* timeConn_;
		ConnectionDelay delayConn_; //current delay between two connection
		unsigned long remainConn_;  //number of attempt before next slow-down
		static const unsigned int numberConn_; //number of connection attempt for each connection delay

		// Communication Diagnostic & Monitoring
		static bool isAlive_; // PLC has repliyed to the ping: true/false
		bool isConnected_; // State of this particular connection: FEC/PLC/Class

		// not copyable object
		Connection(const Connection&);
		Connection& operator=(const Connection&);
	};

} // end namespace

#endif // _SILECS_CONNECTION_H_