/***************************************************/
/* File   : rc5graph.c                             */
/*                                                 */
/* Purpose: statistics graph plotting.             */
/*                                                 */
/* Author : D.Brown                                */
/***************************************************/

//#define DEBUG

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#include "os.h"
#include "colourtrans.h"
#include "wimp.h"

#include "rc5graph.h"

static int _timezone;

/*************************************************/
/* new_graph()                                   */
/*************************************************/

graph_window_data *new_graph(void)
{
  graph_window_data *newwindow;
  struct tm local, gmt;
  time_t t;

  newwindow = malloc(sizeof(graph_window_data));
  if (!newwindow) return NULL;

  newwindow->mintime   = 0;
  newwindow->maxtime   = 0;
  newwindow->minrate   = 0;
  newwindow->maxrate   = 0;

  newwindow->head      = NULL;
  newwindow->tail      = NULL;

  t = time(NULL);
  local = *localtime(&t);
  gmt = *gmtime(&t);
  _timezone = mktime(&gmt) - mktime(&local);

  return newwindow;
}

/*************************************************/
/* free_list()                                   */
/*************************************************/

void free_list(graph_window_data *data)
{
  MyGraphEntry *ge, *tempge;

  ge = data->head;

  /* Free list */
  while(ge)
  {
    tempge = ge->next;
    free(ge);
    ge = tempge;
  }

  data->head = NULL;
  data->tail = NULL;
}

/*************************************************/
/* delete_graph()                                */
/*************************************************/

void delete_graph(graph_window_data *data)
{
  if (!data) return;

  free_list(data);
  free(data);
}

/*************************************************/
/* add_to_tail()                                 */
/*************************************************/

static bool add_to_tail(graph_window_data * data, MyGraphEntry * ge)
{
  MyGraphEntry *nge;

  if (!data) return TRUE; /* ERROR */
  if (!ge)   return TRUE; /* ERROR */

  nge = malloc(sizeof(MyGraphEntry));
  if (!nge) return TRUE; /* ERROR */

  memcpy(nge, ge, sizeof(MyGraphEntry));

  if (data->head == NULL)
  {
    data->head = nge;
    data->tail = nge;
  }
  else
  {
    data->tail->next = nge;
    data->tail       = nge;
  }

  nge->next   = NULL;

  return FALSE;
}

/*************************************************/
/* parse_timestamp()                             */
/*************************************************/

static time_t parse_timestamp(char *stamp)
{
  struct tm t;
  time_t result;
  if (isdigit(*stamp))
  {
    if (sscanf(stamp,
               "%u/%u/%u %u:%u:%u",
               &t.tm_mon, &t.tm_mday, &t.tm_year,
               &t.tm_hour, &t.tm_min, &t.tm_sec) != 6)
    {
      return 0;
    }
  }
  else
  {
    int i;
    char monthname[20];
    static char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
    if (sscanf(stamp, "%5s %u %u:%u:%u",
      monthname, &t.tm_mday,
      &t.tm_hour, &t.tm_min, &t.tm_sec) != 5) return 0;
    t.tm_mon = 0;
    for (i = 0; i < 12; i++)
      if (strcmp(monthname, months[i]) == 0) { t.tm_mon = (i + 1); break; }
    t.tm_year = 1998;    // hard coded for now
  }

  /* correct the date to a full 4-digit year */
  if      (t.tm_year < 0)   return 0;
  else if (t.tm_year < 70)  t.tm_year += 2000;
  else if (t.tm_year < 100) t.tm_year += 1900;

  /* validate all fields */
  if (t.tm_mon  < 1    || t.tm_mon  > 12    ||
      t.tm_mday < 1    || t.tm_mday > 31    ||
      t.tm_year < 1970 || t.tm_year >= 2038 ||
      t.tm_hour < 0    || t.tm_hour > 23    ||
      t.tm_min  < 0    || t.tm_min  > 59    ||
      t.tm_sec  < 0    || t.tm_sec  > 59)
  {
    return 0;
  }

  /* convert the fields to the tm standard */
  t.tm_mon--;
  t.tm_year -= 1900;
  result = mktime(&t);

  if (result == -1) return 0;
  else              return (result - _timezone);
}

