/****************************************************************************/
/* Copyright (c) 2014 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : Interface for the AJA Ki Pro Quad video recorder.             */
/* Filename : KiPro.cc                                                      */
/* Author   : Henthorn                                                      */
/* Project  : i2MAP                                                         */
/* Version  : 1.0                                                           */
/* Created  : 11/23/2014                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
#include "Syslog.h"
#include "KiPro.h"
#include "KiProLog.h"
#include "HtmlUtil.h"

static char   KiPro::_response[8000];
static int    KiPro::_response_size;

// Simple HTML interface for use with the Ki Pro Quad recorder from AJA.
//
KiPro::KiPro(const char* name, const char* server_ip)
: _curl(NULL)
{
   _name = strdup(name);
   _server_ip = strdup(server_ip);
   _server = new HtmlUtil(_server_ip);

   _recording = False;
   _mode = 0;           // 0 => record mode, 1 => data mode
   _timecode = 0.0;
   _available_media = 0;

   curl_setup();
   _log = new KiProLog(this, DataLog::BinaryFormat, "KiPro");
}

KiPro::~KiPro()
{
   if (_name) delete _name;
   if (_server_ip) delete _server_ip;
   if (_server) delete _server;
   curl_cleanup();
}

Boolean KiPro::ready()
{
   Boolean debug = False;
   sprintf(_buf, "http://%s/config?action=get&paramid=eParamID_FormattedSerialNumber",
      _server_ip);

   dprintf("KiPro::ready() - sending %s\n", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   dprintf("KiPro::ready() response - %s\n", KiPro::_response);
   if (strlen(KiPro::_response) > 0)
      return True;
   else
      return False;
}


size_t KiPro::read_response(void *buffer, size_t size, size_t nmemb, void *userp)
{
   Boolean debug = False;

   dprintf("**** buffer len = %d, buffer = %s\n", size*nmemb, (char*)buffer);

   memcpy((void*)_response, (void*)buffer, size*nmemb);
   _response_size = size;

   return size*nmemb;
}

// Returns 0 if html request handled correctly.
//
int KiPro::time_sync()
{
   char dateset_params[200];

   // Using nanosleep to be as precise as we can be.
   // Get the current time to the nanosecond and
   // sleep to the beginning of the next minute.
   //
   timespec ts, rem;
   rem.tv_nsec = 100L;
   while (rem.tv_sec > 0L && rem.tv_nsec > 0L)
   {
      clock_gettime(CLOCK_REALTIME, &ts);
      //printf("%ld.%ld\n", ts.tv_sec, ts.tv_nsec);
      ts.tv_sec = 60 - (ts.tv_sec % 60);
      ts.tv_nsec = 1000000000L - ts.tv_nsec;

      Syslog::write("%s KiPro::time_sync() - delaying %ld:%ld seconds", _name,
         ts.tv_sec, ts.tv_nsec);
      nanosleep(&ts, &rem);
   }
   // The Ki Pro server can only be set to the closest minute
   //
   time_t now;
   time(&now);
   struct tm *now_tm = localtime((const time_t*)&now);
   sprintf(dateset_params, "paramName=eParamID_DateSet&newValue=%02d/%02d/%d %02d:%02d",
                            now_tm->tm_mon+1, now_tm->tm_mday, 1900+now_tm->tm_year,
                            now_tm->tm_hour, now_tm->tm_min);

   sprintf(_buf, "http://%s/values?eParamID_DateSet", _server_ip);

   Syslog::write("KiPro::time_sync() post url - %s, params = %s",
      _buf, dateset_params);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   curl_easy_setopt(_curl, CURLOPT_POSTFIELDS, dateset_params);
   int status = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::time_sync() response - %s", KiPro::_response);

   return status;
}

int KiPro::record_mode()
{
   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_MediaState&value=0",
      _server_ip);
   Syslog::write("KiPro::record_mode() url - %s", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::record_mode() response - %s", KiPro::_response);
   _mode = 0;
   _log->write();

   return c;
}

int KiPro::data_mode()
{
   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_MediaState&value=1",
      _server_ip);
   Syslog::write("KiPro::data_mode() - %s", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::data_mode() response - %s", KiPro::_response);
   _mode = 1;
   _log->write();

   return c;
}


// Return the timecode currently used by the recorder.
// Return value is the number of seconds since midnight.
// E.g., 36052.28 (10:00:52.28)
//
float KiPro::get_timecode()
{
   Boolean debug = False;

   // The connectionid depends on how many clients the AJA server is serving.
   // During missions on the vehicle, the id will nominally be zero, but
   // that assumes we didn't use a browser to look at config settings
   // beforehand.
   //
   // So, we'll try up to id = 4 before giving up.
   //
   Boolean found = False;
   int c, id;
   _timecode = 0.0;
   for (id = 0; id < 5; id++)
   {
      sprintf(_buf, "http://%s/config?action=wait_for_config_events&connectionid=%d",
         _server_ip, id);
      dprintf("KiPro::get_timecode() url - %s\n", _buf);

      curl_setup();
      curl_easy_setopt(_curl, CURLOPT_URL, _buf);
      int c = curl_execute(); curl_cleanup();

      dprintf("KiPro::get_timecode() response - %s\n", KiPro::_response);
      if (strstr(KiPro::_response, "InputTimecode"))
      {
         float result, hr, min, sec, csec;
         char *value = strstr(KiPro::_response, "InputTimecode");
         if (value)
         {
            if (value = strstr(value, "str_value"))
            {
               dprintf("KiPro::get_timecode() - connectionid = %d/value string %s\n", id, value);
               sscanf(value, "str_value\":\"%f:%f:%f:%f\"", &hr, &min, &sec, &csec);
               _timecode = hr*3600 + min*60 + sec + csec/100.;
               dprintf("KiPro::get_timecode() - value: %d:%d:%d:%d => %f\n",
                  (int)hr, (int)min, (int)sec, (int)csec, _timecode);
            }
            else
            {
               dprintf("KiPro::get_timecode() - \"str_value\" not found in response\n");
            }
         }
         else
         {
            Syslog::write("KiPro::get_timecode() - \"InputTimecode\" not found in response\n");
         }
         found = True;
         break;      // Success!
       }
   }
   _log->write();

   return _timecode;
}

// Set the timecode of the recorder to the given epoch seconds value.
// If the value is zero, set the timecode to the current MVC time.
// Returns the epoch seconds value used for the timecode.
//
unsigned long KiPro::set_timecode(unsigned long epoch_seconds)
{
   return 0;
}

void KiPro::connect()
{
   Boolean debug = False;

   sprintf(_buf, "http://%s/config?action=connect", _server_ip);
   Syslog::write("KiPro::connect() url - %s", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   dprintf("KiPro::connect() response - %s, return %d",
      KiPro::_response, c);

}

int KiPro::transport_state()
{
   Boolean debug = False;

   sprintf(_buf, "http://%s/config?action=get&paramid=eParamID_TransportState",
      _server_ip);
   dprintf("KiPro::transport_state() url - %s", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   dprintf("KiPro::transport_state() response - %s, return %d",
      KiPro::_response, c);

   if (strstr(KiPro::_response, "Recording"))
      _recording = 1;
   else
      _recording = 0;

   return _recording;
}

// Set the record state on or off. Return the current state
// of the recorder Transport Command (record, stop, play, ff, rw, etc.)
// state param == 0 means stop, > 0 means record
//
int KiPro::record_button(int state)
{
   if (-1 == state)
   {
      return transport_state();
   }

   record_mode();

   int bcode = state == 0? 4 : 3;
   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_TransportCommand&value=%d",
      _server_ip, bcode);
   Syslog::write("KiPro::record_button(%d) url - %s", state, _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::record_button(%d) response - %s, return %d",
      state, KiPro::_response, c);

   if (c != CURLE_OK && strstr(KiPro::_response, "Record Command"))
   {
      _recording = 0;
   }
   else
   {
      _recording = (state > 0);
   }
   _log->write();

   return _recording;
}


// Set the clip name (recorded file name) to the given string.
// A null string means to use the default value of the ISO 8601 UTC
// (e.g., "2015-05-13T10:20:00Z"). Returns success (0) or failure.
//
int KiPro::set_clip_name(const char* name)
{
#if 0
   // Use a default name based on the time if none is provided
   //
   char time_buf[32];
   if (name == NULL || strlen(name) < 1)
   {
      long now;
      time(&now);
      struct tm *default_time = localtime(&now);
      sprintf(time_buf, "%d-%02d-%02dT%02d:%02d:%02d",
         default_time->tm_year+1900,
         default_time->tm_mon+1,
         default_time->tm_mday,
         default_time->tm_hour,
         default_time->tm_min,
         default_time->tm_sec
         );
      name = time_buf;
   }
#endif
   char postfix[32];
   char time_buf[200];
   memset(postfix, '\0', sizeof(postfix));
   memset(time_buf, '\0', sizeof(time_buf));

   // Use the provided name as a postfix to a standardized naming convention
   //
   if (name && strlen(name) > 0)
      sprintf(postfix, "%s", name);

   // Create the standardized name based on the time and date
   // 
   long now;
   time(&now);
   struct tm *default_time = localtime(&now);
   sprintf(time_buf, "i2MAP_%4d%02d%02dT%02d%02d%02dZ",
      default_time->tm_year + 1900,
      default_time->tm_mon+1,
      default_time->tm_mday,
      default_time->tm_hour,
      default_time->tm_min,
      default_time->tm_sec
      );

   if (strlen(postfix) > 0) strcat(time_buf, postfix);

   // First, tell the KiPro we want to use a custom clip name
   //
   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_UseCustomClipName&value=1",
      _server_ip);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::set_clip_name(%s) response - %s", name, KiPro::_response);

   if (c != 0)
   {
      Syslog::write("KiPro::set_clip_name(%s) unexpected curl_easy_perform response code: %d",
         _name, c);
   }

   // Then, tell the KiPro the custom clip name
   //
   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_CustomClipName&value=%s",
      _server_ip, time_buf);
   Syslog::write("KiPro::set_clip_name(%s) url - %s", name, _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::set_clip_name(%s) response - %s", name, KiPro::_response);

   return c;
}

// Set the recorder reel number (1-999)
// A reel value of 0 means to use the default (julian day)
//
int KiPro::set_reel_num(unsigned int reel)
{
   if (reel == 0)
   {
      long now;
      time(&now);
      struct tm *default_time = localtime(&now);
      reel = default_time->tm_yday;
   }

   sprintf(_buf, "http://%s/config?action=set&paramid=eParamID_ReelName&value=%d",
      _server_ip, reel);
   Syslog::write("KiPro::set_reel_num(%d) url - %s", reel, _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   Syslog::write("KiPro::set_reel_num(%d) response - %s", reel, KiPro::_response);

   return c;
}

// Return the percentage of available media left on the storage devices.
// 0 - 100.
int KiPro::get_available_media()
{
   Boolean debug = False;

   sprintf(_buf, "http://%s/values?eParamID_CurrentMediaAvailable", _server_ip);
   dprintf("KiPro::get_available_media() url - %s\n", _buf);

   curl_setup();
   curl_easy_setopt(_curl, CURLOPT_URL, _buf);
   int c = curl_execute(); curl_cleanup();

   dprintf("KiPro::get_available_media() response - %s\n", KiPro::_response);

   // Find the substring 'value:' and read the result
   //
   int result = -1;
   char *value = strstr(KiPro::_response, "value:");
   dprintf("KiPro::get_available_media() - value string %s\n", value);

	if (value)
	{
     sscanf(value, "value:\"%d\"", &result);
     dprintf("KiPro::get_available_media() - value: %d\n", result);
	}
	else
     dprintf("KiPro::get_available_media() - No response from recorder\n");

   _available_media = result;
   _log->write();

   return result;
}

// This function should be called before setting the URL or post options
//
int KiPro::curl_setup()
{
    curl_cleanup();
   _curl = curl_easy_init();
   if (_curl) {
      curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, read_response);
      /* complete within TIMEOUT seconds */
      curl_easy_setopt(_curl, CURLOPT_TIMEOUT, 1L);
      curl_easy_setopt(_curl, CURLOPT_NOSIGNAL, 1L);
      curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, _errorbuf);
      return 0;
    }
    else
    {
       Syslog::write("%s KiPro::KiPro() - CURL initialization failed %d", _name);
       return -1;
    }
}

int KiPro::curl_execute()
{
   Boolean debug = False;

   _response[0] = '\0';
   int c = curl_easy_perform(_curl);
   if (c != CURLE_OK)
      dprintf("KiPro::curl_execute() curl response - %d => %s\n",
         c, _errorbuf);

   return c;
}

int KiPro::curl_cleanup()
{
   if (_curl)
   {
      curl_easy_cleanup(_curl);
      _curl = NULL;
      return 0;
   }
   return 1;   
}
