#ifndef SUBSYSTEM
#define SUBSYSTEM

#include "PeriodicAction.hxx"
#include "TCPListener.hxx"
#include "PeriodicAction.hxx"
#include "TCPSender.hxx"
#define TIMESTAMP_LEN 12
//%02.2d:%02.2d:%02.2d:%03.3d
//in Makefile#define MAXMSG 4096 
/**
 * Base class for MAGIC Subsystem's communication with CC
 * 
 * It implements commandListener (see MAGIC-TDAS 00-07) as a TCPListener
 * derived class, to be able to custom the behaviour by overriding its
 * methods  
 *
 * Missing properties: Special Report in a GenerateSpecialReport method, also
 * a final implementation of CheckReportAcknowledge (CC sends back
 * "RECV:timestamp", with timestamp the one extracted from subsystem
 * report. So subsystem has to have a member lastReportTimestamp to compare).
 * Timeout in CheckReportAcknowledge?????.
 *
 * @short Base class for MAGIC Subsystem's communication with CC
 * @author Marc Casaldaliga
 * @version 0.9
 * @see TCPListener
 */
class Subsystem: public TCPListener
{

public:

        /**
         * Set the string to be sent in the next report that will be sent to
         * CC (with timestamp but without protocol specific item,
         * e.g. trailing \n). The timestamp is the data one, the time when
         * data was taken (the relevant for analysis), nothing to do with the
         * the time the report will be sent.
         *
         * It takes care internally of suspending and resuming communications
         * not to send something is being modified etc 
         *
         *  @param report CString with pure report (without "\n" ...) 
         */ 
    void SetReportString(char * report);

        /**
         *  Class unique constructor with the parameters which specify
         *  Subsystem configuration.
         *
         *  @param portCommandListener TCP/IP port Subsystem will open to
         *  listen to cc commands
         *
         *  @param reportPeriod Aproximate (+- 500ms) time in microsec,
         *  between each report is sent to CC
         *
         *  @param ccName C string with CC machine name
         *
         *  @param portReportListener TCP/IP port opened in CC which Subsystem
         *  will contact to send reports 
         *
         *  @param maxTimeoutCount_. Times subsystem tries to reconnect CC
         *  (with reportPeriod) periodicity, before it considers CC not
         *  available. After this count is reached Subsystem has to react some
         *  way (parking itself,  ...). This is done by
         *  Subsystem::HandleConnectionTimeoutIsOver 
         *
         *  @param specialReportOnStartup When connection with CC is
         *  stablished for first time after a Subsystem startup a special
         *  report to CC with subsystem setup must be sent to ensure any setup
         *  changes (which may have happened while CC was down) are
         *  recorded. This C string only contains report content without any
         *  protocol dependent character 
         *
         *  @see #HandleConnectionTimeoutIsOver
         *
         */
    Subsystem (unsigned short int portCommandListener, unsigned long int
               reportPeriod, char * ccName, unsigned short int
               portReportListener, unsigned short int maxTimeoutCount_, char *
               specialReportOnStartup );  

        /**
         * Send (immediately) a special report which a part from
         * protocol specific items (e.g. trailing \n and special report
         * identifier) contains the argument specialReport  
         *
         * ??? timestamp ????
         *
         * It takes care internally of suspending and resuming communications
         * not to send something is being modified etc 
         *
         *  @param specialReport CString with pure special report (without
         * "\n" ...)
         * 
         *  @return Returns whether sending will be succesful (true) or not
         *  (false)  
         */ 
    bool SendSpecialReportContaining(char * specialReport);

        /**
         *  Blocks main thread until Subsystem::Shutdown is called
         */
    void WaitingForShutdown();

        /**
         *  Subsystem communications will be shutdown definitely. Call this
         *  inside ProcessCmd or HandleConnectionTimeoutIsOver when your
         *  subsystem has to be stopped. If you only want to go standalone but
         *  still in contact with CC, you have to Ulock it but not this.
         *
         *  This call unblocks WaitingForShutdown in main, so your subsystem
         *  application can finish.
         */    
    void Shutdown();
    
protected:
        /**
         * Field with the report that will be sent to CC (with timestamp but
         * without protocol specific item, e.g. trailing \n)
         *
         * To be modified only within GenerateReport
         */
    char reportString[MAXMSG];
           
