/*  Copyright (C) 2001 Marc Casaldaliga Albisu <casaldaliga@ifae.es>
================================================================
 
  This code 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 2 of the License, or
  (at your option) any later version.
 
  This code 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 Emacs (which is required to make this stuff work); if
  not, write to the Free Software Foundation, Inc., 675 Mass Ave,
  Cambridge, MA 02139, USA.
==================================================================
*/ 

//it is a good idea to encapsulate all the configuration file dependence in one a wrapper class SubFormatSubsystem, and derive from it
//#include "SubFormatSubsystem.C"
#include "Subsystem.H"
#include "PeriodicAction.H"
#include "TCPListener.H"
#include "TCPSender.H"
#include "IONotifier.H"
#include <string>
//for signals/slots (Callbacks in C++, see http://libsigc.sourceforge.net/)
#include <sigc++/signal_system.h>
//libsigc must be installed
#include <stdio.h>
using namespace SigC;
//there's no agreement in maximum lenght message, by now


Subsystem::Subsystem (unsigned short int portCommandListener, unsigned long int reportPeriod, char * ccName, unsigned short int portReportListener , unsigned short int maxTimeoutCount_ ) 
            :
        TCPListener(portCommandListener), reportSender(ccName,portReportListener), tryReportConn(reportPeriod), locked(false), reportingLoop(reportPeriod), reporting(false),reportAckNotifier(0),maxTimeoutCount(maxTimeoutCount_)
        {
//the behaviour is the following: commandListener has been started as a being derived from TCPListener. When connection from cc is opened commands are processed in this->process()
//when REPOR arrives reportSender(TCPSender) here tries to connect reportListener in CC. When this happens will start a periodic reporting


//tryReportConn is the PeriodicAction which will try periodically reportSender.isTrialToConnectSuccesful ...
            tryReportConn.DoWhileNot(slot(reportSender, &TCPSender::isTrialToConnectSuccesful) );
//... and when it is succesfull will start reportingLoop periodic action
            tryReportConn.FinallyDo(slot(this, &Subsystem::StartReporting));
//tryReportConn is started when REPOR received (See process())
            
//reportingLoop is a PeriodicAction which periodically sends the relevant report
            reportingLoop.DoWhile(slot(this,&Subsystem::SendReport));
//according the line above, it will be started whe reportSender connection is succesfull

//by now         
//demo report:
            strcpy(reportString,"standby:11:44:20:11:01:6:34:12:6.0:6.1:6.05:11:01:36:34:12:10.6:6.0:6.0:00:00:30.0:00:00:4.6:0.1:0.05:0.004:0.0:0.001:0.003:guiding:0.1deg:on:3.1Hz:0.09deg:J2000:Mkn421:messagefromthedrive:");
            pthread_mutex_init(&mutex4report,NULL);
            pthread_mutex_init(&mutex4ack,NULL);
    }

void Subsystem::SetReportString(char * report)
{
    SuspendComm();
    strcpy(this->reportString,report);
    ResumeComm();
    
}

