/*
 * mudlib_stats.c
 * created by: Erik Kay
 * last modified: 11/1/92
 * this file is a replacement for wiz_list.c and all of its purposes
 * the idea is that it will be more domain based, rather than user based
 * and will be a little more general purpose than wiz_list was
 */

#include "base/package_api.h"

#include <sys/param.h>

#include "packages/mudlib_stats/mudlib_stats.h"

#ifdef F_DOMAIN_STATS
void f_domain_stats() {
  mapping_t *m;

  if (st_num_arg) {
    m = get_domain_stats(sp->u.string);
    free_string_svalue(sp--);
  } else {
    m = get_domain_stats(nullptr);
  }
  if (!m) {
    push_number(0);
  } else {
    /* ref count is properly decremented by get_domain_stats */
    push_mapping(m);
  }
}
#endif

#ifdef F_SET_AUTHOR
void f_set_author() {
  set_author(sp->u.string);
  free_string_svalue(sp--);
}
#endif

#ifdef F_AUTHOR_STATS
void f_author_stats() {
  mapping_t *m;

  if (st_num_arg) {
    m = get_author_stats(sp->u.string);
    free_string_svalue(sp--);
  } else {
    m = get_author_stats(nullptr);
  }
  if (!m) {
    push_number(0);
  } else {
    /* ref count is properly decremented by get_author_stats */
    push_mapping(m);
  }
}
#endif

/* Support functions */
static mudlib_stats_t *domains = nullptr;
static mudlib_stats_t *backbone_domain = nullptr;
static mudlib_stats_t *authors = nullptr;
static mudlib_stats_t *master_author = nullptr;

static mudlib_stats_t *find_stat_entry(const char * /*name*/, mudlib_stats_t * /*list*/);
static mudlib_stats_t *add_stat_entry(const char * /*str*/, mudlib_stats_t ** /*list*/);
static void init_author_for_ob(object_t * /*ob*/);
static const char *author_for_file(const char * /*file*/);
static void init_domain_for_ob(object_t * /*ob*/);
static const char *domain_for_file(const char * /*file*/);
static void save_stat_list(const char * /*file*/, mudlib_stats_t * /*list*/);
static void restore_stat_list(const char * /*file*/, mudlib_stats_t ** /*list*/);
static mapping_t *get_info(mudlib_stats_t * /*dl*/);
static mapping_t *get_stats(const char * /*str*/, mudlib_stats_t * /*list*/);
static mudlib_stats_t *insert_stat_entry(mudlib_stats_t * /*entry*/, mudlib_stats_t ** /*list*/);

#ifdef DEBUGMALLOC_EXTENSIONS
/* debugging */
int check_valid_stat_entry(mudlib_stats_t *se) {
  mudlib_stats_t *tmp;

  tmp = domains;
  while (tmp) {
    if (tmp == se) {
      return 1;
    }
    tmp = tmp->next;
  }
  tmp = authors;
  while (tmp) {
    if (tmp == se) {
      return 1;
    }
    tmp = tmp->next;
  }
  return 0;
}
#endif

/**************************
 * stat list manipulation
 **************************/

static mudlib_stats_t *insert_stat_entry(mudlib_stats_t *entry, mudlib_stats_t **list) {
  entry->next = *list;
  *list = entry;
  return *list;
}

/*
 * Return the data for an individual domain, if it exists.
 * this uses a simple linear search.  it's a good thing that most muds
 * will have a relatively small number of domains
 */
static mudlib_stats_t *find_stat_entry(const char *name, mudlib_stats_t *list) {
  int length;

  length = strlen(name);
  for (; list; list = list->next) {
    if (list->length == length && strcmp(list->name, name) == 0) {
      return list;
    }
  }
  return nullptr;
}

/*
 * add a new domain to the domain list.  If it exists, do nothing.
 */