/*************************************************/
/* parse_duration()                              */
/*************************************************/

float parse_duration(char *stamp)
{
  int days = 0, hours, mins;
  float secs;
  if (sscanf(stamp, "%d:%d:%f", &hours, &mins, &secs) != 3)
  {
    if (sscanf(stamp, "%d.%d:%d:%f", &days, &hours, &mins, &secs) != 4)
    {
      return 0;
    }
  }
  return (((float) days * 24.0F + (float) hours) * 60.0F + (float) mins) * 60.0F + secs;
}

/*************************************************/
/* read_log_data()                               */
/*************************************************/

bool read_log_data(graph_window_data *data, char * filename)
{
  int   gotfirst = 0;
  char  linebuffer[500];
  char *q, *r, *t;
  FILE *fp;
  time_t lasttimestamp = 0, timestampadd = 0;

  data->mintime = 0;
  data->maxtime = 0;
  data->minrate = 0;
  data->maxrate = 0;

  fp = fopen(filename, "r");

  if (!fp) return TRUE; /* ERROR */

  while(!feof(fp))
  {
    if (!fgets(linebuffer, sizeof(linebuffer), fp)) break;

    if (linebuffer[0] == '[' && strstr(linebuffer, "] Completed "))
    {
      /* Parse first line */
      MyGraphEntry ge;

      ge.timestamp = parse_timestamp(linebuffer + 1);
      #ifdef DEBUG
        printf("timestamp = %d\n", ge.timestamp);
      #endif

      if (ge.timestamp == 0) continue;

      if (lasttimestamp != 0)
      {
        ge.timestamp += timestampadd;
        if (ge.timestamp + (6*30*24*60*60) < lasttimestamp)
        {
          ge.timestamp -= timestampadd;
          timestampadd += 365*24*60*60;
          ge.timestamp += timestampadd;
        }
      }
      lasttimestamp = ge.timestamp;
      /*p = strchr(linebuffer, '(');
      if (p == NULL) continue;
      ge.keycount = (double) atol(p + 1);
      #ifdef DEBUG
        printf("keycount = %f\n", ge.keycount);
      #endif*/

      /* Parse second line */
      if (!fgets(linebuffer, sizeof(linebuffer), fp)) break;
      q = strchr(linebuffer, '[');
      if (q == NULL) continue;
      r = strchr(q+1, '[');  /* with newer logs, we need the second [ */
      if (r != NULL) q=r;
      while (strchr(q+1,',') != NULL) /* get rid of commas */
      {
        char *s=strchr(q+1,',');
        strcpy(s,s+1);
      }
      ge.rate = atof(q + 1);
      #ifdef DEBUG
        printf("rate = %f\n", ge.rate);
      #endif

      t = strchr(linebuffer,']'); /* search for end of timestamp */
      if (t < q) /* new log format w/timestamp */
        ge.duration = parse_duration(t+1);
      else if (t > q) /* old log format w/o timestamp */
        ge.duration = parse_duration(linebuffer);

      #ifdef DEBUG
        printf("duration = %f\n", ge.duration);
      #endif

      /* add to our list */
      if (add_to_tail(data, &ge)) return TRUE;

      if (!gotfirst)
      {
        data->mintime = ge.timestamp;
        data->maxtime = ge.timestamp;
        data->minrate = ge.rate;
        data->maxrate = ge.rate;
        gotfirst = 1;
      }
      else
      {
        if (ge.timestamp < data->mintime) data->mintime = ge.timestamp;
        if (ge.timestamp > data->maxtime) data->maxtime = ge.timestamp;
        if (ge.rate      < data->minrate) data->minrate = ge.rate;
        if (ge.rate      > data->maxrate) data->maxrate = ge.rate;
      }
    }
  }
  fclose(fp);

  if (data->head == NULL             ||
      data->minrate == data->maxrate ||
      data->mintime == data->maxtime)
  {
    return TRUE; /* ERROR Nothing to draw */
  }

  return FALSE;
}



