#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Error.h"

#include "appglobal.h"
#include "control.h"
#include "fsutils.h"
#include "log.h"
#include "msgtrandyn.h"


const char *control_splice_token(const char *base, int num)
{
  static char *buffer = NULL;
  static size_t buflen = 0;
  size_t newlen = strlen(base) + 8;

  if (newlen > buflen)
  {
    buffer = realloc(buffer, buflen = newlen);
    /* Checking return of this fn every time would be really tedious,
    and running out of memory at this point is very rare and terrible */
    if (!buffer)
      Error_ReportFatal(0, MSG_NOMEM);
  }
  sprintf(buffer, "%s%d", base, num);
  return buffer;
}



control_msgtrans_data *control_msgtrans_data_new(const char *filename)
{
  int i;
  control_msgtrans_data *msgs = malloc(sizeof(control_msgtrans_data));
  char env[sizeof("nstall$Parameter00")];

  if (!msgs)
  {
    Error_Report(0, MSG_NOMEM);
    return NULL;
  }

  if (Error_Check(MsgTrans_LoadFile(&msgs->fd, (char *) filename)))
  {
    free(msgs);
    return NULL;
  }

  strcpy(env, "nstall$Parameter0");
  for (i = 0; i < 4; ++i)
  {
    const char *value;

    env[sizeof("nstall$Paramete")] = i + '0';
    value = getenv(env);
    if (value)
    {
      msgs->param[i] = fsutils_strdup(value);
    }
    else
    {
      msgs->param[i] = NULL;
    }
    LOG("nstall$Parameter%d ('%s') is '%s'\n", i, env,
        msgs->param[i] ? msgs->param[i] : "<NULL>");
  }

  LOGQ("\n");

  return msgs;
}

char *control_msgtrans_lookup(const control_msgtrans_data *msgs,
                              const char *token, BOOL report)
{
  return MsgTransDyn_LookupPS(msgs->fd, token,
                              msgs->param[0], msgs->param[1],
                              msgs->param[2], msgs->param[3],
                              report);
}



control_item *control_item_new(void)
{
  control_item *item = malloc(sizeof(control_item));

  if (!item)
  {
    Error_Report(0, MSG_NOMEM);
    return NULL;
  }

  item->name = NULL;
  item->disc = 0;
  item->location = 0;
  item->optional = control_OptionalPrune;
  item->icon = NULL;
  item->script = NULL;
  item->desc = NULL;
  item->opt_icon = -1;
  item->location_leaf = NULL;

  return item;
}

void control_item_delete(control_item *item)
{
  free(item->name);
  free(item->icon);
  free(item->script);
  free(item);
}

BOOL control_item_lookup(control_item *item, int item_num,
                         const control_msgtrans_data *msgs)
{
  LOG("Looking up item %d\n", item_num);
  item->name = MsgTransDyn_LookupPS(msgs->fd,
                                    control_splice_token("ItemName", item_num),
                                    msgs->param[0], msgs->param[1],
                                    msgs->param[2], msgs->param[3],
                                    TRUE);
  if (!item->name)
    return FALSE;

  item->disc = MsgTransDyn_LookupInt(msgs->fd,
                                     control_splice_token("ItemDisc", item_num),
                                     TRUE, -1);
  if (item->disc < 0)
    return FALSE;

  item->location = MsgTransDyn_LookupInt(msgs->fd,
                                         control_splice_token("ItemLocation",
                                                              item_num),
                                         TRUE, -1);
  if (item->location < 0)
    return FALSE;

  item->optional = MsgTransDyn_LookupOptional(msgs->fd,
                               control_splice_token("ItemOptional", item_num));
  if (item->optional == control_OptionalOn ||
      item->optional == control_OptionalOff)
  {
    item->desc = MsgTransDyn_LookupPS(msgs->fd,
                                      control_splice_token("ItemOptionalDesc",
                                                           item_num),
                                      msgs->param[0], msgs->param[1],
                                      msgs->param[2], msgs->param[3],
                                      FALSE);
  }

  item->icon = MsgTransDyn_LookupPS(msgs->fd,
                                    control_splice_token("ItemIcon", item_num),
                                    msgs->param[0], msgs->param[1],
                                    msgs->param[2], msgs->param[3],
                                    TRUE);
  if (!item->icon)
    return FALSE;

  item->script = MsgTransDyn_LookupPS(msgs->fd,
                                      control_splice_token("ItemScript",
                                                           item_num),
                                      msgs->param[0], msgs->param[1],
                                      msgs->param[2], msgs->param[3],
                                      FALSE);

  item->location_leaf = MsgTransDyn_LookupPS(msgs->fd,
                                      control_splice_token("ItemLeaf",
                                                           item_num),
                                      msgs->param[0], msgs->param[1],
                                      msgs->param[2], msgs->param[3],
                                      FALSE);

  LOG("Item %d name '%s', disc %d, location %d, location leaf %s,\n",
      item_num, item->name,
      item->disc, item->location,
      item->location_leaf ? item->location_leaf : "<none>");
  LOGQ("  Optional %d, icon '%s', script '%s'\n",
       item->optional, item->icon, item->script ? item->script : "<none>");

  return TRUE;
}