static mudlib_stats_t *add_stat_entry(const char *str, mudlib_stats_t **list) {
  mudlib_stats_t *entry;

  if ((entry = find_stat_entry(str, *list))) {
    return entry;
  }
  entry = reinterpret_cast<mudlib_stats_t *>(
      DMALLOC(sizeof(mudlib_stats_t), TAG_MUDLIB_STATS, "add_stat_entry"));
  entry->name = make_shared_string(str);
  entry->length = strlen(str);
  entry->moves = 0;
  entry->heart_beats = 0;
  entry->errors = 0;
  entry->objects = 0;
  entry->next = nullptr;
  entry->size_array = 0;
  insert_stat_entry(entry, list);
  return entry;
}

/*************************************
 * general stat modifying accessor functions
 **************************************/

void assign_stats(statgroup_t *st, object_t *ob) {
  st->domain = ob->stats.domain;
  st->author = ob->stats.author;
}

void null_stats(statgroup_t *st) {
  if (st) {
    st->domain = nullptr;
    st->author = nullptr;
  }
}

void init_stats_for_object(object_t *ob) {
  init_domain_for_ob(ob);
  init_author_for_ob(ob);
}

/*
 * Add moves to an existing domain.
 */
void add_moves(statgroup_t *st, int moves) {
  if (st) {
    if (st->domain) {
      st->domain->moves += moves;
    }
    if (st->author) {
      st->author->moves += moves;
    }
  }
}

void add_heart_beats(statgroup_t *st, int hbs) {
  if (st) {
    if (st->domain) {
      st->domain->heart_beats += hbs;
    }
    if (st->author) {
      st->author->heart_beats += hbs;
    }
  }
}

void add_array_size(statgroup_t *st, int size) {
  if (st) {
    if (st->domain) {
      st->domain->size_array += size;
    }
    if (st->author) {
      st->author->size_array += size;
    }
  }
}

void add_errors(statgroup_t *st, int errors) {
  if (st) {
    if (st->domain) {
      st->domain->errors += errors;
    }
    if (st->author) {
      st->author->errors += errors;
    }
  }
}

void add_errors_for_file(const char *file, int errors) {
  mudlib_stats_t *entry;
  const char *name;

  name = domain_for_file(file);
  if (name && domains) {
    entry = find_stat_entry(name, domains);
    if (entry) {
      entry->errors += errors;
    }
  }
  name = author_for_file(file);
  if (name && authors) {
    entry = find_stat_entry(name, authors);
    if (entry) {
      entry->errors += errors;
    }
  }
}

void add_objects(statgroup_t *st, int objects) {
  if (st) {
    if (st->domain) {
      st->domain->objects += objects;
    }
    if (st->author) {
      st->author->objects += objects;
    }
  }
}

/*
 * Basically the "scores" are averaged over time by having them decay
 * gradually at each reset.
 * Here's how the decay breaks down:
 *    moves -= 1%
 *    heart_beats -= 10%
 */
void mudlib_stats_decay() {
  add_gametick_event(time_to_next_gametick(std::chrono::hours(1)),
                     TickEvent::callback_type(mudlib_stats_decay));

  mudlib_stats_t *dl;
  for (dl = domains; dl; dl = dl->next) {
    dl->moves = dl->moves * 99 / 100;
    dl->heart_beats = dl->heart_beats * 9 / 10;
  }
  for (dl = authors; dl; dl = dl->next) {
    dl->moves = dl->moves * 99 / 100;
    dl->heart_beats = dl->heart_beats * 9 / 10;
  }
}

#ifdef DEBUGMALLOC_EXTENSIONS
void mark_mudlib_stats() {
  mudlib_stats_t *dl;

  for (dl = domains; dl; dl = dl->next) {
    DO_MARK(dl, TAG_MUDLIB_STATS);
    EXTRA_REF(BLOCK(dl->name))++;
  }
  for (dl = authors; dl; dl = dl->next) {
    DO_MARK(dl, TAG_MUDLIB_STATS);
    EXTRA_REF(BLOCK(dl->name))++;
  }
}
#endif

/*************************
 Author specific functions
 *************************/