        /** To be overriden. It has to do all the job of interpreting CC
         * commands: (not the ones 
         * regarding starting connection, locking ...) but the ones for the 
         * subsystem function (This way we separate the protocol dependent
         * stuff).  
         *
         * Compares Subsystem::ccCmd with known commands from cc and executes
         * the appropiate function 
         *
         * This method is automatically called when a command from cc is
         * received, after checking it is not a communication/locking command 
         * 
         * If receiving a nonsense command call to ResetConnectionToCC()
         * (Network partner it's not -a properly working- CC). In principle
         * this will close and open again, but let's leave it this way so that
         * it is not protocol dependent 
         *
         * @param ccCmd CString with pure CC command (without "\n" ...) 
         */
    virtual void ProcessCmd(char *ccCmd);

        /** GenerateReport (to be overriden) This next method is automatically
         *   called before sending a report.
         * 
         *  According to state and data this should write
         *  Subsystem::reportString (it 
         *  should include the timestamp of the data sent, but without any
         *  trailing \n--this is part of the protocol) 
         *
         *  After it, this Subsystem::reportString plus any protocol dependent
         *  thing is sent. This GenerateReport and send is periodically
         *  repeated with reportPeriod   
         *
         *  Alternatively, one may leave GenerateReport empty and from outside
         *  call to Subsystem::SetReportString when hardware info is updated
         *  (probably what one what like to do). 
         */
    virtual void GenerateReport();

        /** HandleConnectionTimeoutIsOver. Does what the subsystem is suposed
         *  to do when it has lost definitely connection to CC. To be
         *  overriden. 
         *
         *  After Subsystem::maxTimeoutCount times of reconnection tries CC is
         *  considered not available. Subsystem has to react some way (parking
         *  itself,  ... ) depending on its autonomy. This is done by
         *  overriding this method with the desired behaviour.
         *
         *  After HandleConnectionTimeoutIsOver is done Subsystem will listen
         *  to CC again and start all communication process.
         *
         *  see #maxTimeoutCount
         */  
    virtual void HandleConnectionTimeoutIsOver(); 

        /** SuspendComm. Suspend communication threads
         *
         *  To prevent Subsystem to access its resources (e.g. send
         *  reportString) when we are about to access them externally (fill
         *  reportString with actual data), one can call to this method and
         *  suspend subsystem activity. 
         *  To resume it call to Subsystem::ResumeComm. 
         *  
         *  Always paired with a ResumeComm.
         *
         *  If Subsystem is at that time accessing the resources, SuspendComm
         *  will wait for them to be released and then will lock them. 
         *
         *  @see #ResumeComm
         *
         */
    void SuspendComm();

        /** ResumeComm. Resume communication threads
         *
         *  After calling SuspendComm() and having access to Subsystem
         *  resources (e.g. fill reportString with actual data) allow
         *  Subsystem to access them again and resume it's activity. we are
         *  about to access them externally (fill reportString with actual 
         *  data), one can call to this method and suspend subsystem activity.
         *  
         *  @see #SuspendComm
         *
         */
    void ResumeComm();

        /**
         * Resets communication to CC, to be used when a nonsense command
         * arrives from CC 
         * @see #ProcessCmd
         */
    void ResetConnectionToCC();

    
/////////////////////////////////////////////////////////////////////////
//////////////// IMPLEMENTATION DETAILS /////////////////////////////////
/////////////////////////////////////////////////////////////////////////

        //we implement commandListener as a TCPListener derived class, to be   
        //able to custom the behaviour by overriding it's methods, namely
        //process, which is called when something arrives.
        //This could be though as inheritance for implementation, which is not
        //desired. Can be avoided by using signals/slots
private:
        /**
         * C string with CC machine name
         */
    char* ccName;

        /**
         * Aproximate (+- 500ms) time in microsec, between each report is sent
         * to CC. 
         * It is expressed in microsec, despite the big uncertanty in these
         * units, because is feed directly to usleep. This should be changed at
         * least to ms: remember then to multiply per 1000 in usleep. Take care
         * also with long periods: usleep will accept only long int
         */  
    unsigned long int reportPeriod;

        /**
         * TCP/IP port for commandListener socket, here at subsystem machine
         */
    unsigned short int portCommandListener;

        /**
         * TCP/IP port for reportListener socket in CC machine
         */
    unsigned short int portReportListener;


