#include "library/malloc.h"
#include "library/syscalls.h"
#include "library/string.h"
#include "library/stdio.h"
#include "library/nwindow.h"

/**
 * Livestat is a command line utility to display a graph of operating system
 * statistics in realtime
 */

#define POINTS 60
#define ARG_MAX 16
#define ARG_SIZE 32

typedef enum STAT_LIVE {
  DRIVER_LIVE,
  BCACHE_LIVE,
  SYSTEM_LIVE,
  PROCESS_LIVE
} STAT_LIVE;

struct stat_args {
  /* General */
  STAT_LIVE stat_type;
  char * stat_name;
  void * statistics;
  /* Process */
  char * pid_s;
  int pid;
  int syscall_index;
  /* Driver */
  char * driver_name;
  /* System */
  int device_unit;
};

void help();
void stat_live_2_str(STAT_LIVE stat_l, char * str);
void create_graph(STAT_LIVE stat_type, char * stat_name, char * stat_arg, int window_width, int window_height, int plot_width, int plot_height, int thickness, int char_offset);
int  extract_statistic(struct stat_args * args);
void plot_bars(int * most_recent_vals, int max, int window_width, int window_height, int plot_width, int plot_height, int thickness, int char_offset);
void run_stats(struct stat_args * args);

struct nwindow *nw = 0;

int main(int argc, const char * argv[]) {
  /* Setup program parameters */
  int current_arg     = 1;
  struct stat_args args = { 0 };

  /* Parse command line options */
  if (argc < 2)  {
    help(1); return 1;
  }

  while(current_arg < argc) {
    if (!strcmp(argv[current_arg], "-h") || !strcmp(argv[current_arg], "--help")) {
      help(); return 0;
    }
    else if (!strcmp(argv[current_arg], "-b")) {
      args.statistics = malloc(sizeof(struct bcache_stats));
      args.stat_type = BCACHE_LIVE;
    }
    else if (!strcmp(argv[current_arg], "-dr")) {
      args.statistics = malloc(sizeof(struct device_driver_stats));
      args.stat_type = DRIVER_LIVE;
      args.driver_name = strdup(argv[++current_arg]);
    }
    else if (!strcmp(argv[current_arg], "-sys")) {
      args.statistics = malloc(sizeof(struct system_stats));
      args.stat_type = SYSTEM_LIVE;
    }
    else if (!strcmp(argv[current_arg], "-p")) {
      args.statistics = malloc(sizeof(struct process_stats));
      args.stat_type = PROCESS_LIVE;
      args.pid_s = strdup(argv[++current_arg]);
      str2int(args.pid_s, &(args.pid));
    }
    else if (!strcmp(argv[current_arg], "-s")) {
      args.stat_name = strdup(argv[++current_arg]);
    }
    else if (!strcmp(argv[current_arg], "-sc")) {
      str2int(argv[++current_arg], &(args.syscall_index));
    }
    else if (!strcmp(argv[current_arg], "-du")) {
      str2int(argv[++current_arg], &(args.device_unit));
    }
    else {
      help(); return 1;
    }
    ++current_arg;
  }

  if (!args.stat_name) {
    help(); return 1;
  }

  nw = nw_create_default();

  /* Start tracking stats */
  run_stats(&args);

  return 0;
}

/** HELPER FUNCTIONS **/
/* Create graph and continuously update it */
void run_stats(struct stat_args * args)
{
  /* Initialize variables for graph and stats collection */
  int window_width  = 270;
  int window_height = 270;
  int plot_width    = 180;
  int plot_height   = 180;
  int thickness     = 2;
  int char_offset   = 8;

  /* Generate the graph with correct labels */
  if (args->stat_type == BCACHE_LIVE) {
    create_graph(args->stat_type, args->stat_name, 0, window_width, window_height, plot_width, plot_height, thickness, char_offset);
  } else if (args->stat_type == PROCESS_LIVE) {
    create_graph(args->stat_type, args->stat_name, args->pid_s, window_width, window_height, plot_width, plot_height, thickness, char_offset);
  } else if (args->stat_type == DRIVER_LIVE) {
    create_graph(args->stat_type, args->stat_name, args->driver_name, window_width, window_height, plot_width, plot_height, thickness, char_offset);
  } else if (args->stat_type == SYSTEM_LIVE) {
    create_graph(args->stat_type, args->stat_name, 0, window_width, window_height, plot_width, plot_height, thickness, char_offset);
  }

  /* Start tracking stats */
  int i, new_val, max;
  int most_recent_vals[POINTS];
  int last_value = 0;
  int first = 1;
  char quit = 0;

  for (size_t i = 0; i < POINTS; i++)
    most_recent_vals[i] = -1;

  while(1) {
    /* Check to exit */
    quit = nw_getchar(nw,0);
    if (quit == 'q') break;

    /* Get the correct stats */
    if (args->stat_type  == BCACHE_LIVE) {
      syscall_bcache_stats(args->statistics);
    } else if (args->stat_type  == PROCESS_LIVE) {
      syscall_process_stats(args->statistics, args->pid);
    } else if (args->stat_type == DRIVER_LIVE) {
      syscall_device_driver_stats(args->driver_name, args->statistics);
    } else if (args->stat_type  == SYSTEM_LIVE) {
      syscall_system_stats(args->statistics);
    }

    /* Grab the specified statistic of interest */
    new_val = extract_statistic(args);

    /* Skip first value because it is just a baseline */
    if (first) {
      last_value = new_val;
      first = 0;
      goto sleep;
    }

    /* Update points */
    for (i = 1; i < POINTS; i++)
      most_recent_vals[i-1] = most_recent_vals[i];
    most_recent_vals[POINTS-1] = new_val - last_value;
    last_value = new_val;

    /* Set max to greatest point or 1 */
    max = 1;
    for (i = 0; i < POINTS; i++) {
      if (most_recent_vals[i] > max) max = most_recent_vals[i];
    }

    /* Replot points */
    plot_bars(most_recent_vals, max, window_width, window_height, plot_width, plot_height, thickness, char_offset);

    /* Sleep for 6 seconds and then continue */
    sleep:
    syscall_process_sleep(6000);
  }
  nw_fgcolor(nw,255, 255, 255);
  nw_flush(nw);
}

