/* TherPng - Make a png from a TherMon context save or log file */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include "kernel.h"

#include "<Gd$dir>.h.gd" /* gd lib */
#include "<Gd$dir>.h.gdfonts" /* gd small font */

#define BOOL int
#define inactive_flag 0x7fff

#define max_ch 8
#define text_offset 12

#define xvals 300
#define MON_FILE 1
#define LOG_FILE 0
#define range_step 10

static char line_buf[200] ;
static char colours[8]  ;
static int white, grid, dtype = MON_FILE ;
static int margin, width, height, plot_width, points = xvals - 1 ;
static int active_ever[8] = {0, 0, 0, 0, 0, 0, 0, 0}, ch = 0;
static gdImagePtr im_out ; /* The png file as a gd image */

typedef struct {
  short int data[xvals][8] ;  /* Temperatures from 8 sensors */
  int time[xvals] ;     /* times */
  int last_x ;          /* Last valid pixel */
  int sum[8] ;          /* Sum for integration */
  int N[8] ;            /* N for integration */
  time_t tsum ;         /* time sum for integration */
  int tN ;              /* time N for integration */
  int min ;             /* Min at this mag */
  int max ;             /* Max at this mag */
  int rmin ;
  int rmax ;
} mag_struct ;

static mag_struct mag ;
static _kernel_osfile_block inout ;
static char *outname ;

static void write_png(void)
{
  /* Open png file for o/p and get GD to write it */

  FILE *out ;

  out = fopen(outname, "wb");
  /* Write png */
  if (!out)
  {
    /* Report _this_ error to stderr! */
    fprintf(stderr, "Could not create output file.\n") ;
    exit(0) ;
  }

  gdImagePng(im_out, out);
  fclose(out);
  inout.load = 0xb60 ; /* Filetype for png */
  /* settype */
  _kernel_osfile(18, outname, &inout) ;
  /* forget image */
  gdImageDestroy(im_out);
}

static void gerr(int fatal, char* format, ...)
{
  /* Print error message to the o/p png */

  char temp[256] ;

   va_list va;

   va_start(va, format);
   vsprintf(temp, format, va);
   va_end(va);

   gdImageString(im_out, gdFontSmall,
     margin,
     height - margin/2 - gdFontSmall->h/2 - 2,
     (unsigned char *) temp, colours[0]) ;

   if (fatal) {
     write_png() ;
     exit(0) ;
   }
}


static int count_colons(char *line)
{
  /* Count colons in a log line to determine time format */

  char *c ;

  c = strchr(line, ':') ;
  if (!c) return 0 ;
  ++c ;
  c = strchr(c, ':') ;
  if (!c) return 1 ;
  return 2 ;
}

static int read_line(short int *therm, FILE *fp, int line, int *time)
{
  /* Read a line from the log file and make it look like a mon entry */

  int i, done = 0 ;
  char *next, *endp, *dp ;
  struct tm tm_time ;

  if (dtype == MON_FILE) return 1 ; /* log files only */

  if (!fgets(line_buf, 200, fp)) return 0 ; /* Read the next line */
  switch (count_colons(line_buf)) /* Time format? */
  {
    case 1: /* Without seconds - dd/mm/yy hh:mm */
      if (sscanf(line_buf, "%d/%d/%d %d:%d,",
        &tm_time.tm_mday, &tm_time.tm_mon, &tm_time.tm_year, &tm_time.tm_hour, &tm_time.tm_min) != 5)
      {
        gerr(1, "Failed to read line %d.\n", line) ;
      }
      tm_time.tm_sec = 0 ;
      break ;

    case 2: /* With seconds - dd/mm/yy hh:mm:ss */
      if (sscanf(line_buf, "%d/%d/%d %d:%d:%d,",
        &tm_time.tm_mday, &tm_time.tm_mon, &tm_time.tm_year, &tm_time.tm_hour, &tm_time.tm_min, &tm_time.tm_sec) != 6)
      {
        gerr(1, "Failed to read line %d.\n", line) ;
      }
      break ;

    default:
      gerr(1, "Failed to read line %d.\nUnsuitable time format.\n", line) ;
      break ;
  }

  tm_time.tm_mon -= 1 ;            /* January is month 0 */
  *time = (int) mktime(&tm_time) ; /* Convert to unix time */

  next = strchr(line_buf, ',') ;   /* Start looking for data values */
  *next = '\0' ;     	       	   /* Terminate */
  ++next ;     			   /* Make ready for next value */

  /* Default to inactive */
  for (i=0; i<8; therm[i++] = inactive_flag) ;

  /* Decode text entries into binary integer form */
  for (i=0; ((i<8) && (!done)); ++i) /* Loop over sensors */
  {
    endp = strpbrk(next, ",\n") ; /* Find end of this value */
    if (!endp) break ;	    	  /* No end? */
    if (*endp == '\n') done = 1 ; /* Last value on line? */
    *endp = '\0' ; 	      	  /* Terminate */

    /* Internal representation is x100 */
    /* i.e. xx.xx -> xxxx */
    /* So remove decimal point and terminate after hundredths */
    dp = strchr(next, '.') ; /* Find dp */
    if (dp)
    {
      dp[0] = dp[1] ;
      dp[1] = dp[2] ;
      dp[2] = '\0' ;
      therm[i] = atoi(next) ; /* Convert to int */
      active_ever[i] = 1 ;    /* Activate key entry for this sensor */
    }
    next = ++endp ;           /* Step past the end of this value */
  }
  return 1 ;
}