static void init_author_for_ob(object_t *ob) {
  svalue_t *ret;

  push_malloced_string(add_slash(ob->obname));
  ret = apply_master_ob(APPLY_AUTHOR_FILE, 1);
  if (ret == (svalue_t *)-1) {
    ob->stats.author = master_author;
  } else if (!ret || ret->type != T_STRING) {
    ob->stats.author = nullptr;
  } else {
    ob->stats.author = add_stat_entry(ret->u.string, &authors);
  }
}

void set_author(const char *name) {
  object_t *ob;

  if (!current_object) {
    return;
  }
  ob = current_object;
  if (master_ob == (object_t *)-1) {
    ob->stats.author = nullptr;
    return;
  }
  if (ob->stats.author) {
    ob->stats.author->objects--;
  }
  ob->stats.author = add_stat_entry(name, &authors);
  if (ob->stats.author) {
    ob->stats.author->objects++;
  }
}

mudlib_stats_t *set_master_author(const char *str) {
  mudlib_stats_t *author;

  author = add_stat_entry(str, &authors);
  if (author) {
    master_author = author;
  }
  return author;
}

static const char *author_for_file(const char *file) {
  svalue_t *ret;
  static char buff[50];

  copy_and_push_string(file);
  ret = safe_apply_master_ob(APPLY_AUTHOR_FILE, 1);
  if (ret == nullptr || ret == (svalue_t *)-1 || ret->type != T_STRING) {
    return nullptr;
  }
  strcpy(buff, ret->u.string);
  return buff;
}

/*************************
 Domain specific functions
 *************************/

static void init_domain_for_ob(object_t *ob) {
  svalue_t *ret;
  const char *domain_name;

  if (!current_object
#ifdef PACKAGE_UIDS
      || !current_object->uid
#endif
  ) {
    /*
     * Only for the master and void object. Note that you can't ask for
     * the backbone or root domain here since we're in the process of
     * loading the master object.
     */
    ob->stats.domain = add_stat_entry("NONAME", &domains);
    return;
  }
  /*
   * Ask master object who the creator of this object is.
   */
  push_malloced_string(add_slash(ob->obname));

  if (master_ob) {
    ret = apply_master_ob(APPLY_DOMAIN_FILE, 1);
  } else {
    ret = apply(applies_table[APPLY_DOMAIN_FILE], ob, 1, ORIGIN_DRIVER);
  }

  if (IS_ZERO(ret)) {
    ob->stats.domain = current_object->stats.domain;
    return;
  }
  if (ret->type != T_STRING) {
    error("'domain_file' in the master object must return a string!\n");
  }
  domain_name = ret->u.string;
  if (strcmp(current_object->stats.domain->name, domain_name) == 0) {
    ob->stats.domain = current_object->stats.domain;
    return;
  }
  if (strcmp(backbone_domain->name, domain_name) == 0) {
    /*
     * The object is loaded from backbone. We give domain ownership to
     * the creator rather than backbone.
     */
    ob->stats.domain = current_object->stats.domain;
    return;
  }
  /*
   * The object isn't loaded from backbone or from the same domain as the
   * creator, so we need to lookup the domain, and add it if it isnt
   * present.
   */
  ob->stats.domain = add_stat_entry(domain_name, &domains);
}

mudlib_stats_t *set_backbone_domain(const char *str) {
  mudlib_stats_t *dom;

  dom = add_stat_entry(str, &domains);
  if (dom) {
    backbone_domain = dom;
  }
  return dom;
}

/*
 * Argument is a file name, which we want to get the domain of.
 * Ask the master object.
 */
static const char *domain_for_file(const char *file) {
  svalue_t *ret;
  static char buff[512];

  share_and_push_string(file);
  ret = safe_apply_master_ob(APPLY_DOMAIN_FILE, 1);
  if (ret == nullptr || ret == (svalue_t *)-1 || ret->type != T_STRING) {
    return nullptr;
  }
  strcpy(buff, ret->u.string);
  return buff;
}

/************************************
 * save and restore stats to a file *
 ************************************/

