#include "bonnie.h"
#include <stdio.h>
#include <vector>
#include <string.h>
#include <math.h>

// Maximum number of items expected on a csv line
#define MAX_ITEMS 48
#ifndef OS2
using namespace std;
#endif
typedef vector<PCCHAR> STR_VEC;

#ifndef HAVE_MIN_MAX
#if defined(HAVE_ALGO_H) || defined(HAVE_ALGO)
#ifdef HAVE_ALGO
#include <algo>
#else
#include <algo.h>
#endif
#else
#define min(XX,YY) ((XX) < (YY) ? (XX) : (YY))
#define max(XX,YY) ((XX) > (YY) ? (XX) : (YY))
#endif
#endif

vector<STR_VEC> data;
typedef PCCHAR * PPCCHAR;
PPCCHAR * props;

// Print the start of the HTML file
// return the number of columns space in the middle
int header();
// Splits a line of text (CSV format) by commas and adds it to the list to
// process later.  Doesn't keep any pointers to the buf...
void read_in(CPCCHAR buf);
// print line in the specified line from columns start..end as a line of a
// HTML table
void print_a_line(int num, int start, int end);
// print a single item of data
void print_item(int num, int item);
// Print the end of the HTML file
void footer();
// Calculate the colors for backgrounds
void calc_vals();
// Returns a string representation of a color that maps to the value.  The
// range of values is 0..range_col and val is the value.  If reverse is set
// then low values are green and high values are red.
PCCHAR get_col(double range_col, double val, bool reverse, CPCCHAR extra);

typedef enum { eNoCols, eSpeed, eCPU, eLatency } VALS_TYPE;
const VALS_TYPE vals[MAX_ITEMS] =
  { eNoCols,eNoCols,eNoCols,eNoCols,eNoCols,eNoCols,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,
    eNoCols,eNoCols,eNoCols,eNoCols,eNoCols,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,eSpeed,eCPU,
    eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency,eLatency };

bool col_used[MAX_ITEMS];
#define COL_NAME 2
#define COL_CONCURRENCY 3
#define COL_FILE_SIZE 5
#define COL_DATA_CHUNK_SIZE 6
#define COL_PUTC 7
#define COL_NUM_FILES 19
#define COL_MAX_SIZE 20
#define COL_MIN_SIZE 21
#define COL_NUM_DIRS 22
#define COL_FILE_CHUNK_SIZE 23
#define COL_RAN_DEL_CPU 35
#define COL_PUTC_LATENCY 36
#define COL_SEEKS_LATENCY 41
#define COL_SEQ_CREATE_LATENCY 42
#define COL_RAN_DEL_LATENCY 47

void usage()
{
  exit(1);
}