static void plot_to_point(int i, int j, int x1,
  int x0, int ypix, int basepix)
{
  int y0, y1 ;

  /* Check for invalid limits */
  if (mag.min == mag.max) return ;

  /* Convert data value to pixel position */
  y1 = basepix + ypix + (ypix-1) * ((int) mag.data[j][i] - mag.min) /
       (mag.min - mag.max) ;

  /* Was there a valid previous point? */
  if ((j==0) || (mag.data[j-1][i] == inactive_flag))
  {
    /* Just plot a point here */
    gdImageSetPixel(im_out, x1, y1, colours[i]) ;
    return ;
  }

  /* Draw to last point */
  /* Convert previous data value to pixel position */
  y0 = basepix + ypix + (ypix-1) * (mag.data[j-1][i] - mag.min) /
       (mag.min - mag.max) ;

  /* Draw line */
  gdImageLine(im_out, x0, y0, x1, y1, colours[i]) ;

  return ;
}

static char *i_to_a(int val, int prec)
{
  static char vals[8], vals2[8] ;

  /* Convert xxxx integer representation of xx.xx to a string
     with the given precision */

  switch(prec)
  {
    case 0:
      /* Add / subtract for rounding */
      val += (val < 0) ? -50 : 50 ;
      val /= 100 ;
      val *= 100 ;
      break ;

    case 1:
      /* Add / subtract for rounding */
      val += (val < 0) ? -5 : 5 ;
      val /= 10 ;
      val *= 10 ;
      break ;

    case 2:
      /* Full precision */
      break ;
  }

  /* Print 4-wide, leading 0s, to string, field width 6 */
  sprintf(vals2, "% 04d", val) ;
  sprintf(vals, "%6s", vals2) ;

  /* Shift right to make room for dp */
  vals[6] = vals[5] ;
  vals[5] = vals[4] ;
  vals[4] = '.' ;

  switch (prec)
  {
    case 0:
      vals[4] = '\0' ; /* Terminate at dp */
      break ;

    case 1:
      vals[6] = '\0' ; /* Terminate after tenths */
      break ;

    case 2:
      /* Full precision */
      break ;
  }

  return vals ;
}

static size_t format_time(char *buffer, time_t t, time_t dt)
{
  struct tm *tms ;
  tms = localtime(&t) ;

  if (dt < 100) /* 10 seconds */
    return strftime(buffer, 31, "%H:%M:%S", tms) ;

  if (dt < 86400) /* 1 day */
    return strftime(buffer, 31, "%H:%M", tms) ;

  return strftime(buffer, 31, "%d,%H:%M", tms) ;
}

static void allocate_colours(void)
{
  /* First color allocated is background. */
  white = gdImageColorAllocate(im_out, 255, 255, 255) ;
  grid  = gdImageColorAllocate(im_out, 187, 187, 187) ;

  colours[0] = gdImageColorAllocate(im_out, 0, 0, 0) ;
  colours[1] = gdImageColorAllocate(im_out, 119, 119, 119) ;
  colours[2] = gdImageColorAllocate(im_out, 0, 68, 153) ;
  colours[3] = gdImageColorAllocate(im_out, 0, 204, 0) ;
  colours[4] = gdImageColorAllocate(im_out, 221, 0, 0) ;
  colours[5] = gdImageColorAllocate(im_out, 85, 136, 0) ;
  colours[6] = gdImageColorAllocate(im_out, 255, 187, 0) ;
  colours[7] = gdImageColorAllocate(im_out, 0, 187, 255) ;
}