/*************************************************/
/* render_window()                               */
/*************************************************/

#define HEIGHT(bbox) ((bbox)->y1 - (bbox)->y0)
#define WIDTH(bbox)  ((bbox)->x1 - (bbox)->x0)

#define CONVX(x) ((int)(((x) / xscale) + bbox->x0))
#define CONVY(y) ((int)(((y) / yscale) + bbox->y0))

#define CHARWIDTH  (16)
#define CHARHEIGHT (32)

static const char dotpattern[] = {23, 6, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0};

static int text_width(const char *string)
{
  int width;

  if (xwimptextop_string_width(string, -1, &width))
    width = strlen(string) * CHARWIDTH;

  return width;
}

static void plot_text(const char *string, int x, int y)
{
  if (xwimptextop_paint(wimptextop_GIVEN_BASELINE, string, x, y-28))
  {
    os_plot(os_MOVE_TO, x, y);
    os_write0(string);
  }
}

static void plot_text_right(const char *string, int x, int y)
{
  if (xwimptextop_paint(wimptextop_RJUSTIFY|wimptextop_GIVEN_BASELINE, string, x, y-28))
  {
    os_plot(os_MOVE_TO, x - strlen(string) * CHARWIDTH, y);
    os_write0(string);
  }
}

static void set_text_colour(os_colour fg, os_colour bg)
{
  if (xwimptextop_set_colour(fg, bg))
    colourtrans_set_gcol(fg, 0, os_ACTION_OVERWRITE, NULL);
}

bool render_window(graph_window_data *data, os_box *bbox)
{
  char           tempstring[32];
  MyGraphEntry * ge;
  int            donefirst = 0;
  time_t         lasttime;
  float          lastrate;
  float          xscale, yscale;
  float          rcount;
  float          xinterval, yinterval;
  struct tm    * gmt;
  int            intervals, yintervals;
  int            count;
  int            dx, dy;

  os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR, &dx);
  os_read_mode_variable(os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR, &dy);

  dx = 1 << dx; dy = 1 << dy;

  if (data->head == NULL             ||
      data->minrate == data->maxrate ||
      data->mintime == data->maxtime)
  {
    return TRUE; /* Nothing to draw */
  }

  colourtrans_set_gcol(0xdddddd00, 0, os_ACTION_OVERWRITE, NULL);
  os_plot(os_MOVE_TO,                   bbox->x0,         bbox->y0 + CHARHEIGHT * 2 + 16 - dy * 2);
  os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, bbox->x0 + CHARWIDTH*10 - dx * 2, bbox->y1);
  os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, bbox->x1,         bbox->y1 - (CHARHEIGHT * 2 + 16) + dy * 2);
  os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, bbox->x1 - CHARWIDTH*4 +  dx * 2,  bbox->y0);
  os_plot(os_PLOT_TO|os_PLOT_RECTANGLE, bbox->x0,         bbox->y0 + CHARHEIGHT * 2 + 16 - dy * 2);

  bbox->y0 += (CHARHEIGHT * 2) + 16;
  bbox->x0 += CHARWIDTH  * 10;
  bbox->y1 -= CHARHEIGHT * 2 + 16;
  bbox->x1 -= CHARWIDTH * 4;

  xscale = (float)(data->maxtime - data->mintime) / (float)WIDTH(bbox);
  yscale = (float)(data->maxrate - data->minrate) / (float)HEIGHT(bbox);

  xinterval = xscale * (CHARWIDTH * 10);
  intervals = (int)(WIDTH(bbox) * (xscale / xinterval) + 0.5F);
  if (intervals<1) intervals = 1;
  xinterval = (float) (data->maxtime - data->mintime) / intervals;

  yinterval = yscale * CHARHEIGHT * 4;
  yintervals = (int)(HEIGHT(bbox) * (yscale / yinterval) + 0.5F);
  if (yintervals<1) yintervals = 1;
  yinterval = (data->maxrate - data->minrate) / yintervals;

  colourtrans_set_gcol(0x00000000, 0, os_ACTION_OVERWRITE, NULL);

  os_plot(os_MOVE_TO, bbox->x0-dx, bbox->y1+dy);
  os_plot(os_PLOT_TO, bbox->x0-dx, bbox->y0-dy);
  os_plot(os_PLOT_TO, bbox->x1+dx, bbox->y0-dy);
  os_plot(os_PLOT_TO, bbox->x1+dx, bbox->y1+dy);
  os_plot(os_PLOT_TO, bbox->x0-dx, bbox->y1+dy);

  set_text_colour(0x00000000, 0xdddddd00);

  plot_text_right("kkeys/sec", bbox->x0, bbox->y1 + CHARHEIGHT*2 + 4);