int main(int argc, char **argv)
{
  unsigned int i;
  for(i = 0; i < MAX_ITEMS; i++)
    col_used[i] = false;

  char buf[1024];

  FILE *fp = NULL;
  if(argc > 1)
  {
    fp = fopen(argv[1], "r");
    if(!fp)
      usage();
  }
  while(fgets(buf, sizeof(buf), fp ? fp : stdin))
  {
    buf[sizeof(buf) - 1] = '\0';
    strtok(buf, "\r\n");
    read_in(buf);
  }

  props = new PPCCHAR[data.size()];
  for(i = 0; i < data.size(); i++)
  {
    props[i] = new PCCHAR[MAX_ITEMS];
    props[i][0] = NULL;
    props[i][1] = NULL;
    props[i][COL_NAME] = "bgcolor=\"#FFFFFF\" class=\"rowheader\"><FONT SIZE=+1";
    int j;
    for(j = COL_CONCURRENCY; j < MAX_ITEMS; j++)
    {
      if( (j >= COL_NUM_FILES && j <= COL_FILE_CHUNK_SIZE) || j <= COL_DATA_CHUNK_SIZE )
      {
        props[i][j] = "class=\"size\" bgcolor=\"#FFFFFF\"";
      }
      else
      {
        props[i][j] = NULL;
      }
    }
  }
  calc_vals();
  int mid_width = header();
  for(i = 0; i < data.size(); i++)
  {
// First print the average speed line
    printf("<TR>");
    print_item(i, COL_NAME);
    if(col_used[COL_CONCURRENCY] == true)
      print_item(i, COL_CONCURRENCY);
    print_item(i, COL_FILE_SIZE); // file_size
    if(col_used[COL_DATA_CHUNK_SIZE] == true)
      print_item(i, COL_DATA_CHUNK_SIZE);
    print_a_line(i, COL_PUTC, COL_NUM_FILES);
    if(col_used[COL_MAX_SIZE])
      print_item(i, COL_MAX_SIZE);
    if(col_used[COL_MIN_SIZE])
      print_item(i, COL_MIN_SIZE);
    if(col_used[COL_NUM_DIRS])
      print_item(i, COL_NUM_DIRS);
    if(col_used[COL_FILE_CHUNK_SIZE])
      print_item(i, COL_FILE_CHUNK_SIZE);
    print_a_line(i, COL_FILE_CHUNK_SIZE + 1, COL_RAN_DEL_CPU);
    printf("</TR>\n");
// Now print the latency line
    printf("<TR>");
    print_item(i, COL_NAME);
    int lat_width = 1;
    if(col_used[COL_DATA_CHUNK_SIZE] == true)
      lat_width++;
    if(col_used[COL_CONCURRENCY] == true)
      lat_width++;
    printf("<TD class=\"size\" bgcolor=\"#FFFFFF\" COLSPAN=%d>Latency</TD>"
         , lat_width);
    print_a_line(i, COL_PUTC_LATENCY, COL_SEEKS_LATENCY);
    int bef_lat_width;
    lat_width = 1;
    if(mid_width > 1)
      lat_width = 2;
    bef_lat_width = mid_width - lat_width;
    if(bef_lat_width)
      printf("<TD COLSPAN=%d></TD>", bef_lat_width);
    printf("<TD class=\"size\" bgcolor=\"#FFFFFF\" COLSPAN=%d>Latency</TD>", lat_width);
    print_a_line(i, COL_SEQ_CREATE_LATENCY, COL_RAN_DEL_LATENCY);
    printf("</TR>\n");
  }
  footer();
  return 0;
}

typedef struct { double val; int pos; int col_ind; } ITEM;
typedef ITEM * PITEM;

int compar(const void *a, const void *b)
{
  double a1 = PITEM(a)->val;
  double b1 = PITEM(b)->val;
  if(a1 < b1)
    return -1;
  if(a1 > b1)
    return 1;
  return 0;
}

