#include <stdio.h>
#include <stdlib.h>
#include "cbthread.h"
#include "stockroom.h"
#include "jrb.h"
#include "dllist.h"

#define talloc(ty, sz) (ty *) malloc ((sz) * sizeof(ty))

typedef struct {
  int *shelves;
  int robot_next_item;
  int cust_counter;
  JRB *bay_items;
  Customer **customers;
  int *itemgen;
  double profit;
  int calling_item_stocked;
} Private;

void new_customer_at_bay(Customer *c)
{
  Private *p;
  int i;
  int index;

  p = (Private *) c->s->private;
  p->customers[c->bay] = c;
  p->bay_items[c->bay] = make_jrb();

  c->id = p->cust_counter;
  p->cust_counter++;

  c->nitems = lrand48()%c->s->max_items_per_cust + 1;
  c->items = talloc(int, c->nitems);

  for (i = 0; i < c->nitems; i++) {
    index = lrand48()%(c->s->nitems-i)+i;
    c->items[i] = p->itemgen[index];
    p->itemgen[index] = p->itemgen[i];
    p->itemgen[i] = c->items[i];
  }

  if (c->s->verbose) {
    printf("%10.3lf: New customer %03d at bay %03d:", cbthread_get_fake_time(), c->id, c->bay);
    for (i = 0; i < c->nitems; i++) printf(" %d", c->items[i]);
    printf("\n");
    fflush(stdout);
  }
  new_customer(c);
  cbthread_exit();
}
   

void fork_new_customer(Customer *c)
{
  double sleeptime;

  sleeptime = c->arrival_time - cbthread_get_fake_time();
  if (sleeptime < 0) sleeptime = 0;
  cbthread_fake_sleep(sleeptime, new_customer_at_bay, c);
}

void create_new_customer(Simulation *s, int bay, double wait_time)   
{
  Customer *c;

  c = talloc(Customer, 1);
  c->s = s;
  c->bay = bay;
  c->arrival_time = cbthread_get_fake_time() + wait_time;
  cbthread_fork(fork_new_customer, c);
}

void customer_done(Customer *c)
{
  Simulation *s;
  Private *p;
  int bay;
  double profit;
  int i;
  JRB tmp;

  s = c->s;
  p = (Private *) s->private;
  bay = c->bay;

  if (p->bay_items[bay] == NULL) {
    fprintf(stderr, "Customer_done called on an empty bay (Customer %d, Bay %d)\n", c->id, bay);
    exit(1);
  }

  for (i = 0; i < c->nitems; i++) {
    tmp = jrb_find_int(p->bay_items[bay], c->items[i]);
    if (tmp == NULL) {
      fprintf(stderr, "Customer_done called on customer %d and bay %d doesn't have item %d.\n", c->id, bay, c->items[i]);
      exit(1);
    }
    jrb_delete_node(tmp);
  }
  jrb_free_tree(p->bay_items[bay]);
  p->bay_items[bay] = NULL;
  
  profit = c->nitems * 2 - 1;
  if (s->verbose) {
    printf("%10.3lf: Customer %03d Bay %03d done.  Profit = $%.0lf\n", cbthread_get_fake_time(), c->id, c->bay, profit);
    fflush(stdout);
  }
  p->profit += profit;
  free(c->items);
  free(c);
  
  create_new_customer(s, bay, drand48()*s->cust_time);
  cbthread_exit();
}

void robot_thread(Simulation *s)
{
  Private *p;

  p = (Private *) s->private;

  if (p->shelves[p->robot_next_item] < s->max_items_on_shelves) {
    p->shelves[p->robot_next_item]++;
    if (s->verbose) {
      printf("%10.3lf: Robot stocking item %03d (%d on shelf)\n", cbthread_get_fake_time(), 
                       p->robot_next_item, p->shelves[p->robot_next_item]);
      fflush(stdout);
    }
    p->calling_item_stocked = 1;
    item_stocked(s, p->robot_next_item);
    p->calling_item_stocked = 0;
  } else {
    if (s->verbose) {
      printf("%10.3lf: Robot skipping item %03d\n", cbthread_get_fake_time(), p->robot_next_item);
      fflush(stdout);
    }
  }
  p->robot_next_item++;
  if (p->robot_next_item == s->nitems) p->robot_next_item = 0;
  cbthread_fake_sleep(s->robot_time, robot_thread, s);
}