/* Return one stat from statistics structure */
int extract_statistic(struct stat_args * args) {

  if (args->stat_type == BCACHE_LIVE) {
    if (!strcmp(args->stat_name, "read_hits")) {
      return ((struct bcache_stats *)args->statistics)->read_hits;
    } else if (!strcmp(args->stat_name, "read_misses")) {
      return ((struct bcache_stats *)args->statistics)->read_misses;
    } else if (!strcmp(args->stat_name, "write_hits")) {
      return ((struct bcache_stats *)args->statistics)->write_hits;
    } else if (!strcmp(args->stat_name, "write_misses")) {
      return ((struct bcache_stats *)args->statistics)->write_misses;
    } else if (!strcmp(args->stat_name, "writebacks")) {
      return ((struct bcache_stats *)args->statistics)->writebacks;
    }
  }
  else if (args->stat_type == PROCESS_LIVE) {
    if (!strcmp(args->stat_name, "blocks_read")) {
      return ((struct process_stats *)args->statistics)->blocks_read;
    } else if (!strcmp(args->stat_name, "blocks_written")) {
      return ((struct process_stats *)args->statistics)->blocks_written;
    } else if (!strcmp(args->stat_name, "bytes_read")) {
      return ((struct process_stats *)args->statistics)->bytes_read;
    } else if (!strcmp(args->stat_name, "bytes_written")) {
      return ((struct process_stats *)args->statistics)->bytes_written;
    } else if (!strcmp(args->stat_name, "syscall_count")) {
      return ((struct process_stats *)args->statistics)->syscall_count[args->syscall_index];
    }
  }
  else if (args->stat_type == DRIVER_LIVE) {
      if (!strcmp(args->stat_name, "blocks_read")) {
        return ((struct device_driver_stats *)args->statistics)->blocks_read;
      } else if (!strcmp(args->stat_name, "blocks_written")) {
        return ((struct device_driver_stats *)args->statistics)->blocks_written;
      }
  }
  else if (args->stat_type == SYSTEM_LIVE) {
    if (!strcmp(args->stat_name, "time")) {
      return ((struct system_stats *)args->statistics)->time;
    } else if (!strcmp(args->stat_name, "blocks_read")) {
      return ((struct system_stats *)args->statistics)->blocks_read[args->device_unit];
    } else if (!strcmp(args->stat_name, "blocks_written")) {
      return ((struct system_stats *)args->statistics)->blocks_written[args->device_unit];
    }
  }

  return -1;
}