//  plot_text("Block key rate", (((bbox->x1 - bbox->x0) / 2) + bbox->x0) - (strlen("Block key rate") * CHARWIDTH)/2, bbox->y1 + CHARHEIGHT*2);

  for(count = yintervals; count >= 0; count--)
  {
    rcount = count * yinterval;
    sprintf(tempstring, "%.1f", (rcount + data->minrate));// * 0.001F);
    plot_text(tempstring, bbox->x0 - text_width(tempstring) - CHARWIDTH, CONVY(rcount) + CHARHEIGHT / 2 + 4);
  }

  for(count = 0; count <= intervals; count ++)
  {
    time_t tcount = (time_t) (count * xinterval + 0.5F);
    time_t temptime = tcount + data->mintime;
    int x = CONVX(tcount);

    gmt = gmtime(&temptime);
    strftime(tempstring, sizeof(tempstring), "%b %d", gmt);
    plot_text(tempstring, x - (text_width(tempstring)/2), bbox->y0 - 12);
    strftime(tempstring, sizeof(tempstring), "%H:%M", gmt);
    plot_text(tempstring, x - (text_width(tempstring)/2), (bbox->y0 - CHARHEIGHT) - 12);
  }

  colourtrans_set_gcol(0xeeeeee00, colourtrans_USE_ECFS, os_ACTION_OVERWRITE, NULL);
  colourtrans_set_gcol(0xffffff00, os_GCOL_SET_BG, os_ACTION_OVERWRITE, NULL);

  os_plot(os_MOVE_TO, bbox->x0, bbox->y1);
  for(count = 0; count < intervals; count++)
  {
    int x1 = (count == intervals-1) ? bbox->x1 : CONVX((count+1) * xinterval + 0.5F);

    if (x1 > bbox->x1) x1 = bbox->x1;

    os_plot((count & 1) ?  os_PLOT_RECTANGLE|os_PLOT_BG_TO : os_PLOT_RECTANGLE|os_PLOT_TO, x1, (count & 1) ? bbox->y1 : bbox->y0);
  }

  os_writen(dotpattern, 10);
  colourtrans_set_gcol(0x00000000, 0, os_ACTION_OVERWRITE, NULL);

  for(count = 1; count < yintervals; count++)
  {
    int y = CONVY(count * yinterval);

    os_plot(os_MOVE_TO,                bbox->x0, y);
    os_plot(os_PLOT_DOTTED|os_PLOT_TO, bbox->x1, y);
  }

  colourtrans_set_gcol(0x0000ff00, 0, os_ACTION_OVERWRITE, NULL);

  ge = data->head;

  while(ge)
  {
    time_t ts;
    float rate;
    int cts, crate;

    ts   = ge->timestamp - data->mintime;
    rate = ge->rate - data->minrate;

    cts = CONVX(ts); crate = CONVY(rate);

    if (!donefirst)
    {
      os_plot(os_MOVE_TO, cts, crate);
      donefirst = 1;
    }
    else
    {
      if (((float)ts - lasttime) > ((1.25F * ge->duration) + 300.0F))
      {
        int c0 = CONVY(0);

        os_plot(os_PLOT_TO, CONVX(lasttime + 0.25F * ge->duration), c0);
        os_plot(os_MOVE_TO, CONVX(ts       - 0.25F * ge->duration), c0);
      }
      os_plot(os_PLOT_TO, cts,             crate);
    }
    lasttime = ts;
    lastrate = rate;
    ge = ge->next;
  }

  return FALSE;
}
