/* office.c
 * Nicholas Ingolia
 * ingolia@mit.edu
 */

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>

#define S_PER_HR 3600

#include "errors.h"
#include "finger.h"
#include "office.h"
#include "sendz.h"

#define ERRMSGLEN 1024
static char errmsg[ERRMSGLEN];

static int office_since_last_cleaning(void);
static void office_initialize(void);
static void office_add_head(const char *headname);
static char **office_user_list(int *);
static char *office_users_string(char **users);

static char **office_heads = NULL;

static const char *ignore_users[] = {
  "root",
  NULL
};


static const int min_interval = 60; /* Hours */
static const int avg_interval = 96; /* Hours */

static const int required_users = 5;

static time_t last_cleaning;

static int office_initialized = 0;

static void office_initialize(void)
{
  office_heads = malloc(sizeof(char *) * 1);
  office_heads[0] = NULL;

  last_cleaning = (time(NULL) - min_interval) * S_PER_HR;

  office_initialized = 1;
}

#define BUFLEN 1024

void office_read_head_list(const char *filename)
{
  FILE *f;
  char  *c, buf[BUFLEN];
  int   i;

  if (!office_initialized) {
    office_initialize();
  }

  if ((f = fopen(filename, "r")) == NULL) {
    snprintf(errmsg, ERRMSGLEN, "office: Unable to open head list \'%s\'\n", 
	     filename);
    error(errmsg);
    return;
  }

  while (fgets(buf, BUFLEN, f) != NULL) {
    if (strlen(buf) == 0) {
      continue;
    }

    if (buf[strlen(buf) - 1] != '\n') {
      snprintf(errmsg, ERRMSGLEN, "office: Head line too long in \'%s\'\n", 
	       filename);
      error(errmsg);
      break;
    }

    buf[strlen(buf) - 1] = '\0';

    if (buf[0] == '#') {
      continue;
    }

    /* Trim leading whitespace */
    for (c = buf; isspace(*c); c++) 
      ;

    if (*c != '\0') {
      /* Trim trailing whitespace */
      for (i = strlen(c) - 1; i >= 0 && isspace(c[i]); i--)
	;
      c[i + 1] = '\0';

      office_add_head(c);
    }
  }

  fclose(f);
}

void office_print_head_list(void)
{
  char **c;

  if (!office_initialized) {
    office_initialize();
  }

  printf("#office_print_head_list:\n");

  for (c = office_heads; *c != NULL; c++) {
    puts(*c);
  }
}

static void office_add_head(const char *headname)
{
  int nheads;

  for (nheads = 0; office_heads[nheads] != NULL; nheads++)
    ;

  office_heads = realloc(office_heads, sizeof(char *) * (nheads + 2));

  office_heads[nheads] = strdup(headname);
  office_heads[nheads + 1] = NULL;
}

#define MAX_USERS 100

static char **office_user_list(int *usercount)
{
  int    i, j, k, ignore, userno;
  char **headusers, **users;

  users = calloc(MAX_USERS, sizeof(char *));

  userno = 0;

  for (i = 0; office_heads[i] != NULL; i++) {
    headusers = finger_users(office_heads[i]);

    for (j = 0; headusers[j] != NULL; j++) {
      ignore = 0;

      if (userno >= MAX_USERS) {
	ignore++;
      }

      for (k = 0; users[k] != NULL; k++) {
	if (!strcmp(users[k], headusers[j])) {
	  ignore++;
	}
      }

      for (k = 0; ignore_users[k] != NULL; k++) {
	if (!strcmp(ignore_users[k], headusers[j])) {
	  ignore++;
	}
      }

      if (ignore) {
	free(headusers[j]);
      } else {
	users[userno] = headusers[j];
	userno++;
      }
    }

    free(headusers);
  }

  if (usercount) {
    *usercount = userno;
  }

  return users;
}