control_item *control_item_new_and_lookup(int item_num,
                                          const control_msgtrans_data *msgs)
{
  control_item *item = control_item_new();

  if (!item)
    return NULL;

  if (!control_item_lookup(item, item_num, msgs))
  {
    control_item_delete(item);
    return NULL;
  }

  return item;
}

int control_item_count_stages(const control_item *item)
{
  return 3 + (item->script != NULL);
}



control_location *control_location_new(void)
{
  control_location *location = malloc(sizeof(control_location));

  if (!location)
  {
    Error_Report(0, MSG_NOMEM);
    return NULL;
  }

  location->icon = NULL;
  location->prompt = NULL;
  location->pathname = NULL;
  location->leafname = NULL;
  location->optional = control_OptionalPrune;

  return location;
}

void control_location_delete(control_location *location)
{
  free(location->icon);
  free(location->prompt);
  free(location->pathname);
  free(location);
}

BOOL control_location_lookup(control_location *location, int location_num,
                             const control_msgtrans_data *msgs)
{
  char *path;

  location->icon = MsgTransDyn_LookupPS(msgs->fd,
                                        control_splice_token("LocationIcon",
                                                             location_num),
                                        msgs->param[0], msgs->param[1],
                                        msgs->param[2], msgs->param[3],
                                        TRUE);
  if (!location->icon)
    return FALSE;

  location->prompt = MsgTransDyn_LookupPS(msgs->fd,
                                          control_splice_token("LocationPrompt",
                                                               location_num),
                                          msgs->param[0], msgs->param[1],
                                          msgs->param[2], msgs->param[3],
                                          TRUE);
  if (!location->prompt)
    return FALSE;

  path = MsgTransDyn_LookupPS(msgs->fd,
                              control_splice_token("LocationPath",
                                                   location_num),
                              msgs->param[0], msgs->param[1],
                              msgs->param[2], msgs->param[3],
                              FALSE);
  if (path)
  {
    location->pathname = fsutils_canonicalise_path(path, FALSE);
    free(path);
  }

  location->leafname = MsgTransDyn_LookupPS(msgs->fd,
                                   control_splice_token("LocationLeaf",
                                                        location_num),
                                   msgs->param[0], msgs->param[1],
                                   msgs->param[2], msgs->param[3],
                                   TRUE);
  if (!location->leafname)
    return FALSE;

  location->optional = MsgTransDyn_LookupOptional(msgs->fd,
                                   control_splice_token("LocationOptional",
                                                        location_num));

  return TRUE;
}

control_location *control_location_new_and_lookup(int location_num,
                                                  const
                                                  control_msgtrans_data *msgs)
{
  control_location *location = control_location_new();

  if (!location)
    return NULL;
  if (!control_location_lookup(location, location_num, msgs))
  {
    control_location_delete(location);
    return NULL;
  }
  return location;
}