static void draw_key(int width)
{
  /* Draw a key accross the top */

  int key_unit_width, text_indent, line_len, i, line_y, text_y ;
  char key[4] ;

  /* The width of one entry in the key... */
  key_unit_width = (width - margin) / max_ch ;
  text_indent = key_unit_width / 2 ; /* Text in RH half */
  line_len =    key_unit_width / 3 ; /* Line for 1st 1/3 */
  /* line vertically centered in key area */
  line_y = margin / 2 ;
  /* Text vertically centered on line - take off an extra h/2 */
  text_y = margin / 2 - gdFontSmall->h / 2 ;

  for (i=0; i<max_ch; ++i)
  {
    if ((ch & 1 << i) || (!active_ever[i]))
      continue ; /* Don't add key for inactive sensors */
    /* Draw the line */
    gdImageLine(im_out, margin + i * key_unit_width,
      line_y, margin + i * key_unit_width + line_len, line_y, colours[i]) ;
    /* Key text */
    sprintf(key, "%d", i) ;
    /* Add text */
    gdImageString(im_out, gdFontSmall,
      margin + i * key_unit_width + text_indent,
      text_y,
      (unsigned char *) key, colours[0]) ;
  }
}

static int scan_log(FILE *fp)
{
  char line_buf[200] ; /* String buffer for reading */
  int t0 = 0, line = 1, i, min, max ;

  mag.max = -100000 ;
  mag.min =  100000 ;

  if (!fgets(line_buf, 200, fp)) return 0 ; /* Read the first line */
  while (read_line(&(mag.data[xvals-1][0]), fp, line,
    &(mag.time[xvals-1])))
  {
    if (t0 == 0) t0 = mag.time[xvals-1] ;
    for (i=0; i<8; ++i)
    {
      if (mag.data[xvals-1][i] == inactive_flag) continue ;
      if (mag.data[xvals-1][i] > mag.max) mag.max = mag.data[xvals-1][i] ;
      if (mag.data[xvals-1][i] < mag.min) mag.min = mag.data[xvals-1][i] ;
    }
    ++line ;
  }

  rewind(fp) ;
  if (!fgets(line_buf, 200, fp)) return 0 ; /* Read the first line */

  mag.time[0] = t0 ;
  mag.last_x = line - 2 ;
  max = range_step*(mag.max/range_step) ;
  mag.max = (max < mag.max) ? max + range_step : max ;
  min = range_step*(mag.min/range_step) ;
  mag.min = (min > mag.min) ? min - range_step : min ;

  return 1 ;
}

