/**************************************************************\

  drsdaq.cpp

  Main program for DRS CTX DAQ system. Global initialization, 
  starts threads for console input and socket interface.
  
  Sebastian Commichau, Oliver Grimm
 
\**************************************************************/

#define DEFAULT_CONFIG "../config/DRSDAQ.conf"  // Default configuration file
#define LOCKFILE "/tmp/CT3_DAQ_LOCK"

#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

#include "DAQReadout.h"
#include "HVFeedback.h"

// Function prototypes
void ConsoleCommand(DAQReadout *);
void CCCommand(DAQReadout *);
void SignalHandler(int);
void CrashHandler(int);

// ================
//   Main program
// ================
//
// Several unlikely system call failures are handled via throwing an exception.

int main(int argc, char *argv[]) {

  char str[MAX_COM_SIZE];
  pthread_t thread_ConsoleCommand, thread_CCCommand;
  int LockDescriptor;
 
  // Interpret command line (do before lockfile creation in case of exit())
  if((argc==3 && strcmp(argv[1],"-c")!=0) || argc==2) {
    printf("Usage: %s [-c <ConfigFile>]    Default file is \"%s\"\n", argv[0], DEFAULT_CONFIG);
    exit(EXIT_SUCCESS);
  }

  // Assure only one instance of program runs (lock creator written to log file)
  if((LockDescriptor = open(LOCKFILE,O_WRONLY|O_CREAT|O_EXCL, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1) {
    if(errno==EEXIST) {
      printf("Error: Lock file already existing\n");
      sprintf(str,"paste %s -s -d ' '",LOCKFILE);
      system(str);
    }
    else printf("Could not create lock file %s (%s)\n", LOCKFILE, strerror(errno));
    exit(EXIT_FAILURE);
  }
  close(LockDescriptor);
  sprintf(str,"echo Created >%s; date >>%s; echo by $USER@$HOSTNAME >>%s",LOCKFILE,LOCKFILE,LOCKFILE);
  system(str);
  
  system("clear");
  printf("\n************* DRS readout built %s, %s\n\n",__DATE__,__TIME__);

  // Set signal handlers
  signal(SIGUSR1, &SignalHandler);
  siginterrupt (SIGUSR1, true);   // Set SIGUSR1 to interrupt (and not restart) blocking system calls  
  signal(SIGQUIT, &CrashHandler);
  signal(SIGILL, &CrashHandler);
  signal(SIGABRT, &CrashHandler);
  signal(SIGFPE, &CrashHandler);
  signal(SIGSEGV, &CrashHandler);
  signal(SIGBUS, &CrashHandler);
  signal(SIGTERM, &CrashHandler);
  signal(SIGINT, &CrashHandler);
    
  // Construct main instance
  DAQReadout dreadout(argc==3 ? argv[2] : DEFAULT_CONFIG);

  // Create threads and mutex for thread synchronization
  if (pthread_mutex_init(&dreadout.control_mutex, NULL) != 0) {
    perror("pthread_mutex_init failed");
    throw;
  }
  if ((pthread_create(&thread_ConsoleCommand, NULL, (void * (*)(void *)) ConsoleCommand,(void *) &dreadout)) != 0) {
    perror("pthread_create failed with console thread");
    throw;
  }
  if ((pthread_create(&thread_CCCommand, NULL, (void * (*)(void *)) CCCommand,(void *) &dreadout)) != 0) {
    perror("pthread_create failed with socket thread");
    dreadout.SocketThread = NULL;
  }
  else dreadout.SocketThread = &thread_CCCommand;  // Thread should be accessible for sending signals  
 
  // Wait for threads to quit
  pthread_join(thread_ConsoleCommand, NULL);
  if(dreadout.SocketThread != NULL) pthread_join(thread_CCCommand, NULL);

  // Destruct mutex and main instance
  pthread_mutex_destroy (&dreadout.control_mutex);
  dreadout.~DAQReadout();
  
  // Remove lockfile
  if (remove(LOCKFILE)==-1) {
    printf("Could not remove lock file %s (%s)\n", LOCKFILE, strerror(errno));
    exit(EXIT_FAILURE);
  }
  exit(EXIT_SUCCESS);
}

 
/********************************************************************\

  ConsoleCommand thread

  Handle console input

\********************************************************************/
 
void ConsoleCommand(DAQReadout *m) {

  char Command[MAX_COM_SIZE], Buf[MAX_COM_SIZE];
  time_t Time;

  while (!m->Exit) {
    m->PrintMessage("");	// New prompt
    if (fgets(Command, MAX_COM_SIZE, stdin)==NULL)
      m->PrintMessage("Error reading command line input\n");

    strftime(Buf,MAX_COM_SIZE,"%d/%m/%y %X", localtime(&(Time=time(NULL))));
    m->PrintMessage(MsgToLog, "CONSOLE(%s)> %s",Buf, Command);

    pthread_mutex_lock(&m->control_mutex);
    m->CommandControl(Command, false);
    pthread_mutex_unlock(&m->control_mutex);
  }
}



/********************************************************************\

  CCCommand thread

  Listen to commands from socket (Central Control)
  
  This thread will block execution in the accept() and read() socket function
  while waiting for a connection or data. If the exit function is invoked through
  keyboard command, these blocking functions are interrupted by raising the signal
  SIGUSR1. Testing on errno=EINTR indicates this termination. The dummy signal
  handler below is needed to prevent the standard thread termination occurring
  when this signal is received.
  
\********************************************************************/

void CCCommand(DAQReadout *m) {

  int ServerSocket,ConnectionSocket,ReadResult;
  struct sockaddr_in SocketAddress, ClientAddress;
  struct hostent *ClientName;
  socklen_t SizeClientAddress=sizeof(ClientAddress);
  char Command[MAX_COM_SIZE], Buf[MAX_COM_SIZE];
  time_t Time;

  // Set up server socket
  if ((ServerSocket = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
    m->PrintMessage("Could not open server socket, no remote connection possible (%s).\n", strerror(errno));
    return;
  }
 
  SocketAddress.sin_family = PF_INET;
  SocketAddress.sin_port = htons((unsigned short) m->fCCPort);
  SocketAddress.sin_addr.s_addr = INADDR_ANY;
  if (bind(ServerSocket, (struct sockaddr *) &SocketAddress, sizeof(SocketAddress)) == -1)
  {
    m->PrintMessage("Could not bind port to socket (%s)\n", strerror(errno));
    close(ServerSocket);
    return;
  }
  if (listen(ServerSocket, 0) == -1) {
    m->PrintMessage("Could not set socket to listening (%s)\n", strerror(errno));
    close(ServerSocket);
    return;
  }
  
  // Looping to wait for incoming connection  
  while (!m->Exit) {  
    if ((ConnectionSocket = accept(ServerSocket, (struct sockaddr *) &ClientAddress, &SizeClientAddress)) == -1) {
      if (errno!=EINTR) m->PrintMessage("Failed to accept incoming connection (%s)\n", strerror(errno));
      close(ServerSocket);
      return;
    }

    ClientName = gethostbyaddr((char *) &ClientAddress.sin_addr ,sizeof(struct sockaddr_in),AF_INET);
    m->PrintMessage("Connected to client at %s (%s).\n", inet_ntoa(ClientAddress.sin_addr), ClientName!=NULL ? ClientName->h_name:"name unknown");
    m->Socket = ConnectionSocket;

    while (!m->Exit) { // Looping as long as client exists
      memset(Command,0,sizeof(Command));
      ReadResult = read(ConnectionSocket, Command, MAX_COM_SIZE);
      if (ReadResult==0) break;  // Client no exisiting anymore
      if (ReadResult==-1) {
	if (errno!=EINTR) m->PrintMessage("Error read from socket (%s)\n", strerror(errno));
	break;
      }
      
      strftime(Buf,MAX_COM_SIZE,"%d/%m/%y %X", localtime(&(Time=time(NULL))));
      m->PrintMessage(MsgToLog,"SOCKET(%s)> %s%s",Buf, Command, Command[strlen(Command)-1]=='\n' ? "":"\n");
      m->PrintMessage(MsgToConsole,"SOCKET> %s%s",Command, Command[strlen(Command)-1]=='\n' ? "":"\n");
     
      pthread_mutex_lock(&m->control_mutex);       
      m->CommandControl(Command, true);	// Process CC command
      pthread_mutex_unlock(&m->control_mutex);
      m->PrintMessage("");	// New prompt
    }
    m->Socket = -1;
    m->PrintMessage("Disconnected from client.\n");
    close(ConnectionSocket);
  } 
  close(ServerSocket);
  m->PrintMessage("Server socket closed.\n");
}


/********************************************************************\

  Signal handlers

\********************************************************************/

// Remove lock file before running default signal code
void CrashHandler(int Signal) {
  remove(LOCKFILE);
  printf("Caught signal number %d. Removing lockfile and performing standard signal action. Good luck.\n",Signal);
  signal(Signal, SIG_DFL);
  raise(Signal);
}

// Dummy signal handler to return from blocking syscalls
void SignalHandler(int Signal) {
  return;
}
