/****************************************************************************/
/* Copyright (c) 2015 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : Power Buoy Data Logger log file writer class                  */
/* Filename : CSVWriter.cpp                                                 */
/* Author   : Henthorn                                                      */
/* Project  : Power Buoy                                                    */
/* Version  : 1.0                                                           */
/* Created  : 04/21/2015                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
/*                                                                          */
/* This class is meant to be used with data source objects to write source  */
/* data to a log file in a particular format. Need a different format?      */
/* Derive a new class from CSVWriter and provide the updated functions.     */
/*                                                                          */
/****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <wait.h>
#include <time.h>
#include <sys/time.h>

#include "CSVWriter.hpp"

// These identifiers are included in each record written to the log file
// and are used in post-processing and plotting. They are NOT related to
// the controller ids defined in FileSource.hpp (unfortunate, I know)
// 
#define BC_ID  0
#define SC_ID  1
#define PC_ID  2
#define XB_ID  3
#define TF_ID  4
#define RC_ID  5

// CSVWriter constructor opens a log file in the directory for writing.
//
CSVWriter::CSVWriter(const char *dirname, const char *filename,
   unsigned char new_file_interval)
  : _filename((char*)NULL), _dirname((char*)"."), _file(NULL),
    _file_interval(new_file_interval),
    _file_open_time(0), _verbose(false)
{
   if (dirname)  _dirname  = strdup(dirname);
   if (filename) _filename = strdup(filename);
   memset(&_xb, 0, sizeof(_xb));
   memset(&_pc, 0, sizeof(_pc));

   return;
   
   // Open a log file
   //
   if (0 != this->open_log())
   {
      perror("pblog: Could not create log file");
      printf("pblog: %s/%s\n", _dirname, _filename);
   }
   if (_verbose) printf("verbose!\n");
}

// Open a log file for writing.
// Use the _dirname and _filename to construct
// the full path name of the file.
// If no filename was specified in the constructor,
// use the current time to construct the name as YYYY.DDD-HH.MM.SS
//
int CSVWriter::open_log()
{
   _file_open_time = time(&_file_open_time);
   if (!_filename || strlen(_filename) == 0)
   {
      struct tm now;
      localtime_r(&_file_open_time, &now);
      sprintf(_pathname, "%s/%4d.%02d.%02dT%02d.%02d.%02d.csv", _dirname,
         now.tm_year+1900, now.tm_mon+1, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec);
   }
   else
   {
      sprintf(_pathname, "%s/%s.csv", _dirname, _filename);
   }

   //printf("Attempting to open %s\n", _pathname);
   _file = fopen(_pathname, "w+");
   if (!file_open())
   {
      perror("pblog: ");
      return -1;
   }
   char linkname[200];
   sprintf(linkname, "%s/latest", _dirname);
   unlink(linkname);
   symlink(_pathname, linkname);

   write_header();
   return 0;
}

CSVWriter::~CSVWriter()
{
   if (_dirname)  delete _dirname;
   if (_filename) delete _filename;
   if (file_open())
   {
     printf("gzipping working file %s\n", _pathname);
     char gzip[210];
     sprintf(gzip, "gzip %s", _pathname);
     system(gzip);
     close_log();
   }
}

int CSVWriter::write_to_log(const char* buf)
{
   int nbytes = fputs(buf, _file);
   fflush(_file);
   return nbytes;
}

// Write a header to the CSV file
//
int CSVWriter::write_header()
{
   if (file_open())
   {
      const char* header;

      // Timestamp
      //
      header = "Source ID, Timestamp, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Timestamp header\n");

      // Power Controller columns
      //
      header = "PC RPM, PC RPM Std Dev, PC Target RPM Std Dev, PC Voltage (V), PC Power (W), "
               "PC Battery Current, PC Load Current, PC Torque, PC Bridge DC, "
               "PC Load DC, PC Diff PSI, PC Scale, PC Retract, "
               "PC Target Voltage (V), PC Status, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Power Controller header\n");

      // Battery Controller columns
      //
      header = "BC Voltage, BC Ips, BC V Balance, BC V Stopcharge, BC Ground Fault, "
               "BC_Hydrogen, BC Status, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Battery Controller header\n");

      // Write the Crossbow column names
      //
      header = "XB Roll XB Angle (deg), XB Pitch Angle (deg), XB Yaw Angle (deg), "
               "XB X Rate, XB Y Rate, XB Z Rate, XB X Accel, XB Y Accel, XB Z Accel, "
               "XB North Vel, XB East Vel, XB Down Vel, XB Lat, XB Long, XB Alt, XB Temp, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Crossbow header\n");

      // Spring controller columns
      //
      header = "SC Load Cell (lbs), SC Range Finder (in), "
               "SC Upper PSI,SC Lower PSI, SC Status, CTD Time, CTD Salinity, CTD Temp, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Spring Controller header\n");

      // Trefoil controller columns
      //
      header = "TF Power-Loss Timeouts, TF Tether Volt, TF Batt Volt, TF Pressure psi, "
               "TF Qtn 1, TF Qtn 2, TF Qtn 3, TF Qtn 4, TF Mag 1 gauss, TF Mag 2, TF Mag 3, TF Status, "
               "TF Ang Rate 1 rad/sec, TF Ang Rate 2,  TF Ang Rate 3, TF VPE status,"
               "TF Accel 1 m/sec^2, TF Accel 2, TF Accel 3, TF Comms-Loss Timeouts, "
               "TF Maxon status, TF Motor curren mA, TF Encoder counts, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Trefoil Controller header\n");

#if 0
      // Rudder controller columns
      //
      header = "RC X Vel m/s, RC Y Vel, RC Z Vel, RC PSI, RC Beam1 Ampl counts, RC Beam2 Ampl, RC Beam3 Ampl, "
               "RC Beam1 Corr counts, RC Beam3 Corr, RC Beam3 Corr, RC Target Hdg degrees, RC Measured Hdg, "
               "RC Filtered Hdg, RC Rudder Counts, RC Ram %%, RC Qtn 1, RC Qtn 2, RC Qtn 3, RC Qtn 4, "
               "RC Mag 1 gauss, RC Mag 2, RC Mag 3, RC Status, RC Ang Rate 1 rad/sec, RC Ang Rate 2,  RC Ang Rate 3, "
               "RC VPE Status,  RC Accel 1 m/sec^2, RC Accel 2, RC Accel 3, Rudder Position counts, "
               "RC Gain P, RC Gain I, RC Gain D, RC Period, Filter TC, ";
      if (write_to_log(header) < 0)
         printf("pblog: Couldn't write Rudder Controller header\n");
#endif

      write_to_log("\n");
   }
   return 0;
}

// Write a complete record to the CSV file along with the
// id of the data source that triggered the update.
//
int CSVWriter::write_record(char src_id)
{
   if (file_open())
   {
      struct timeval time;
      gettimeofday( &time, 0 );
      double sec = time.tv_sec + time.tv_usec / 1000000.0;

      char logbuf[32];
      sprintf(logbuf, "%d, %.3f, ", src_id, sec);
      if (write_to_log(logbuf) < 0)
         printf("Didn't write timestamp\n");

      // Make sure these are written in the same order as the header line
      // 2015.10.23 - PC, BC, XB, SC
      //
      if (PC_ID == src_id) write_pc();
      else if (BC_ID == src_id) write_bc();
      else if (XB_ID == src_id) write_xb();
      else if (SC_ID == src_id) write_sc();
      else if (TF_ID == src_id) write_tf();
      else if (RC_ID == src_id) write_rc();

      if (write_to_log("\n") < 0)
         printf("Didn't write newline\n");

   }

   // Create a new log file every interval minutes
   // if no filename was specified in the constructor
   //
   if (!_filename)
   {
      long now;
      time(&now);
      if (now > (_file_open_time + _file_interval * 60) )
      {
         char gzip[210];
         sprintf(gzip, "gzip %s", _pathname);
         close_log();
         //
         int pid = fork();
         if (0 == pid)
         {
            if (execlp("gzip", "gzip", "-f", _pathname, (char*)NULL) < 0)
                perror(gzip);
            exit(0);
         }
         else if (pid < 0)
         {
            printf("!! Failed to fork() for gzip\n");
         }
         else
         {
            int stat;
            waitpid(pid, &stat, 0);   // Avoids leaving zombie processes
         }
         //
         // system(gzip);
         return open_log();
      }
   }

   return 0;
}

// We pad the record with commas for the fields that this source
// doesn't update.
static const char *pc_fmt = \
                      "%.1f, %.1f, %.1f, %.1f, %.1f, "
                      "%.2f, %.2f, %.2f, %.1f, %.1f, "
                      "%.2f, %.2f, %.2f, %.3f, %d, "
                      ",,,,,,, "
                      ",,,,,,,,,,,,,,,, "
                      ",,,,,,,, "
                      ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ";

// Write the Power controller record
//
int CSVWriter::write_pc()
{
   if (file_open())
   {
      char logbuf[1020];
      sprintf(logbuf, pc_fmt,
         _pc.rpm, _pc.sd_rpm, _pc.sd_rpm_target, _pc.voltage, _pc.power,
         _pc.bcurrent, _pc.lcurrent, _pc.torque, _pc.bridgedc, _pc.loaddc,
         _pc.diff_press, _pc.scale, _pc.retract, _pc.target_v, _pc.status);
      if (write_to_log(logbuf) < 0)
         printf("Didn't write pc record\n");

   }
   return 0;
}

static const char *bc_fmt = \
                      ",,,,,,,,,,,,,,, " 
                      "%.1f, %.2f, %.2f, %.2f, %.2f, %.2f, %d, "
                      ",,,,,,,,,,,,,,,, "
                      ",,,,,,,, "
                      ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ";

// Write the Battery controller record
//
int CSVWriter::write_bc()
{
   if (file_open())
   {
      char logbuf[1020];
      sprintf(logbuf, bc_fmt,
         _bc.voltage, _bc.ips, _bc.vbalance, _bc.vstopcharge, _bc.gfault,
         _bc.hydrogen, _bc.status);
      if (write_to_log(logbuf) < 0)
         printf("Didn't write bc record\n");
   }

   return 0;
}

static const char *xb_fmt = \
                      ",,,,,,,,,,,,,,, " 
                      ",,,,,,, "
                      "%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f, "
                      "%.3f, %.3f, %.3f, %.3f, %.5f, %.5f, %.3f, %.3f, "
                      ",,,,,,,, "
                      ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ";

// Write the Crossbow record
//
int CSVWriter::write_xb()
{
   if (file_open())
   {
      char logbuf[1020];
      sprintf(logbuf, xb_fmt,
         _xb.rollAngle, _xb.pitchAngle, _xb.yawAngleTrue, 
         _xb.xRateCorrected, _xb.yRateCorrected, _xb.zRateCorrected, 
         _xb.xAccel, _xb.yAccel, _xb.zAccel, 
         _xb.nVelocity, _xb.eVelocity, _xb.dVelocity,
         _xb.latitude, _xb.longitude, _xb.altitude, _xb.xRateTemp);
      if (write_to_log(logbuf) < 0)
         printf("Didn't write xb record\n");

   }
   return 0;
}

static const char *sc_fmt = \
                      ",,,,,,,,,,,,,,, " 
                      ",,,,,,, "
                      ",,,,,,,,,,,,,,,, "
                      "%d, %.2f, %.2f, %.2f, %d, %ld, %.6f, %.3f, "
                      ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, ";

// Write the Spring Controller record
//
int CSVWriter::write_sc()
{
   if (file_open())
   {
      char logbuf[320];
      sprintf(logbuf, sc_fmt,
         _sc.load, _sc.range, _sc.upsi, _sc.lpsi, _sc.status,
         _sc.epoch, _sc.salinity, _sc.temperature);
      if (write_to_log(logbuf) < 0)
         printf("Didn't write sc record\n");
   }

   return 0;
}

static const char *tf_fmt = \
                      ",,,,,,,,,,,,,,, " 
                      ",,,,,,, "
                      ",,,,,,,,,,,,,,,, "
                      ",,,,,,,, "
                      "%d, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f,"
                      "%.3f, %.3f, %.3f, %d, "
                      "%.3f, %.3f, %.3f, %d, "
                      "%.3f, %.3f, %.3f, %d, "
                      "%d, %d, %d, ";

// Write the Trefoil Controller record
//
int CSVWriter::write_tf()
{
   if (file_open())
   {
      char logbuf[420];
      sprintf(logbuf, tf_fmt,
        _tf.spare1, _tf.tvoltage, _tf.bvoltage, _tf.pressure,
        _tf.qtn[0], _tf.qtn[1], _tf.qtn[2], _tf.qtn[3],
        _tf.mag[0],  _tf.mag[1],  _tf.mag[2], _tf.status,
        _tf.ang_rate[0], _tf.ang_rate[1], _tf.ang_rate[2], _tf.vpe_status,
        _tf.accel[0], _tf.accel[1], _tf.accel[2], _tf.spare2,
        _tf.mstatus,  _tf.mcurr, _tf.encoder);

      if (write_to_log(logbuf) < 0)
         printf("Didn't write tf record\n");
   }

   return 0;
}

static const char *rc_fmt = \
                      ",,,,,,,,,,,,,,, " 
                      ",,,,,,, "
                      ",,,,,,,,,,,,,,,, "
                      ",,,,,,,, "
                      ",,,,,,,,,,,,,,,,,,,,,, "
                      "%.3f, %.3f, %.3f, %.3f, %d, %d, %d, %d, %d, %d, %.1f, %.1f, %.1f,"
                      "%d, %4.1f, %.4f, %.4f, %.4f, %.4f, %.3f, %.3f, %.3f, %d, %.3f, %.3f, %.3f,"
                      "%d, %.3f, %.3f, %.3f, %d, %.2f, %.2f, %.2f, %.1f, %d, ";

// Write the Rudder Controller record
//
int CSVWriter::write_rc()
{
   if (file_open())
   {
      char logbuf[420];
      sprintf(logbuf, rc_fmt,
        _rc.x_vel, _rc.y_vel, _rc.z_vel, _rc.pressure,
        _rc.b_amp1, _rc.b_amp2, _rc.b_amp3, _rc.b_cor1, _rc.b_cor2, _rc.b_cor3, _rc.target,
        _rc.measured, _rc.filtered, _rc.rudder, _rc.ram,
        _rc.qtn[0], _rc.qtn[1], _rc.qtn[2],  _rc.qtn[3],
        _rc.mag[0],  _rc.mag[1],  _rc.mag[2], _rc.status,
        _rc.ang_rate[0], _rc.ang_rate[1], _rc.ang_rate[2], _rc.vpe_status,
        _rc.accel[0], _rc.accel[1], _rc.accel[2], _rc.position,
        _rc.p_gain, _rc.i_gain, _rc.d_gain, _rc.period, _rc.ftc);

      if (write_to_log(logbuf) < 0)
         printf("Didn't write rc record\n");
   }

   return 0;
}

int CSVWriter::update_xbow_record(Crossbow::XB_Record *xb_rec)
{
   memcpy(&_xb, xb_rec, sizeof(_xb));
   //printf("Updating XB...\n");
   return write_record(XB_ID);
}

int CSVWriter::update_power_record(CanBus::PC_Record *pc_rec)
{
   memcpy(&_pc, pc_rec, sizeof(_pc));
   //printf("Updating Power Controller...\n");
   return write_record(PC_ID);
}

int CSVWriter::update_battery_record(CanBus::BC_Record *bc_rec)
{
   memcpy(&_bc, bc_rec, sizeof(_bc));
   //printf("Updating Battery Controller...\n");
   return write_record(BC_ID);
}

int CSVWriter::update_spring_record(CanBus::SC_Record *sc_rec)
{
   memcpy(&_sc, sc_rec, sizeof(_sc));
   //printf("Updating Spring Controller...\n");
   return write_record(SC_ID);
}

int CSVWriter::update_trefoil_record(CanBus::TF_Record *tf_rec)
{
   memcpy(&_tf, tf_rec, sizeof(_tf));
   //printf("Updating Rudder Controller...\n");
   return write_record(TF_ID);
}

int CSVWriter::update_rudder_record(CanBus::RC_Record *rc_rec)
{
   memcpy(&_rc, rc_rec, sizeof(_rc));
   //printf("Updating Rudder Controller...\n");
   return write_record(RC_ID);
}