void finish_thread(Simulation *s)
{
  Private *p;

  p = (Private *) s->private;
  printf("Simulation Over.  Profit = %10.2lf\n", p->profit);
  exit(0);
}

void move_item_to_bay(Simulation *s, int item, int bay)
{
  Private *p;

  p = (Private *) s->private;

  if (s->verbose) {
    printf("%10.3lf: Moving item %03d to bay %03d\n", cbthread_get_fake_time(), item, bay);
    fflush(stdout);
  }
  if (p->calling_item_stocked) {
    fprintf(stderr, "Error: called move_item_to_bay() inside item_stocked().\n");
    exit(1);
  }
  if (p->shelves[item] == 0) {
    fprintf(stderr, "Error -- no item %d on the shelves\n", item);
    exit(1);
  }
  p->shelves[item]--;

  if (p->bay_items[bay] == NULL) {
    fprintf(stderr, "Error -- no customer at bay %d\n", bay);
    exit(1);
  }
  jrb_insert_int(p->bay_items[bay], item, new_jval_i(0));
} 

void usage(char *s)
{
  fprintf(stderr, "usage: stockroom nitems nbays max_items_on_shelves max_items_per_cust cust_time robot_time duration verbose seed\n");
  if (s != NULL) fprintf(stderr, "%s\n", s);
  exit(1);
}

main(int argc, char **argv)
{
  Simulation *s;
  Private *p;
  int i, j;
  int seed;
 
  s = talloc(Simulation, 1);

  if (argc != 10) usage(NULL);

  if (sscanf(argv[1], "%d", &s->nitems) == 0 || s->nitems <= 0) usage("Bad nitems");
  if (sscanf(argv[2], "%d", &s->nbays) == 0 || s->nbays <= 0) usage("Bad nbays");
  if (sscanf(argv[3], "%d", &s->max_items_on_shelves) == 0 || s->max_items_on_shelves <= 0) usage("Bad max_items_on_shelves");
  if (sscanf(argv[4], "%d", &s->max_items_per_cust) == 0 
           || s->max_items_per_cust <= 0 
           || s->max_items_per_cust > s->nitems) usage("Bad max_items_per_cust");
  if (sscanf(argv[5], "%lf", &s->cust_time) == 0 || s->cust_time < 0) usage("Bad cust_time");
  if (sscanf(argv[6], "%lf", &s->robot_time) == 0 || s->robot_time <= 0) usage("Bad robot_time");
  if (sscanf(argv[7], "%lf", &s->duration) == 0 || s->duration <= 0) usage("Bad duration");
  s->verbose = (argv[8][0] == 'y' || argv[8][0] == 'Y');
  if (sscanf(argv[9], "%d", &seed) == 0) usage("Bad seed");

  srand48(seed);

  p = talloc(Private, 1);
  s->private = (void *) p;

  int cust_counter;
  JRB *bay_items;
  double profit;

  p->shelves = talloc(int, s->nitems);
  for (i = 0; i < s->nitems; i++) p->shelves[i] = 0;
  p->robot_next_item = 0;
  p->cust_counter = 0;
  p->calling_item_stocked = 0;

  p->bay_items = talloc(JRB, s->nbays);
  for (i = 0; i < s->nbays; i++) p->bay_items[i] = NULL;

  p->customers = talloc(Customer *, s->nbays);
  for (i = 0; i < s->nbays; i++) p->customers[i] = NULL;

  p->itemgen = talloc(int, s->nitems);
  for (i = 0; i < s->nitems; i++) p->itemgen[i] = i;

  p->profit = 0;
  initialize_simulation(s);
  
  cbthread_fork(robot_thread, s);
  for (i = 0; i < s->nbays; i++) {
    create_new_customer(s, i, drand48()*s->cust_time);
  }
  cbthread_fake_sleep(s->duration, finish_thread, s);
}