void calc_vals()
{
  ITEM *arr = new ITEM[data.size()];
  for(unsigned int column_ind = 0; column_ind < MAX_ITEMS; column_ind++)
  {
    switch(vals[column_ind])
    {
    case eNoCols:
    {
      for(unsigned int row_ind = 0; row_ind < data.size(); row_ind++)
      {
        if(data[row_ind][column_ind] && strlen(data[row_ind][column_ind]))
          col_used[column_ind] = true;
      }
    }
    break;
    case eCPU:
      // gotta add support for CPU vals.  This means sorting on previous-col
      // divided by this column.  If a test run takes twice the CPU but does
      // three times the work it should be green!  If it does half the work but
      // uses the same CPU it should be red!
    break;
    case eSpeed:
    case eLatency:
    {
      for(unsigned int row_ind = 0; row_ind < data.size(); row_ind++)
      {
        arr[row_ind].val = 0.0;
        if(data[row_ind].size() <= column_ind
         || sscanf(data[row_ind][column_ind], "%lf", &arr[row_ind].val) == 0)
          arr[row_ind].val = 0.0;
        if(vals[column_ind] == eLatency && arr[row_ind].val != 0.0)
        {
          if(strstr(data[row_ind][column_ind], "ms"))
            arr[row_ind].val *= 1000.0;
          else if(!strstr(data[row_ind][column_ind], "us"))
            arr[row_ind].val *= 1000000.0; // is !us && !ms then secs!
        }
        arr[row_ind].pos = row_ind;
      }
      qsort(arr, data.size(), sizeof(ITEM), compar);
      int col_count = -1;
      double min_col = -1.0, max_col = -1.0;
      for(unsigned int sort_ind = 0; sort_ind < data.size(); sort_ind++)
      {
        // if item is different from previous or if the first row
        // (sort_ind == 0) then increment col count
        if(sort_ind == 0 || arr[sort_ind].val != arr[sort_ind - 1].val)
        {
          if(arr[sort_ind].val != 0.0)
          {
            col_count++;
            if(min_col == -1.0)
              min_col = arr[sort_ind].val;
            else
              min_col = min(arr[sort_ind].val, min_col);
            max_col = max(max_col, arr[sort_ind].val);
          }
        }
        arr[sort_ind].col_ind = col_count;
      }
      // if more than 1 line has data then calculate colors
      if(col_count > 0)
      {
        double divisor = max_col / min_col;
        if(divisor < 2.0)
        {
          double mult = sqrt(2.0 / divisor);
          max_col *= mult;
          min_col /= mult;
        }
        double range_col = max_col - min_col;
        for(unsigned int sort_ind = 0; sort_ind < data.size(); sort_ind++)
        {
          if(arr[sort_ind].col_ind > -1)
          {
            bool reverse = false;
            PCCHAR extra = "";
            if(vals[column_ind] != eSpeed)
            {
              reverse = true;
              extra = " COLSPAN=2";
            }
            props[arr[sort_ind].pos][column_ind]
                  = get_col(range_col, arr[sort_ind].val - min_col, reverse, extra);
          }
          else if(vals[column_ind] != eSpeed)
          {
            props[arr[sort_ind].pos][column_ind] = "COLSPAN=2";
          }
        }
      }
      else
      {
        for(unsigned int sort_ind = 0; sort_ind < data.size(); sort_ind++)
        {
          if(vals[column_ind] == eLatency)
          {
            props[sort_ind][column_ind] = "COLSPAN=2";
          }
        }
      }
    }
    break;
    } // end switch
  }
}

PCCHAR get_col(double range_col, double val, bool reverse, CPCCHAR extra)
{
  if(reverse)
    val = range_col - val;
  const int buf_len = 256;
  PCHAR buf = new char[buf_len];
  int green = int(255.0 * val / range_col);
  green = min(green, 255);
  int red = 255 - green;
  _snprintf(buf
#ifndef NO_SNPRINTF
          , buf_len
#endif
          , "bgcolor=\"#%02X%02X00\"%s", red, green, extra);
  buf[buf_len - 1] = '\0';
  return buf;
}

void heading(const char * const head);