/* Create the graph template for the display */
void create_graph( STAT_LIVE stat_type, char * stat_name, char * stat_arg, int window_width, int window_height, int plot_width, int plot_height, int thickness, int char_offset) {
  /* Initialize Parameters */
  int i;
  int x_offset      = (window_width - plot_width) / 2;
  int y_offset      = (window_height - plot_height) / 2;
  char title[100] = "";

  stat_live_2_str(stat_type, title);
  if (stat_arg != 0) {
    strcat(title, ": ");
    strcat(title, stat_arg);
  }

  /* Draw border around window */
  nw_clear(nw,0, 0, window_width, window_height);
  nw_rect(nw,0, 0, window_width, thickness);
  nw_rect(nw,0, 0, thickness, window_height);
  nw_rect(nw,window_width - thickness, 0, thickness, window_height);
  nw_rect(nw,0, window_height - thickness, window_width, thickness);

  /* Draw X, Y axis in center of window */
  nw_rect(nw,x_offset, y_offset+plot_height, plot_width, thickness);
  nw_rect(nw,x_offset, y_offset, thickness, plot_height);

  /* Draw title, x_axis label, y_axis label */
  nw_string(nw,window_width/2 - 70, 8, title);
  nw_string(nw,window_width/2 - 34, window_height - 16, "Time (6s)");

  /* Setup Y axis */
  char y[2] = { 0 };
  for (i = 0; i < strlen(stat_name); i++) {
      y[0] = stat_name[i];
      nw_string(nw,10, window_height/2 - 20 + i*char_offset, y);
      nw_flush(nw);
  }

  /* For tick marks on x axis -- always keep the most recent minute - 10 ticks */
  int x_tick_width = plot_width/10;
  for (i = 0; i < 10+1; i++) {
    nw_rect(nw,x_offset+i*x_tick_width, y_offset+plot_height, thickness, 6);
  }

  /* For tick marks on y axis -- max will be dynamically set */
  int y_tick_width = plot_height/10;
  for (i = 0; i < 10+1; i++) {
    nw_rect(nw,x_offset - 4, y_offset + plot_height - i * y_tick_width, 6, thickness);
  }

  nw_flush(nw);
}

/* Plot all of the points on the graph and print the max */
void plot_bars( int most_recent_vals[POINTS], int max, int window_width, int window_height, int plot_width, int plot_height, int thickness, int char_offset) {
  /* Clear the graph */
  int x_offset      = (window_width - plot_width) / 2;
  int y_offset      = (window_height - plot_height) / 2;
  nw_clear(nw,x_offset, y_offset,  plot_width+thickness, plot_height);

  /* Redraw the axis */
  nw_rect(nw,x_offset, y_offset+plot_height, plot_width, thickness);
  nw_rect(nw,x_offset, y_offset, thickness, plot_height);

  /* Create string of the max */
  char max_str[32];
  uint_to_string((uint32_t) max, max_str);
  nw_string(nw,x_offset - 26, y_offset - 10, max_str);
  nw_flush(nw);

  /* Plot the points */
  int i, current_point[2] = { 0 };

  /* Variables to plot value in correct location on the graph */
  float x_step = (float)plot_width/POINTS;
  float y_step = (float)plot_height/max;

  nw_fgcolor(nw,0, 255, 0);

  for (i = 0; i < POINTS; i++) {
    if (most_recent_vals[i] == -1)
      continue;

    current_point[0] = x_offset + (int)(x_step*(i+1));
    current_point[1] = y_offset + plot_height - (int)(y_step*most_recent_vals[i]);
    nw_rect(nw,current_point[0], current_point[1], thickness, (int)(y_step*most_recent_vals[i]));
  }
  nw_flush(nw);
  nw_fgcolor(nw,255, 255, 255);
}

/* Convert stat type to string */
void stat_live_2_str(STAT_LIVE stat_l, char * str) {

  if (stat_l == BCACHE_LIVE) {
    strcpy(str, "Bcache");
  }
  else if (stat_l == PROCESS_LIVE) {
    strcpy(str, "Process");
  }
  else if (stat_l == DRIVER_LIVE) {
    strcpy(str, "Driver");
  }
  else if (stat_l == SYSTEM_LIVE) {
    strcpy(str, "System");
  }
}

/* Help message */
void help() {
  printf("\nusage\n\n");
  printf("statslive.exe\n");
  printf("                -h | --help          # display help\n");
  printf("                -b                   # buffer cache stats\n");
  printf("                -dr  <DRIVER_NAME>   # driver stats\n");
  printf("                -sys <BLOCK>         # system stats\n");
  printf("                -p   <PID>           # process stats\n");
  printf("                -sc  <SYSCALL>       # syscall number\n");
  printf("                -s   <STAT_NAME>     # name of statistic\n");

  printf("\nBuffercache STAT_NAME options:\n");
  printf("    read_hits\n");
  printf("    read_misses\n");
  printf("    write_hits\n");
  printf("    write_misses\n");
  printf("    writebacks\n\n");

  printf("\nProcess STAT_NAME options:\n");
  printf("    blocks_read\n");
  printf("    blocks_written\n");
  printf("    bytes_read\n");
  printf("    bytes_written\n");
  printf("    syscall_count\n\n");

  printf("\nDriver STAT_NAME options:\n");
  printf("    blocks_read\n");
  printf("    blocks_written\n\n");

  printf("\nSystem STAT_NAME options:\n");
  printf("    time\n");
  printf("    blocks_read\n");
  printf("    blocks_written\n\n");

  //TODO memory utilizations (page), kernel malloc

}