control_disc *control_disc_new(void)
{
  control_disc *disc = malloc(sizeof(control_disc));

  if (!disc)
  {
    Error_Report(0, MSG_NOMEM);
    return NULL;
  }

  disc->human_name = NULL;
  disc->num_items = disc->installed_items = 0;
  disc->items = NULL;
  disc->num_stages = disc->installed_stages = 0;

  return disc;
}

void control_disc_delete(control_disc *disc)
{
  free(disc->human_name);
  free(disc->items);
  free(disc);
}

BOOL control_disc_lookup(control_disc *disc, int disc_num,
                         const control_msgtrans_data *msgs)
{
  disc->human_name = MsgTransDyn_LookupPS(msgs->fd,
                                        control_splice_token("DiscHumanName",
                                                             disc_num),
                                        msgs->param[0], msgs->param[1],
                                        msgs->param[2], msgs->param[3],
                                        TRUE);
  if (!disc->human_name)
    return FALSE;

  return TRUE;
}

control_disc *control_disc_new_and_lookup(int disc_num,
                                          const control_msgtrans_data *msgs)
{
  control_disc *disc = control_disc_new();

  if (!disc)
    return NULL;
  if (!control_disc_lookup(disc, disc_num, msgs))
  {
    control_disc_delete(disc);
    return NULL;
  }
  return disc;
}

int control_disc_count_stages(control_disc *disc, int num)
{
  int i;

  disc->num_stages = 0;
  for (i = 0; i < disc->num_items; ++i)
    disc->num_stages += control_item_count_stages(disc->items[i]);
  LOG("Disc %d contains %d installation stages\n", num, disc->num_stages);

  return disc->num_stages;
}




control_master_data *control_master_data_new(void)
{
  control_master_data *master = malloc(sizeof(control_master_data));

  if (!master)
  {
    Error_Report(0, MSG_NOMEM);
    return NULL;
  }

  master->msgs = NULL;
  master->num_items = 0;
  master->items = NULL;
  master->num_locations = 0;
  master->locations = NULL;
  master->installed_locations = 0;
  master->num_discs = 0;
  master->discs = NULL;
  master->installed_discs = 0;
  master->master_location_dir = NULL;
  master->num_stages = master->installed_stages = 0;
  master->reboot = TRUE;

  return master;
}

BOOL control_master_data_open_control_file(control_master_data *master,
                                    const char *control_filename)
{
  master->msgs = control_msgtrans_data_new(control_filename);
  if (!master->msgs)
    return FALSE;

  return TRUE;
}

BOOL control_master_data_lookup_items(control_master_data *master)
{
  int i;

  master->num_items = MsgTransDyn_LookupInt(master->msgs->fd, "NumberOfItems",
                                            TRUE, -1);
  if (master->num_items <= 0)
    return FALSE;

  master->items = malloc(master->num_items * sizeof(control_item *));
  if (!master->items)
  {
    Error_Report(0, MSG_NOMEM);
    return FALSE;
  }
  for (i = 0; i < master->num_items; ++i)
  {
    master->items[i] = NULL;
  }

  for (i = 0; i < master->num_items; ++i)
  {
    const char *val = NULL;
    char *env = MsgTransDyn_LookupPS(master->msgs->fd,
                                     control_splice_token("ItemExcludeIf", i),
                                     master->msgs->param[0],
                                     master->msgs->param[1],
                                     master->msgs->param[2],
                                     master->msgs->param[3],
                                     FALSE);

    if (env && env[0])
      val = getenv(env);
    if (!val || !val[0])
    {
      master->items[i] = control_item_new_and_lookup(i, master->msgs);
      if (!master->items[i])
        return FALSE;
    }
    else
    {
      master->items[i] = control_item_new();
      LOG("Item %d is excluded by variable %s\n", i, env);
    }
    free(env);
  }

  LOGQ("\n");

  return TRUE;
}