int header()
{
  int vers_width = 2;
  if(col_used[COL_DATA_CHUNK_SIZE] == true)
    vers_width++;
  if(col_used[COL_CONCURRENCY] == true)
    vers_width++;
  int mid_width = 1;
  if(col_used[COL_MAX_SIZE])
    mid_width++;
  if(col_used[COL_MIN_SIZE])
    mid_width++;
  if(col_used[COL_NUM_DIRS])
    mid_width++;
  if(col_used[COL_FILE_CHUNK_SIZE])
    mid_width++;
  printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">"
"<HTML>"
"<HEAD><TITLE>Bonnie++ Benchmark results</TITLE>"
"<STYLE type=\"text/css\">"
"TD.header {text-align: center; backgroundcolor: \"#CCFFFF\" }"
"TD.rowheader {text-align: center; backgroundcolor: \"#CCCFFF\" }"
"TD.size {text-align: center; backgroundcolor: \"#CCCFFF\" }"
"TD.ksec {text-align: center; fontstyle: italic }"
"</STYLE>"
"<BODY>"
"<TABLE ALIGN=center BORDER=3 CELLPADDING=2 CELLSPACING=1>"
"<TR><TD COLSPAN=%d class=\"header\"><FONT SIZE=+1><B>"
"Version " BON_VERSION
"</B></FONT></TD>"
"<TD COLSPAN=6 class=\"header\"><FONT SIZE=+2><B>Sequential Output</B></FONT></TD>"
"<TD COLSPAN=4 class=\"header\"><FONT SIZE=+2><B>Sequential Input</B></FONT></TD>"
"<TD COLSPAN=2 ROWSPAN=2 class=\"header\"><FONT SIZE=+2><B>Random<BR>Seeks</B></FONT></TD>"
"<TD COLSPAN=%d class=\"header\"></TD>"
"<TD COLSPAN=6 class=\"header\"><FONT SIZE=+2><B>Sequential Create</B></FONT></TD>"
"<TD COLSPAN=6 class=\"header\"><FONT SIZE=+2><B>Random Create</B></FONT></TD>"
"</TR>\n"
"<TR>", vers_width, mid_width);
  if(col_used[COL_CONCURRENCY] == true)
    printf("<TD COLSPAN=2>Concurrency</TD>");
  else
    printf("<TD></TD>");
  printf("<TD>Size</TD>");
  if(col_used[COL_DATA_CHUNK_SIZE] == true)
    printf("<TD>Chunk Size</TD>");
  heading("Per Char"); heading("Block"); heading("Rewrite");
  heading("Per Char"); heading("Block");
  printf("<TD>Num Files</TD>");
  if(col_used[COL_MAX_SIZE])
    printf("<TD>Max Size</TD>");
  if(col_used[COL_MIN_SIZE])
    printf("<TD>Min Size</TD>");
  if(col_used[COL_NUM_DIRS])
    printf("<TD>Num Dirs</TD>");
  if(col_used[COL_FILE_CHUNK_SIZE])
    printf("<TD>Chunk Size</TD>");
  heading("Create"); heading("Read"); heading("Delete");
  heading("Create"); heading("Read"); heading("Delete");
  printf("</TR>");

  printf("<TR><TD COLSPAN=%d></TD>", vers_width);

  int i;
  CPCCHAR ksec_form = "<TD class=\"ksec\"><FONT SIZE=-2>%s/sec</FONT></TD>"
                      "<TD class=\"ksec\"><FONT SIZE=-2>%% CPU</FONT></TD>";
  for(i = 0; i < 5; i++)
  {
    printf(ksec_form, "K");
  }
  printf(ksec_form, "");
  printf("<TD COLSPAN=%d></TD>", mid_width);
  for(i = 0; i < 6; i++)
  {
    printf(ksec_form, "");
  }
  printf("</TR>\n");
  return mid_width;
}

void heading(const char * const head)
{
  printf("<TD COLSPAN=2>%s</TD>", head);
}

void footer()
{
  printf("</TABLE>\n</BODY></HTML>\n");
}

STR_VEC split(CPCCHAR delim, CPCCHAR buf)
{
  STR_VEC arr;
  char *tmp = strdup(buf);
  while(1)
  {
    arr.push_back(tmp);
    tmp = strstr(tmp, delim);
    if(!tmp)
      break;
    *tmp = '\0';
    tmp += strlen(delim);
  }
  return arr;
}

void read_in(CPCCHAR buf)
{
  STR_VEC arr = split(",", buf);
  if(strcmp(arr[0], CSV_VERSION) )
  {
    fprintf(stderr, "Can't process: %s\n", buf);
    free((void *)arr[0]);
    return;
  }
  data.push_back(arr);
}

void print_item(int num, int item)
{
  PCCHAR line_data;
  if(int(data[num].size()) > item)
    line_data = data[num][item];
  else
    line_data = "";
  if(props[num][item])
    printf("<TD %s>%s</TD>", props[num][item], line_data);
  else
    printf("<TD>%s</TD>", line_data);
}

void print_a_line(int num, int start, int end)
{
  int i;
  for(i = start; i <= end; i++)
  {
    print_item(num, i);
  }
}


syntax highlighted by Code2HTML, v. 0.9.1