/****************************************************************************/
/* Copyright (c) 2015 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : Power Buoy logger app entry point.                            */
/* Filename : pblog.cpp                                                     */
/* Author   : Henthorn                                                      */
/* Project  : Power Buoy                                                    */
/* Version  : 1.0                                                           */
/* Created  : 04/13/2015                                                    */
/* Modified : 04/04/2018 - optionally spawn nav440 server                   */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
/*                                                                          */
/* (1) Creates new data directory and data file.                            */
/* (2) Optionally spawns a nav440 server that allows multiple consumers of  */
/*     the Crossbow NAV440 data stream.                                     */
/* (3) Sets up the Logger and the data sources.                             */
/* (4) Kicks-off a continous loop that:                                     */
/*     (a) Selects on the file sources to see if any data is pending        */
/*     (b) Calls read_data() on the file sources with data pending          */
/*     (c) After 60 minutes, closes the current data file and opens another */
/*                                                                          */
/****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#include "CSVWriter.hpp"
#include "ZSVWriter.hpp"
#include "Crossbow.hpp"
#include "CanBus.hpp"

#define  DELAY      5      // System init delay (seconds)
#define  OK         0
#define  BAD_SOCK  -1      // Socket comms failed
#define  BAD_OPTS  -2      // Inconsistent option values
#define  DEFAULT_BASE_DIR  "/home/buoy/Logs"
#define  DEFAULT_INTERVAL  60
static CSVWriter *w;

// Structure contains the options:
//   verbose :    Be chatty?
//   base_dir:    The top level log directory (e.g., /opt/log)
//   filename:    Optional log filename (defaults to YYYY.DDD-HH.MM.SS)
//
struct LogOptions
{
   char    zipped;
   char    verbose;
   char    crossbow;
   char    interval;
   char    base_dir[250];
   char    filename[50];
   char    nanoname[50];
};

static char _verbose;

int parse_options(int argc, char **argv, LogOptions *opt);
int dir_setup(LogOptions* opt);

void usage()
{
   printf("usage:\n");
   printf(" pblog [-v -i min] [log-home]\n");
   printf("   -v        Verbose     - chatty\n");
   printf("   -i        Interval    - minutes of logging per log file (%d)\n",
      DEFAULT_INTERVAL);
   printf("   -c        Ignore Crossbow errors\n");
   printf("   log-home  Base directory for logs (default is %s)\n",
      DEFAULT_BASE_DIR);
}

void cleanup(int sig)
{
   if (w) delete w;
   exit(0);
}

///////////////////////////////////////////////////////////////////////////////
//
int main(int argc, char **argv)
{
   w = NULL;

   signal(SIGINT, cleanup);
   signal(SIGQUIT, cleanup);
   signal(SIGKILL, cleanup);

   struct LogOptions options;
   strcpy(options.base_dir, DEFAULT_BASE_DIR);

   ////////////////////////////////////////////////////////////////////////////
   ///////////////////////  HIGH-LEVEL SETUP  /////////////////////////////////

   // Check cmdline options and optional base directory argument
   //
   if (parse_options(argc, argv, &options) != 0)
   {
      usage();
      return -1;
   }
   printf("verbose:%d   base_dir:%s\n", options.verbose, options.base_dir);

   _verbose = options.verbose;

   // Set up a new Run directory under the base directory
   //
   if (dir_setup(&options) != 0)
   {
      char buf[300];
      sprintf(buf, "pblog: Could not create the run directory %s",
         options.base_dir);
      perror(buf);
      usage();
      return -1;
   }
   printf("interval: %d\tverbose:%d\tbase_dir:%s\n",
      options.interval, options.verbose, options.base_dir);

   char verbose = options.verbose;
   int max_fd = 0, fd;

   ////////////////////////////////////////////////////////////////////////////
   //////////////////////  SETUP Writers and Readers //////////////////////////

   printf("Delay for %d seconds for the system initialize I/O ports\n", DELAY);
   sleep(DELAY);    // Race condition occurs at times

   // Create CSVWriter to write log records to the log files
   //
   if (options.zipped)
      w = new ZSVWriter(options.base_dir, NULL, options.interval);
   else
      w = new CSVWriter(options.base_dir, NULL, options.interval);

   w->open_log();
   if (!w->file_open())
      return -1;

   // Create the data readers, one for each data source.
   // The readers send updated data to the LogWriter object.
   // The LogWriter decides what, how, and when to write updated records.
   //
   Crossbow xbow(w);
   for (int j = 0, fd = 0; j < 3 && fd <= 0; j++)
   {
     if ( (fd = xbow.get_fd()) < 0)
     {
       perror("pblog: Failed to open Crossbow port: ");
       sleep(5);    // Race condition occurs at times
     }
     else
     {
       if (fd > max_fd) max_fd = fd;     // Track maximum fd for FD_SET
     }
     if (options.crossbow) break;        // Ignore crossbow? We're done here.
   }

   // If we need the crossbow but the port fails to open, exit
   // the app and let the system restart me. Otherwise, continue.
   //
   if (fd < 0 && !options.crossbow) exit(1);


   CanBus   can("can0", w);
   if ( (fd = can.get_fd()) < 0)
      perror("pblog: Failed to open CanBus port: ");
   else
      if (fd > max_fd) max_fd = fd;     // Track maximum fd for FD_SET

   ////////////////////////////////////////////////////////////////////////////
   ////////////////////////////  MAIN LOOP ////////////////////////////////////


   // loop (forever)
   //    if (there is data for the readers to read)
   //       let readers read new data and send it to the LogWriter
   //    endif
   // end loop
   //
   printf("entering main loop\n");
   while(1)
   {
      struct timeval timeout = {0, 10000L};   // 10 ms period (100Hz)
      fd_set readset;
      FD_ZERO(&readset);

      // Specifiy the ports to select on
      //
      if ( (fd = can.get_fd()) > 0 )
         FD_SET(fd, &readset);

      if ( (fd = xbow.get_fd()) > 0)
         FD_SET(fd, &readset);

      // See if any ports have data waiting
      //
      if (select((max_fd + 1), &readset, NULL, NULL, &timeout) > 0)
      {
         // Get CanBus data
         //
         if (FD_ISSET(can.get_fd(), &readset))
            can.read_data(_verbose);

         // Get Crossbow data
         //
         else if (FD_ISSET(xbow.get_fd(), &readset))
            xbow.read_data(_verbose);
      }
   }

   return OK;
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////  HELPER FUNCTIONS  ////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//
// Parse command line, populate the options struct
//
int parse_options(int argc, char** argv, LogOptions *opt)
{
   opt->zipped  = false;
   opt->verbose = false;
   opt->crossbow = false;
   opt->interval = DEFAULT_INTERVAL;
   opt->base_dir[0] = '\0';

   int o, nopts = 0;
   while ( (o = getopt(argc, argv, "vzi:")) != -1)
   {
      switch(o)
      {
         case 'v':
            opt->verbose = true;
            nopts++;
            break;

         case 'c':
            opt->crossbow = true;
            nopts++;
            break;

         case 'i':
            opt->interval = atoi(optarg);
            printf("New file interval set to %d minutes\n", opt->interval);
            nopts++;
            break;

         case 'z':
            opt->zipped = true;
            nopts++;
            break;

         default:
            return -1;
      }
   }

   // If there is one more argument than options, use it as
   // the base directory
   //
   if (argc > nopts+2) return -1;
   else if (argc == nopts+2) strcpy(opt->base_dir, argv[argc-1]);
   else                      strcpy(opt->base_dir, DEFAULT_BASE_DIR);

   return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
// Create the home directory for this run, e.g., "R000004"
// if this run number 4.
// Scan the base directory specified in opt for run directory names.
// Create a new name +1 from the last.
//
int dir_setup(LogOptions* opt)
{
   // Create the base directory
   //
   char mkdircmd[260];
   sprintf(mkdircmd, "mkdir -p %s", opt->base_dir);
   if (0 != system(mkdircmd))
   {
      perror("Unable to create log directory");
      return -1;
   }

   // Alphabetize the file list
   //
   struct dirent **namelist;
   int n = scandir(opt->base_dir, &namelist, 0, alphasort);
   if (n < 0)
   {
      // Can't find top-level log directory? We're done.
      //
      return -1;
   }

   // Determine the run directory name == max exisiting run name + 1
   //
   int yr, mon, mday, runnum, newnum = 0;
   time_t t = time(NULL);
   struct tm *lt = localtime(&t);
   for (int i = 0; i < n; i++)
   {
      //if (1 == sscanf(namelist[i]->d_name, "PBR%d", &runnum))
      //printf("scanning %s...\n", namelist[i]->d_name);
      if (4 == sscanf(namelist[i]->d_name, "%d-%02d-%02d.%03d", 
                      &yr, &mon, &mday, &runnum))
      {
         if (yr == lt->tm_year+1900 && mon == lt->tm_mon+1 && mday == lt->tm_mday)
           newnum = runnum + 1;
      }
      free(namelist[i]);
   }
   free(namelist);
   if (_verbose) printf("new run num = %03d\n", newnum);

   // Write the new run directory name in options
   //
   char* base_dir = strdup(opt->base_dir);
   sprintf(opt->base_dir, "%s/%04d-%02d-%02d.%03d", opt->base_dir,
           lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday, newnum);

   // Create the new run dir unless it already exists (not likely)
   //
   struct stat st = {0};
   if (stat(opt->base_dir, &st) == -1)
   {
      mkdir(opt->base_dir, 0777);
   }

   sprintf(mkdircmd, "rm -f %s/latest && ln -s %s %s/latest", base_dir, opt->base_dir, base_dir);
   if (0 != system(mkdircmd))
   {
      perror("Unable to create latest link");
   }
   if (base_dir) free(base_dir);

   return 0;
}