        /* maxTimeoutCount. Times subsystem tries to reconnect CC (with reportPeriod)
         *  periodicity, before it considers CC not available.
         *  
         *  After this count is reached Subsystem has to react some way
         *  (parking itself,  ...). This is done by
         *  Subsystem::HandleConnectionTimeoutIsOver 
         *
         *  @see #HandleConnectionTimeoutIsOver
         *
         */
    
    short int maxTimeoutCount;

        /**
         * If locked == true Subsystem is being commanded by CC.
         */
    bool locked;

        /**
         * When REPOR received, reporting = true, and thread
         * ReportingAndCheckingLoop is started
         */
    bool reporting;
        /**
         * mutex used to prevent that while reporting state is being checked
         * in ReportingAndCheckingLoop thread, connection closing is detected
         * in TCPListener thread and reporting is set at the same time.
         *
         * I'm not sure if really needed
         */
    pthread_mutex_t mutex4reporting;
        /**
         * Accessor function for reporting bool variable that takes care of
         * properly locking the resource
         */ 
    inline bool Reporting(){ 
        pthread_mutex_lock(&mutex4reporting);
        bool ret=reporting;
        pthread_mutex_unlock(&mutex4reporting);
        return ret;
    }

        /**
         * Accessor function for reporting bool variable that takes care of
         * properly locking the resource
         */ 
    inline void SetReporting(bool trueValue)
        {
            pthread_mutex_lock(&mutex4reporting);
            reporting=trueValue;
            pthread_mutex_unlock(&mutex4reporting);
        }
    
        /**
         * We implement reportSender as an instance of class TCPSender
         */
    TCPSender reportSender;

        /**
         * mutex used to prevent that while report is being send 
         * in ReportingAndCheckingLoop thread, it is changed at the same time
         * in the main thread
         *
         * This mutex is for sure crucial 
         *
         * SuspendComm and ResumeComm lock/unlock it to achieve this purpose
         */
    pthread_mutex_t mutex4report;
    
        /**
         * Method that we override from TCPListener and gets called when some
         * command arrives. Here is where all the LOCK, REPOR protocol is
         * checked and where ReportingAndCheckingLoop thread is started and
         * stopped 
         */
    virtual void process();

        /**
         * Method that we also override from TCPListener and mainly gets
         * called when endofline is received (connection closed by the other
         * partner); we don't call it explicitely when the protocol is
         * not followed (this is done in ResetConnectionToCC). As in
         * ResetConnectionToCC we properly
         * close reportSender, and stop ReportingAndCheckingLoop
         */  
    virtual void ClosingChannel();

        /**
         * Method that actually sends the report via reportSender and returns
         * if it was successful (to be used inside a PeriodicAction or
         * whatever). 
         * It will also fail if we didn't receive an acknowledge for
         * last report several (timeoutCount) times, as asynchronously checked
         * in method CheckReportAcknowledge.
         * Before actually sending this->reportString it calls the
         * GenerateReport method that some real subsystems (as Subsystem
         * derived classes) will override to fill this string. It also calls
         * ExtractTimeStampFromLastReportTo to be able to check the timestamp
         * later (in CheckReportAcknowledge) when the acknowledge arrives. 
         */
    bool SendReport();
    
        /** 
         * Does the actual check of the report acknowledge (when it arrives)
         * and puts acknowledgeReceivedForLastReport to true; for synchronizing
         * the threads its better it does
         * not do anything else like resetting connection itself. If the
         * acknowledge doesn't arrive acknowledgeReceivedForLastReport will
         * remain false. SendReport will know and increase the timeoutCount.
         */
    void CheckReportAcknowledge();

        /**
         * to be able to checkreportacknowledge on has to have the timestamp of
         * the last report sent
         */
    char lastTimeStamp[TIMESTAMP_LEN];

        /**
         * Extracts this timestamp to its parameter lastTimeStamp
         */
    void ExtractTimeStampFromLastReportTo(char * lastTimeStamp);

        /**
         * the thread handle itself for ReportingAndCheckingLoop
         */
    pthread_t reportThread;

        /**
         * make up for pthread use. Allows pthread to effectively start a
         * non-static function, which is very convenient to avoid the use of
         * self
         */ 
    inline static void * pthread_ReportingAndCheckingLoop(void* self)
        { 
            return (void *) ( ((Subsystem* )self)->ReportingAndCheckingLoop() );
        }

        /** 
         * The actual method started in reportThread
         */
    void * ReportingAndCheckingLoop();
    
    bool acknowledgeReceivedForLastReport;
    short int timeoutCount;
    void Lock();
    void ULock();

};

#endif
