#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();

    

private:
        /**
         * C string with CC machine name
         */
    char* ccName;
        /**
         * Aproximate (+- 500ms) time in microsec, between each report is sent
         * to CC
         */  
    unsigned long int reportPeriod;
    
    unsigned short int portCommandListener;
    unsigned short int portReportListener;

        //we implement commandListener as a TCPListener derived class, to be   
        //able to custom the behaviour by overriding it's methods, namely
        //process     

        /* 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;


    bool locked;
    bool reporting;
    pthread_mutex_t mutex4reporting;
    inline bool Reporting(){ 
        pthread_mutex_lock(&mutex4reporting);
        bool ret=reporting;
        pthread_mutex_unlock(&mutex4reporting);
        return ret;
    }
    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;
        //reporting and trying connections to cc are implemented as
        //PeriodicActions     

    pthread_mutex_t mutex4report;
    virtual void process();
    virtual void ClosingChannel();
    
    bool SendReport();
    void CheckReportAcknowledge();
        //to be able to checkreportacknowledge on has to have the timestamp of
        //the last report sent
    char lastTimeStamp[TIMESTAMP_LEN];
    void ExtractTimeStampFromLastReportTo(char *);
    pthread_t reportThread;
    inline static void * pthread_ReportingAndCheckingLoop(void* self)
        { 
            return (void *) ( ((Subsystem* )self)->ReportingAndCheckingLoop() );
        }
    
            
    void * ReportingAndCheckingLoop();
    
    bool acknowledgeReceivedForLastReport;
    short int timeoutCount;
    void Lock();
    void ULock();

};

#endif