BOOL control_master_data_lookup_locations(control_master_data *master)
{
  int i;

  master->num_locations = MsgTransDyn_LookupInt(master->msgs->fd,
                                                "NumberOfLocations",
                                                TRUE, -1);
  if (master->num_locations <= 0)
    return FALSE;

  master->locations = malloc(master->num_locations
                             * sizeof(control_location *));
  if (!master->locations)
  {
    Error_Report(0, MSG_NOMEM);
    return FALSE;
  }
  for (i = 0; i < master->num_locations; ++i)
  {
    master->locations[i] = NULL;
  }

  for (i = 0; i < master->num_locations; ++i)
  {
    const char *val = NULL;
    char *env = MsgTransDyn_LookupPS(master->msgs->fd,
                                     control_splice_token("LocationExcludeIf",
                                                          i),
                                     master->msgs->param[0],
                                     master->msgs->param[1],
                                     master->msgs->param[2],
                                     master->msgs->param[3],
                                     FALSE);
    if (env && env[0])
      val = getenv(env);
    if (!val || !val[0])
    {
      master->locations[i] = control_location_new_and_lookup(i, master->msgs);
      if (!master->locations[i])
        return FALSE;
    }
    else
    {
      master->locations[i] = control_location_new();
      LOG("Location %d is excluded by variable %s\n", i, env);
    }
    free(env);
  }

  LOGQ("\n");

  return TRUE;
}

BOOL control_master_data_lookup_discs(control_master_data *master)
{
  int i;

  master->num_discs = MsgTransDyn_LookupInt(master->msgs->fd, "NumberOfDiscs",
                                            TRUE, -1);
  if (master->num_discs <= 0)
    return FALSE;

  master->discs = malloc(master->num_discs * sizeof(control_disc *));
  if (!master->discs)
  {
    Error_Report(0, MSG_NOMEM);
    return FALSE;
  }
  for (i = 0; i < master->num_discs; ++i)
  {
    master->discs[i] = NULL;
  }

  for (i = 0; i < master->num_discs; ++i)
  {
    master->discs[i] = control_disc_new_and_lookup(i, master->msgs);
    if (!master->discs[i])
      return FALSE;
  }

  return TRUE;
}

BOOL control_master_data_lookup_all(control_master_data *master)
{
  char *env = NULL;
  const char *val = NULL;

  if (!control_master_data_lookup_items(master)
      || !control_master_data_lookup_locations(master)
      || !control_master_data_lookup_discs(master))
    return FALSE;

  env = MsgTransDyn_LookupPS(master->msgs->fd, "NoRebootIf",
                             master->msgs->param[0], master->msgs->param[1],
                             master->msgs->param[2], master->msgs->param[3],
                             FALSE);
  if (env)
  {
    val = getenv(env);
    free(env);
    if (val && val[0])
      master->reboot = FALSE;
  }
  LOG("Reboot flag: %d\n", master->reboot);

  return TRUE;
}

control_master_data *control_master_data_new_with_lookup(const char *
                                                         control_filename)
{
  control_master_data *master = control_master_data_new();

  if (!master)
    return NULL;

  if (!control_master_data_open_control_file(master, control_filename) ||
      !control_master_data_lookup_all(master))
  {
    free(master);
    return NULL;
  }

  return master;
}

int control_master_data_count_optional_items(const control_master_data *master)
{
  int i;
  int optional = 0;

  for (i = 0; i < master->num_items; ++i)
  {
    if (master->items[i]->optional == control_OptionalOff ||
        master->items[i]->optional == control_OptionalOn)
      ++optional;
  }

  return optional;
}

int control_master_data_get_nth_optional_item(const control_master_data *master,
                                              int opt)
{
  int i;

  for (i = 0; i < master->num_items; ++i)
  {
    if ((master->items[i]->optional == control_OptionalOff ||
         master->items[i]->optional == control_OptionalOn) &&
        !opt--)
    {
      return i;
    }
  }
  return -1;
}


void control_master_data_prune_items(control_master_data *master, BOOL also_off)
{
  control_prune_items(&master->num_items, master->items, also_off);
  LOG("After pruning, remaining items are:\n");
  control_enumerate(master);
}