bool Subsystem::SendSpecialReportContaining(char * specialReport)
{
    pthread_mutex_lock(&mutex4ack);
    if(!acknowledgeReceivedForLastReport){
        timeoutCount++;
        if(timeoutCount==maxTimeoutCount){
            pthread_mutex_unlock(&mutex4ack);
            return false; //SendReport is not succesfull so this will stop Periodic action 
        }
    }
            //before writing the report or sending it make sure no other thread thouches it
    pthread_mutex_lock(&mutex4report);
//        SubFormatSubsystem::ElaborateReport
            //calls overriden method GenerateReport that will do the
        //append the tag for specialReport in this protocol 
    sprintf(this->reportString,"SPECIAL:%s",specialReport);
    
        //GenerateReport may have filled timestamp. Record it to checkreportacknowledge in future


    ExtractTimeStampFromLastReportTo(lastTimeStamp);
    reportSender.Send(this->reportString);
    cerr<<"Sending "<<this->reportString<<"\n";
    pthread_mutex_unlock(&mutex4report);    

    acknowledgeReceivedForLastReport=false;
    pthread_mutex_unlock(&mutex4ack);
//????????Order mutexes are lock/unlock????????????
    return true; //if it goes here reporting was succesfull (Used in PeriodicAction reportingLoop)
}

    inline void Subsystem::StartReporting()
        {
            reportingLoop.Start();
            reportAckNotifier=new IONotifier(reportSender.IODescriptor());
//after report is sent via reportSender socket, reportListener in CC is sending an acknowledge. The next signal triggers CheckReportAcknowledge when something is received in reportSender channel (suposedly to be this ack)
            reportAckNotifier->readable.connect(slot(this,&Subsystem::CheckReportAcknowledge));
                //the first time
            timeoutCount=0;
            acknowledgeReceivedForLastReport=true;
            
        }     
    void Subsystem::ResetConnectionToCC () {
        if(locked){
            ULock();
        }
        reportingLoop.Stop();
        reporting=false;
        delete reportAckNotifier;
        reportSender.CloseConnection();
        TCPListener::ClosingChannel(); //but still is waiting for new connections
    }
    void Subsystem::process(){
         if (!locked){
             if(!reporting){
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! pending: compare the whole string, not only the first 5 bytes. strcmp gave problems ??????????????????? 
                 if ( !strncmp(TCPListener::receivedStream, "REPOR", 5) ) {
                     cerr<<"received REPOR\n";
                         //before startreporting, the connection to cc by reportSender has to be stablished
                     tryReportConn.Start();
                     reporting=true;
                     return;
                 }
             } else { //already reporting
                if ( !strncmp(TCPListener::receivedStream,"LOCK!", 5) ) {
                    Lock();
                    return;
                } 
             }
             
         } else { //locked
//as preliminary tests instead of commands, the new subsystem state is sent
             if (! strncmp(TCPListener::receivedStream, "ULOCK", 5) ) {
                 ULock();
                 return;
             } 
             cerr<<"processing ... seting new state: "<< TCPListener::receivedStream <<"\n";
                 //calls overriden method thet will process the command itself
             ProcessCmd(TCPListener::receivedStream);
             
                 //state=receivedStream;
             return;
             
         }

//if program reaches here is because it hasn't Received REPOR & LOCK in the proper way. Network partner it's not (a properly working) CC. Closing the opened connection and waiting for new one            
         ResetConnectionToCC();
    }

bool Subsystem::SendReport () {

    pthread_mutex_lock(&mutex4ack);
    if(!acknowledgeReceivedForLastReport){
        timeoutCount++;
        if(timeoutCount==maxTimeoutCount){
            pthread_mutex_unlock(&mutex4ack);
            return false; //SendReport is not succesfull so this will stop Periodic action 
        }
    }
            //before writing the report or sending it make sure no other thread thouches it
    pthread_mutex_lock(&mutex4report);
//        SubFormatSubsystem::ElaborateReport
            //calls overriden method GenerateReport that will do the
    GenerateReport();
        //GenerateReport may have filled timestamp. Record it to checkreportacknowledge in future


    ExtractTimeStampFromLastReportTo(lastTimeStamp);
    reportSender.Send(this->reportString);
    cerr<<"Sending "<<this->reportString<<"\n";
    pthread_mutex_unlock(&mutex4report);    

    acknowledgeReceivedForLastReport=false;
    pthread_mutex_unlock(&mutex4ack);
//????????Order mutexes are lock/unlock????????????
    return true; //if it goes here reporting was succesfull (Used in PeriodicAction reportingLoop)
        //acknowledge check is done elsewhere, when it arrives (asynchronously)
}
    void Subsystem::CheckReportAcknowledge()
        {
            reportSender.Receive();

            pthread_mutex_lock(&mutex4ack);
//incorporate lastTimeStamp in check           
            if (reportSender.ReturnNew() == "RECV@" ) {
                acknowledgeReceivedForLastReport=true;
                timeoutCount=0;
            }else{
                cerr<<"wrong ack!\n";
            }
            pthread_mutex_unlock(&mutex4ack);          
        }
    
    void Subsystem::Lock(){
        cerr<<"Locking to CC mode\n";
        locked=true;
    }
    void Subsystem::ULock(){
        cerr<<"UNLocking from CC mode\n";
        locked=false;
    }  
    void Subsystem::SuspendComm()
        {
            pthread_mutex_lock(&mutex4report);
        };
    void Subsystem::ResumeComm()
        {
            pthread_mutex_unlock(&mutex4report);
        };
void Subsystem::ExtractTimeStampFromLastReportTo(char * lastTimeStamp)
{
    string report(this->reportString);
    string::iterator it=report.begin();
        //point it to the first appearence of : 
    it+=report.find(":");
    
//cut the first field (between :) which is the status
    report.erase(report.begin(),it);

//#define TIMESTAMP_LEN 12
//%02.2d:%02.2d:%02.2d:%03.3d
    it=report.begin(); //back to the beginning
    it+=12;
//cut from the timestamp end to the rest
    report.erase(it,report.end());
    strcpy(lastTimeStamp,report.c_str());
    
}


//TO BE OVERRIDEN:

 void Subsystem::ProcessCmd(char *)
          {};
 void Subsystem::GenerateReport()
          {}; 
 void Subsystem::HandleConnectionTimeoutIsOver()
          {};