static void save_stat_list(const char *file, mudlib_stats_t *list) {
  FILE *f;
  char fname_buf[MAXPATHLEN];
  char *fname = fname_buf;

  if (file) {
    if (strchr(file, '/')) {
      if (file[0] == '/') {
        file++;
      }
      f = fopen(file, "w");
    } else {
      sprintf(fname, "%s/%s", CONFIG_STR(__LOG_DIR__), file);
      if (fname[0] == '/') {
        fname++;
      }
      f = fopen(fname, "w");
    }
  } else {
    debug_message("*Warning: call to save_stat_list with null filename\n");
    return;
  }
  if (!f) {
    debug_message("*Error: unable to open stat file %s for writing.\n", file);
    return;
  }
  while (list) {
    fprintf(f, "%s %d %d\n", list->name, list->moves, list->heart_beats);
    list = list->next;
  }
  fclose(f);
}

static void restore_stat_list(const char *file, mudlib_stats_t **list) {
  FILE *f;
  char fname_buf[MAXPATHLEN];
  char *fname = fname_buf;
  mudlib_stats_t *entry;

  if (file) {
    if (strchr(file, '/')) {
      if (file[0] == '/') {
        file++;
      }
      f = fopen(file, "r");
    } else {
      sprintf(fname, "%s/%s", CONFIG_STR(__LOG_DIR__), file);
      if (fname[0] == '/') {
        fname++;
      }
      f = fopen(fname, "r");
    }
  } else {
    debug_message("*Warning: call to save_stat_list with null filename\n");
    return;
  }
  if (!f) {
    debug_message("*Warning: unable to open stat file %s for reading.\n", file);
    return;
  }
  while (fscanf(f, "%s", fname) != EOF) {
    entry = add_stat_entry(fname, list);
    if (fscanf(f, "%d %d\n", &entry->moves, &entry->heart_beats) != 2) {
      entry->moves = 0;
      entry->heart_beats = 0;
    }
  }
  fclose(f);
}

void save_stat_files() {
  save_stat_list(DOMAIN_STATS_FILE_NAME, domains);
  save_stat_list(AUTHOR_STATS_FILE_NAME, authors);
}

void restore_stat_files() {
  restore_stat_list(DOMAIN_STATS_FILE_NAME, &domains);
  restore_stat_list(AUTHOR_STATS_FILE_NAME, &authors);
}

/*************************************
 * The following functions are the interface for efuns to get mappings
 * that describe the statistics for authors and domains.
 **************************************/

static mapping_t *get_info(mudlib_stats_t *dl) {
  mapping_t *ret;

  ret = allocate_mapping(8);
  add_mapping_pair(ret, "moves", dl->moves);
  add_mapping_pair(ret, "errors", dl->errors);
  add_mapping_pair(ret, "heart_beats", dl->heart_beats);
  add_mapping_pair(ret, "array_size", dl->size_array);
  add_mapping_pair(ret, "objects", dl->objects);
  return ret;
}

static mapping_t *get_stats(const char *str, mudlib_stats_t *list) {
  mudlib_stats_t *dl;
  mapping_t *m;
  svalue_t lv, *s;

  if (str) {
    for (dl = list; dl; dl = dl->next) {
      if (!strcmp(str, dl->name)) { /* are these both shared strings? */
        break;
      }
    }
    if (dl) {
      mapping_t *tmp;

      tmp = get_info(dl);
      tmp->ref--;
      return tmp;
    }
    return nullptr;
  }
  m = allocate_mapping(8);
  for (dl = list; dl; dl = dl->next) {
    lv.type = T_STRING;
    lv.subtype = STRING_SHARED;
    lv.u.string = dl->name; /* find_for_insert() adds a ref */
    s = find_for_insert(m, &lv, 1);
    s->type = T_MAPPING;
    s->subtype = 0;
    s->u.map = get_info(dl);
  }
  m->ref--;
  return m;
}

mapping_t *get_domain_stats(const char *str) { return get_stats(str, domains); }

mapping_t *get_author_stats(const char *str) { return get_stats(str, authors); }