static void control_report_bad_number(const char *token, int item)
{
  char item_str[8];

  sprintf(item_str, "%d", item + 1);
  Error_Report(0, MsgTransDyn_LookupPS(messages, token, item_str,
                                       NULL, NULL, NULL, TRUE));
  exit(0);
}

BOOL control_master_data_populate_disc_items(control_master_data *master)
{
  int i;

  /* First look at all items so we know how much space to allocate for each
     disc
   */
  for (i = 0; i < master->num_items; ++i)
  {
    int disc;
    int location;

    if (master->items[i]->optional == control_OptionalPrune)
      continue;

    location = master->items[i]->location;
    if (location < 0 || location >= master->num_locations)
      control_report_bad_number("InvalidLocation", i);

    disc = master->items[i]->disc;
    if (disc < 0 || disc >= master->num_discs)
      control_report_bad_number("InvalidDisc", i);
    ++master->discs[disc]->num_items;
  }

  for (i = 0; i < master->num_discs; ++i)
  {
    master->discs[i]->items = malloc(master->discs[i]->num_items
                                     * sizeof(control_item *));
    if (!master->discs[i]->items)
    {
      Error_Report(0, MSG_NOMEM);
      return FALSE;
    }
    /* Now reset num_items prior to second pass through all items */
    master->discs[i]->num_items = 0;
  }

  /* Make second pass through all items, this time adding their pointers
     to discs' arrays
   */
  for (i = 0; i < master->num_items; ++i)
  {
    int disc = master->items[i]->disc;

    master->discs[disc]->items[master->discs[disc]->num_items++]
            = master->items[i];
  }

  return TRUE;
}

BOOL control_master_data_lookup_master_location(control_master_data *control)
{
  int loc = MsgTransDyn_LookupInt(control->msgs->fd, "MasterLocation",
                                  FALSE, -1);

  if (loc < 0 || loc >= control->num_locations ||
      !control->locations[loc]->pathname)
  {
    MsgTrans_Report(messages, "InvalidMaster", FALSE);
    return FALSE;
  }

  control->master_location_dir = control->locations[loc]->pathname;

  return TRUE;
}

int control_master_data_count_stages(control_master_data *master)
{
  int i;

  master->num_stages = 0;
  for (i = 0; i < master->num_discs; ++i)
  {
    master->num_stages += control_disc_count_stages(master->discs[i], i);
  }
  LOG("There are %d installation stages in total\n\n", master->num_stages);

  return master->num_stages;
}



void control_prune_items(int *num_items, control_item **items, BOOL also_off)
{
  int i;

  for (i = 0; i < *num_items; ++i)
  {
    if (items[i]->optional == control_OptionalPrune ||
        (items[i]->optional == control_OptionalOff && also_off))
    {
      LOG("Pruning item %d\n", i);
      control_item_delete(items[i]);
      if (i < *num_items - 1)
      {
        memmove(items + i, items + i + 1,
                (*num_items - i - 1) * sizeof(control_item *));
      }
      --*num_items;
      --i;   /* Memmove makes i point to next item, so cancel out loop ++ */
    }
  }
}
#ifdef NSTALL_DEBUG
void control_enumerate(control_master_data *control)
{
  int i;
  int d;

  LOGQ("%d items\n", control->num_items);
  for (i = 0; i < control->num_items; ++i)
  {
    LOGQ("%3d %s\n", i, control->items[i]->name);
  }
  LOGQ("%d/%d discs installed\n", control->installed_discs, control->num_discs);
  for (d = 0; d < control->num_discs; ++d)
  {
    LOGQ("Disc %d has %d/%d items installed\n", d,
          control->discs[d]->installed_items, control->discs[d]->num_items);
    for (i = 0; i < control->discs[d]->num_items; ++i)
    {
      LOGQ("%3d %s\n", i, control->discs[d]->items[i]->name);
    }
  }
  LOGQ("\n");
}
#endif
