/*  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.hxx"
#include "Subsystem.hxx"
#include "PeriodicAction.hxx"
#include "TCPListener.hxx"
#include "TCPSender.hxx"
#include "IONotifier.hxx"
#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;

#include <unistd.h> //for usleep
#include <sys/time.h>
#include <sys/types.h>
Subsystem::Subsystem (unsigned short int portCommandListener, unsigned long int reportPeriod, char * ccName, unsigned short int portReportListener , unsigned short int maxTimeoutCount_, char * specialReportOnStartup ) 
            :
        TCPListener(portCommandListener), reportSender(ccName,portReportListener),  locked(false),  reporting(false),maxTimeoutCount(maxTimeoutCount_),reportPeriod(reportPeriod)
        {
//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

            
//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(&mutex4reporting,NULL);
    }
void * Subsystem::ReportingAndCheckingLoop()
{
#ifdef DEBUG
    printf("Subsystem: ReportingAndChecking Thread created\n");
    
#endif
    timeoutCount=0;
    acknowledgeReceivedForLastReport=true;

    while(!reportSender.isTrialToConnectSuccesful()){
#ifdef DEBUG
        printf("Subsystem: retrying in %d usec\n",reportPeriod);
#endif
        usleep(reportPeriod);
    }
    fd_set read_fd_set;
    FD_ZERO (&read_fd_set);
    FD_SET(reportSender.IODescriptor(), &read_fd_set); 
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = (reportPeriod);
    while(Reporting())
    {
            /* Block until input arrives, which is ack. */

        if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, &tv) < 0)
        {
            perror("Subsystem: select");
            exit (EXIT_FAILURE);
        }
  
        if (FD_ISSET(reportSender.IODescriptor(), &read_fd_set))
        {
//input arrived, which is the ack
#ifdef DEBUG
        printf("Subsystem: ack received\n");
#endif          
            CheckReportAcknowledge();
            FD_SET (reportSender.IODescriptor(), &read_fd_set);
            continue;
                //in linux, tv after select represents the time not slept, so we feed it again to select to complete the reportPeriod
        }else{
                //input didn't arrrive so it's time to report
            if(!SendReport()){
                    //sending was not succesfull
                
                    //ResetConnectionToCC();
                if(locked){
                    ULock();
                }
                reportSender.CloseConnection();
                
#ifdef DEBUG
                printf("Subsystem: ReportingAndChecking Thread exited on ack checking \n");
#endif
//TO DO!!!!!!!!!!!!! 
//                TCPListener::ClosingChannel();
                
                break;
                
                
            }
            
            FD_SET (reportSender.IODescriptor(), &read_fd_set);
            tv.tv_sec = 0;
            tv.tv_usec = reportPeriod;  
      }
        
    }
//if program reaches here is because it hasn't been able to report correctly 
#ifdef DEBUG
    printf("Subsystem: ReportingAndChecking Thread finished\n");
#endif
return 0;

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

bool Subsystem::SendSpecialReportContaining(char * specialReport)
{
    if(!acknowledgeReceivedForLastReport){
        timeoutCount++;
        if(timeoutCount==maxTimeoutCount){
#ifdef DEBUG
            printf("Subsystem: Timeout expired for report acknowledge. ResetConnectionToCC\n");
#endif            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);
#ifdef DEBUG
    printf("Subsystem: Sending %s \n",this->reportString);
#endif
    pthread_mutex_unlock(&mutex4report);    

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

void Subsystem::ClosingChannel()
{
//  #ifdef DEBUG
//      printf("Subsystem: cancelling ReportingAndChecking\n");
//  #endif

//      pthread_cancel(reportThread);
//  #ifdef DEBUG
//      printf("Subsystem: cancelled ReportingAndChecking\n");
//  #endif
    
    TCPListener::ClosingChannel();
    if(locked){
        ULock();
    }
#ifdef DEBUG
    printf("Subsystem: resetting connection\n");
#endif
      SetReporting(false);
#ifdef DEBUG
      printf("Subsystem: waiting ReportingAndChecking Thread to finish\n");
#endif
      pthread_join(reportThread,NULL);
#ifdef DEBUG
      printf("Subsystem: ReportingAndChecking finished\n");
#endif    
    reportSender.CloseConnection();
}


    void Subsystem::ResetConnectionToCC () {
        if(locked){
            ULock();
        }
#ifdef DEBUG
        printf("Subsystem: resetting connection\n");
#endif
        SetReporting(false);
#ifdef DEBUG
        printf("Subsystem: waiting ReportingAndChecking Thread to finish\n");
#endif
        pthread_join(reportThread,NULL);
#ifdef DEBUG
        printf("Subsystem: ReportingAndChecking finished\n");
#endif    
        reportSender.CloseConnection();
        TCPListener::ClosingChannel(); //but still is waiting for new connections
    }
void Subsystem::process(){
    if (!locked){
        if(!Reporting()){

            if ( !strcmp(TCPListener::receivedStream, "REPOR") ) {
#ifdef DEBUG
                printf("Subsystem: received REPOR\n");
#endif
                    //before startreporting, the connection to cc by reportSender has to be stablished
                pthread_create(&reportThread,NULL,&Subsystem::pthread_ReportingAndCheckingLoop,this);
                SetReporting(true);
                return;
            }
        } else { //already reporting
            if ( !strcmp(TCPListener::receivedStream,"LOCK!") ) {
                Lock();
                return;
            } 
        }
             
    } else { //locked
//as preliminary tests instead of commands, the new subsystem state is sent
        if (! strncmp(TCPListener::receivedStream, "ULOCK", 5) ) {
            ULock();
            return;
        } 
#ifdef DEBUG
        printf("Subsystem: processing ... seting new state: %s \n", TCPListener::receivedStream);
#endif  
            //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 () {

    if(!acknowledgeReceivedForLastReport){
        timeoutCount++;
        if(timeoutCount==maxTimeoutCount){
#ifdef DEBUG
            printf("Subsystem: Timeout expired for report acknowledge. ResetConnectionToCC\n");
#endif
            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);
#ifdef DEBUG
    printf("Subsystem: Sending %s \n",this->reportString);
#endif
    pthread_mutex_unlock(&mutex4report);    

    acknowledgeReceivedForLastReport=false;

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

//incorporate lastTimeStamp in check           
            if (reportSender.ReturnNew() == "RECV@" ) {
                acknowledgeReceivedForLastReport=true;
                timeoutCount=0;
            }else{
                cerr<<"wrong ack!\n";
            }
        }
    
    void Subsystem::Lock(){
#ifdef DEBUG
        printf("Subsystem: Locking to CC mode\n");
#endif       
        locked=true;
    }
    void Subsystem::ULock(){
#ifdef DEBUG
        printf("Subsystem: UNLocking to CC mode\n");
#endif       

        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());
    
}
void Subsystem::Shutdown()
{}

void Subsystem::WaitingForShutdown()
{
    sleep(6000);
}


//TO BE OVERRIDEN:

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