int office_needs_cleaning(void)
{

  if (!office_initialized) {
    office_initialize();
  }

  if (office_since_last_cleaning() >= min_interval) {
    sendz_send_zephyr_debugging("Cleaning needed: since-last-cleaning %d > %d, min-interval", 
				office_since_last_cleaning(), min_interval);

    printf("Cleaning needed--it has been long enough.\n");
    fflush(stdout);

    return 1;
  } else {
    sendz_send_zephyr_debugging("No cleaning needed: since-last-cleaning %d < %d, min-interval", 
				office_since_last_cleaning(), min_interval);

    printf("Don't need cleaning--not long enough since last cleaning.\n");
    fflush(stdout);
 
    return 0;
  }
}

int office_try_clean_now(void)
{
  time_t     tnow;
  struct tm *tmnow;
  char     **users, *userlist;
  int        i, nusers;

  if (!office_initialized) {
    office_initialize();
  }

  tnow = time(NULL);
  tmnow = localtime(&tnow);

  if ((tmnow->tm_wday == 1) 
      && ((tmnow->tm_hour == 19) || (tmnow->tm_hour == 20))) {
    /* Monday, 19:00-20:59 */
    printf("%s office: Can't do cleaning--this is the meeting blackout.\n",
	   now_string());
    fflush(stdout);

    sendz_send_zephyr_debugging("Can't do cleaning--meeting blackout.");
    
    return 0;
  }

  users = office_user_list(&nusers);
  userlist = office_users_string(users);

  if (nusers < required_users) {
    printf("%s office: Can't do cleaning--not enough users in the office.\n",
	   now_string());
    fflush(stdout);

    sendz_send_zephyr_debugging("Can't do cleaning--too few users in office");
    sendz_send_zephyr_debugging("%s", userlist);

    for (i = 0; users[i] != NULL; i++) {
      free(users[i]);
    }
    free(users);
    
    free(userlist);
    
    return 0;
  }

  printf("%s office: Can do cleaning--enough users, not during a meeting.\n",
	 now_string());
  fflush(stdout);
  
  sendz_send_zephyr_debugging("Can do cleaning--enough users, not in meeting");
  sendz_send_zephyr_debugging("%s", userlist);

  sendz_send_zephyrs();

  for (i = 0; users[i] != NULL; i++) {
    sendz_send_personal_zephyrs(users[i]);
    free(users[i]);
  }
  free(users);

  free(userlist);

  return 1;
}

/* Take p to the the probability of cleaning in a given hour interval and 
 *   q to be 1 - p.
 * Then, the probability of going n hours without a cleaning is, q^n.
 * We want to find p such that the probability of going avg_interval hours
 *   with no office cleaning is 0.5.
 *     q^n = 0.5
 *     => q = 0.5^{1/n}
 *     => p = 1 - 0.5^{1/n}.
 */
int office_should_clean(void)
{
  int    avg;
  double p;

  if (!office_initialized) {
    office_initialize();
  }

  avg = avg_interval - min_interval;
  if (avg <= 0) {
    error("Average interval - Minimum interval <= 0.\n");
    return 1;
  }

  p = 1.0 - pow(0.5, 1.0 / ((double) avg));

  if (drand48() < p) {
    printf("%s office: Chose office cleaning, p = %f\n", now_string(), p);
    fflush(stdout);

    sendz_send_zephyr_debugging("Chose office cleaning, P = %f\n", p);
    
    return 1;
  }

  printf("%s office: Chose no office cleaning, p = %f\n", now_string(), p);
  fflush(stdout);

  sendz_send_zephyr_debugging("Chose no office cleaning, P = %f\n", p);
  
  return 0;
}

void office_now_cleaned(void)
{
  if (!office_initialized) {
    office_initialize();
  }

  last_cleaning = time(NULL);
}

static int office_since_last_cleaning(void)
{
  time_t then, now;
  int    Dt; /* in s */

  then = last_cleaning;
  now = time(NULL);

  Dt = difftime(now, then);

  return Dt / S_PER_HR;
}

static char *office_users_string(char **users)
{
  int   i, listlen;
  char *list;

  listlen = 1;
  for (i = 0; users[i] != NULL; i++) {
    listlen += strlen(users[i]) + 2; 
  }

  list = calloc(listlen, sizeof(char));

  for (i = 0; users[i] != NULL; i++) {
    if (i > 0) {
      strcat(list, ", ");
    }

    strcat(list, users[i]);
  }

  return list;
}