int main(int argc, char *argv[])
{
  int i, x, xx, row, x0, x1, y0, y1, line = 1, end ;

  FILE *data ;

  end = xvals-1 ;
  /* Check for correct usage */
  if (argc < 6)
  {
    fprintf(stderr, "Usage: thermpng in_data out_png type (mon / log) width height channels\n") ;
    exit(0) ;
  }

  outname = argv[2] ; /* Name of png */

  /* Determine dimensions */
  width  = atoi(argv[4]) ;
  height = atoi(argv[5]) ;

  /* Channels to switch off */
  if (argc > 6) ch = atoi(argv[6]) ;

  row = (width + 1) / 2 ;
  if (row % 4) row += 4 - row % 4 ;

  margin = (3 * gdFontSmall->h) / 2 ;
  plot_width = width - margin ;

  /* Create the image inb GD */

  im_out = gdImageCreate(width, height) ;
  if (!im_out)
  {
    fprintf(stderr, "Failed to create image.\n") ;
    exit(0) ;
  }

  /* Image now exists - all further user messages to image */

  /* Set up all the colours we will need */
  allocate_colours() ;

  /* M or m => monitor file - anything else => log file */
  if (tolower(argv[3][0]) == 'm') dtype = MON_FILE ;
  else dtype = LOG_FILE ;

  if (dtype == MON_FILE)
  {
    /* Check that file is right length to be real mon file */
    if (_kernel_osfile(17, argv[1], &inout) != 1)
      gerr(1, "%s - file not found", argv[1]) ;

    if (inout.start != sizeof(mag_struct))
    {
      gerr(1, "%s is not a TherMon file.", argv[1]) ;
      exit(0) ;
    }
  }

  /* Open input file */
  data = fopen(argv[1], "rb") ;

  if (!data) /* Did input file open? */
  {
    gerr(1, "%s - open failed", argv[1]) ;
  }
  else
  {
    /* Load the data file if it's a MON */

    if (dtype == MON_FILE)
    {
      /* Mon file - just dump into memory */
      fread((void *) &mag, sizeof(mag_struct), 1, data) ;
      fclose(data) ;
      if (mag.last_x < end) end = mag.last_x ;
    }
    else
    {
      /* Log file - parse it for min / max */
      if (!scan_log(data))
      {
        fclose(data) ;
        exit(0) ;
      }
      end = mag.last_x ;
      points = end + 1 ;

      /* File will be closed later */
    }

    xx = line = x = 0 ;

    x0 = margin ;
    x1 = margin + plot_width  * (points - 1)/points ;
    y0 = margin ;
    y1 = margin + (height - 2 * margin - 1) ;

    /* Plot the grid */
    gdImageDashedLine(im_out, x0, y0, x1, y0, grid) ;
    gdImageDashedLine(im_out, x1, y0, x1, y1, grid) ;
    gdImageDashedLine(im_out, x1, y1, x0, y1, grid) ;
    gdImageDashedLine(im_out, x0, y1, x0, y0, grid) ;

    /* Plot the data
       read_line does nothing for MON data
       but populates mag from log file */

    while ((x<=end) && (read_line(mag.data[xx], data, x,
    &(mag.time[xx]))))
    {
      /* Horizontal pixel positions for this x and previous */
      x1 = margin + (plot_width * x)/points ;
      x0 = margin + (plot_width * (x-1))/points ;
      for (i=0; i<8; ++i) /* Loop over sensors */
      {
        if (!(ch & 1 << i) && (mag.data[xx][i] != inactive_flag))
        {
          active_ever[i] = 1 ; /* Flagged for key generation */
          /* Now draw line or point */
          plot_to_point(i, xx, x1, x0,
            height - 2 * margin, margin) ;
        }
      }
      ++x ;
      if (dtype == LOG_FILE)
      {
        if (xx > 0)
        {
          /* Copy latest values over previous - to use on next x */
          for (i=0; i<8; ++i) /* Loop over sensors */
          {
            mag.data[0][i] = mag.data[1][i] ;
          }
        }
        xx = 1 ;
      }
      else ++xx ;
    }

    if (dtype == MON_FILE) --xx ; /* Last valid point */

    if (dtype == LOG_FILE) fclose(data) ; /* A log file must be closed */

    /* Add the key */
    draw_key(width) ;

    /* Add min / max */
    strcpy(line_buf, i_to_a(mag.min, 1)) ;
    gdImageStringUp(im_out, gdFontSmall,
      margin / 2 - gdFontSmall->h / 2,
      height - margin  + (strlen(line_buf) * gdFontSmall->w) / 3,
      (unsigned char *) line_buf, colours[0]) ;
    strcpy(line_buf, i_to_a(mag.max, 1)) ;
    gdImageStringUp(im_out, gdFontSmall,
      margin / 2 - gdFontSmall->h / 2,
      margin  + (2 * strlen(line_buf) * gdFontSmall->w) / 3,
      (unsigned char *) line_buf, colours[0]) ;
    format_time(line_buf, mag.time[0],
      mag.time[xx] - mag.time[0]) ;
    gdImageString(im_out, gdFontSmall,
      margin,
      height - margin/2 - gdFontSmall->h/2 + 2,
      (unsigned char *) line_buf, colours[0]) ;
    format_time(line_buf, mag.time[xx],
      mag.time[xx] - mag.time[0]) ;
    gdImageString(im_out, gdFontSmall,
      width - strlen(line_buf)*gdFontSmall->w - 2,
      height - margin/2 - gdFontSmall->h/2 + 2,
      (unsigned char *) line_buf, colours[0]) ;
  }

  write_png() ;

  return 0 ;
}
