/* -*- c-basic-offset:2; tab-width:2; indent-tabs-mode:nil -*- */

#include <stdio.h>  /* printf */
#include <signal.h> /* sigaction */
#include <stdlib.h>
#include <string.h>
#if defined(__sunos) || defined(__sun)
#include <alloca.h>
#endif

#include "comm.h"
#include "data.h"

/* should be removed... */
#include "data_entry_string.h"
#include "data_entry_bool.h"
#include "data_entry_color.h"
#include "data_entry_radio.h"
#include "data_entry_numeric.h"

#define MAX_SECTION 8

typedef struct {
  section_t section[MAX_SECTION];
  int filled;
  int selected;
  display_state_t state;
} config_data_t;

/*
 *  global variables
 */

/* must be global to be changed by signal handler */
static int cols;
static int rows;
static int redraw_all = 0;

section_t *current_section(config_data_t *data);
section_t *section_from_name(config_data_t *data, char *name);
section_t *section_from_name(config_data_t *data, char *name);
window_t *section_window_new(window_t *parent, config_data_t *data);
section_t *section_add(config_data_t *data, const char *name);
int section_reset(section_t *section);
int display_section(window_t *window, config_data_t *data);

int data_reset(config_data_t *data);
void data_free(config_data_t *data);

int edit_entry(window_t *window, config_data_t *data);
int display_entry(window_t *window, config_data_t *data);
int select_entry(config_data_t *data);

int query_exit(window_t *parent);
int init_data(config_data_t *data);

static void help_msg(void) {
  printf("[Usage]\n");
  printf(" mlcc               : Show configuration screen.\n");
  printf(" mlcc -h/--help     : Show this message.\n");
  printf(
      " mlcc exec [command]: Execute mlterm command. (full_reset, mlclient, "
      "open_pty and so on)\n");
  printf(" mlcc [key]         : Get current value of [key].\n");
  printf(" mlcc [key] [value] : Set [value] for [key].\n");
  printf(
      " mlcc [font file name] [charset] : Get font name of [charset] in [font "
      "file name].\n");
  printf(
      " mlcc [font file name] [charset] [font name] : Set [font name] in [font "
      "file name].\n");
  printf(" mlcc color [color name] [rgb] : Set [rgb] for [color name].\n");
  printf(" mlcc color [color name] : Get rgb of [color name].\n");
  printf(
      " (See doc/en/PROTOCOL, PROTOCOL.font and PROTOCOL.color for "
      "configuration details.)\n");
}

/*
 *  backyard
 */

/* recover terminal settings and die gracefully */
static void finalize(void) {
  termios_final();
  unset_altscr();
  unset_keypad();
  normal_char();
  cursor_show();
  exit(0);
}

/*
 *  signal handler
 */

static void signal_int(int signo) { finalize(); }

static void signal_winch(int signo) {
  term_size(&cols, &rows);
  redraw_all = 1; /* do not redraw immediately to avoid overload */
}

/*
 *  config data handler
 */

section_t *current_section(config_data_t *data) { return &(data->section[data->selected]); }

section_t *section_from_name(config_data_t *data, char *name) {
  int i;

  for (i = 0; i < data->filled; i++)
    if (strcmp(data->section[i].name, name) == 0) return &(data->section[i]);
  return NULL;
}

section_t *section_add(config_data_t *data, const char *name) {
  if (data->filled > MAX_SECTION - 2) return NULL;
  data->section[data->filled].name = name;
  data->section[data->filled].size = 0;
  data->section[data->filled].selected = 0;
  data->section[data->filled].modified = 0;
  data->section[data->filled].maxwidth = 0;
  return &(data->section[data->filled++]);
}

window_t *section_window_new(window_t *parent, config_data_t *data) {
  int cur = 0;
  int pos = 1;
  int skip = 0;

  while (cur < data->filled) {
    pos += (strlen(data->section[cur].name) + 2);
    if (pos > (window_width(parent))) {
      cur--;
      pos = 2;
      skip++;
    }
    cur++;
  }
  return window_new(0, 0, pos, skip + 3, 1, parent);
}

int section_reset(section_t *section) {
  int i;
  for (i = 0; i < section->size; i++) {
    entry_reset(&(section->entry[i]));
  }
  return 0;
}

int display_section(window_t *window, config_data_t *data) {
  int cur = 0;
  int pos = 0;
  int len;
  int skip = 0;

  while (cur < data->filled) {
    len = strlen(data->section[cur].name) + 2;
    if ((pos + len) > (window->right - window->left)) {
      cur--;
      pos = 1;
      skip++;
    } else {
      display_str(window, pos + 1, skip + 1, (data->section[cur].name),
                  (data->selected == cur) ? (data->state == DS_SELECT ? DC_POINTED : DC_CHOOSED)
                                          : DC_NORMAL);
      pos += len;
    }
    cur++;
  }
  return 0;
}

int data_reset(config_data_t *data) {
  int i;

  for (i = 0; i < data->filled; i++) section_reset(&(data->section[i]));
  return 0;
}

void data_free(config_data_t *data) {
  for (; data->filled > 0; (data->filled)--) section_free(&data->section[data->filled - 1]);
}

int edit_entry(window_t *window, config_data_t *data) {
  section_t *section;
  entry_t *entry;
  int result = 0xDEADBEEF;

  section = current_section(data);
  entry = &(section->entry[section->selected]);
  result = entry_edit(window, entry, section->maxwidth + 4, section->selected * 2);
  if (result == 0) return 0;
  if (result < 0) data->state = DS_SELECT;

  entry_apply(entry);
  if (entry->modified) section->modified = 1;
  return 1;
}

int display_entry(window_t *window, config_data_t *data) {
  section_t *current;
  int i;

  current = current_section(data);
  for (i = 0; i < current->size; i++) {
    display_str(window, 1, i * 2, (current->entry[i].name),
                (current->selected == i) ? (data->state == DS_SELECT ? DC_CHOOSED : DC_POINTED)
                                         : DC_NORMAL);

    window_addstr(window, current->maxwidth + 3, i * 2, ":");
    if ((i == current->selected) && (data->state == DS_EDIT))
      entry_display(window, &(current->entry[i]), current->maxwidth + 4, i * 2, 1);
    else
      entry_display(window, &(current->entry[i]), current->maxwidth + 4, i * 2, 0);
  }
  return 0;
}

int select_entry(config_data_t *data) {
  int buffer;

  section_t *cur;
  cur = current_section(data);
  buffer = read_one();
  switch (buffer) {
    case KEY_ESC:
      data->state = DS_CANCEL;
      return 1; /* redraw */
    case KEY_UP:
      if (cur->selected > 0) cur->selected--;
      return 1;    /* redraw */
    case KEY_DOWN: /* DOWN */
      if (cur->selected < cur->size - 1) cur->selected++;
      return 1;     /* redraw */
    case KEY_RIGHT: /* RIGHT */
      if (data->selected < data->filled - 1) data->selected++;
      return 1;    /* redraw */
    case KEY_LEFT: /* LEFT */
      if (data->selected > 0) data->selected--;
      return 1; /* redraw */
    case 10:    /* ret */
      data->state = DS_EDIT;
      return 1; /* redraw */
    default:
      /* ignore */
      return 0; /* don't redraw*/
  }
}

int query_exit(window_t *parent) {
  window_t *query;
  int flag, buffer;
  int state = 0;

  flag = 1;
  query = window_new((parent->right - parent->left) / 2 - 7, (parent->bottom - parent->top) / 2 - 2,
                     (parent->right - parent->left) / 2 + 7, (parent->bottom - parent->top) / 2 + 3,
                     1, parent);
  while (1) {
    if (flag) {
      window_clear(query);
      window_addstr(query, 0, 0, " Really Quit?");
      if (state == 0) {
        window_addstr(query, 1, 2, " <apply>");
        window_addstr(query, 1, 3, " discard ");
      } else {
        window_addstr(query, 1, 2, "  apply ");
        window_addstr(query, 1, 3, "<discard>");
      }
      flush_stdout();
      flag = 0;
    }
    buffer = read_one();
    switch (buffer) {
      case KEY_ESC: /* cursor key or ESC */
        window_free(query);
        return -1;
      case KEY_UP:
      case KEY_DOWN:
      case KEY_RIGHT:
      case KEY_LEFT:
        state = 1 - state;
        flag = 1;
        break;
      case 10: /* ret */
        window_free(query);
        return state;
      default:
        break;
    }
  }
}

/*
 *  initialize ... may be separated into conffile
 */

int init_data(config_data_t *data) {
  section_t *section;
  data->state = DS_SELECT;
  data->selected = 0;
  data->filled = 0;

  /* Check the number of entries in each section and change MAX_ENTRY in data.h as necessary. */

  section = section_add(data, "Encoding");
  entry_string_add(section, "Encoding", "encoding");
  entry_string_add(section, "Input Method", "input_method");
  entry_bool_add(section, "Bidi", "use_bidi");
  entry_bool_add(section, "Indic", "use_ind");
  entry_bool_add(section, "Combining", "use_combining");
  entry_bool_add(section, "Process via unicode", "copy_paste_via_ucs");

  section = section_add(data, "Appearance");
  entry_numeric_add(section, "Font size", "fontsize", 5, 50, "pix");
  entry_numeric_add(section, "Line space", "line_space", 0, 5, "pix");
  entry_numeric_add(section, "Width", "screen_width_ratio", 0, 100, "%");
  entry_bool_add(section, "Variable column width", "use_variable_column_width");
  entry_string_add(section, "Anti alias", "use_anti_alias");
  entry_string_add(section, "Type engine", "type_engine");

  section = section_add(data, "Color");
  entry_color_add(section, "Foreground color", "fg_color");
  entry_color_add(section, "Background color", "bg_color");
  entry_numeric_add(section, "Fade ratio", "fade_ratio", 0, 100, "%");
  entry_string_add(section, "Wall picture", "wall_picture");
  entry_numeric_add(section, "Brightness", "brightness", 0, -1, "%");
  entry_numeric_add(section, "Contrast", "contrast", 0, -1, "%");
  entry_numeric_add(section, "Gammma", "gamma", 0, -1, "%");
  entry_numeric_add(section, "Alpha", "alpha", 0, -1, "%");
  entry_bool_add(section, "Transparent background", "use_transbg");

  section = section_add(data, "Scrollbar");
  entry_radio_add(section, "Position", "scrollbar_mode", "none/left/right");
  entry_string_add(section, "View", "scrollbar_view_name");
  entry_color_add(section, "Foreground color", "sb_fg_color");
  entry_color_add(section, "Background color", "sb_bg_color");

  section = section_add(data, "Others");
  entry_numeric_add(section, "Tab width", "tabsize", 0, 100, "column");
  entry_numeric_add(section, "Backlog size", "logsize", 128, 2048, "line");
  entry_radio_add(section, "Meta key outputs", "mod_meta_mode", "none/esc/8bit");
  entry_radio_add(section, "Vertical mode", "vertical_mode", "none/cjk/mongol");
  entry_radio_add(section, "Bel mode", "bel_mode", "none/sound/visual");
  entry_bool_add(section, "Combining", "use_dynamic_comb");
  entry_bool_add(section, "Fullwidth", "use_multi_column_char");
  return 0;
}

int main(int argc, char **argv) {
  static config_data_t data;
  struct sigaction act;
  window_t *win_root = NULL, *win_section = NULL, *win_entry = NULL;

  if (argc == 2) {
    if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
      help_msg();
    } else {
      mlterm_get_param(argv[1]);
    }
    exit(0);
  } else if (argc >= 3 && strcmp(argv[1], "exec") == 0) {
    int i;
    char *cmd;
    size_t cmd_len = 0;
    for (i = 2; i < argc; i++) {
      /* +3 is for "" and space(or NULL terminator). */
      cmd_len += (strlen(argv[i]) + 3);
    }
    cmd = alloca(cmd_len);
    i = 2;
    strcpy(cmd, argv[i]);
    while (++i < argc) {
      int enclose;
      strcat(cmd, " ");
      if (strncmp(argv[2], "mlclient", 8) == 0 || strcmp(argv[2], "snapshot") == 0 ||
          strcmp(argv[2], "show_picture") == 0 || strcmp(argv[2], "scp") == 0) {
        enclose = 1;
        strcat(cmd, "\"");
      } else {
        enclose = 0;
      }
      strcat(cmd, argv[i]);
      if (enclose) {
        strcat(cmd, "\"");
      }
    }
    mlterm_exec(cmd);
    exit(0);
  } else if (argc == 3 || argc == 4) {
    char *p;

    if (argv[1][0] == 't' || argv[1][0] == 'v') {
      p = argv[1] + 1;
    } else {
      p = argv[1];
    }

    if (strcmp(p, "font") == 0 || strcmp(p, "aafont") == 0) {
      if (argc == 3) {
        mlterm_get_font_param(argv[1], argv[2]);
        exit(0);
      }
      if (argc == 4) {
        mlterm_set_font_param(argv[1], argv[2], argv[3]);
        exit(0);
      }
    } else if (strcmp(p, "color") == 0) {
      if (argc == 3) {
        mlterm_get_color_param(argv[2]);
        exit(0);
      }
      if (argc == 4) {
        mlterm_set_color_param(argv[2], argv[3]);
        exit(0);
      }
    } else if (argc == 3) {
      mlterm_set_param(argv[1], argv[2]);
      exit(0);
    }
  }

  if (!getenv("MLTERM")) {
    printf("MLTERM environment variable not set.\n");
    printf(
        "mlcc without options doesn't work on a terminal except mlterm "
        "or on a remote host.\n");
    exit(1);
  }

  sigemptyset(&(act.sa_mask));
  sigaddset(&act.sa_mask, SIGINT | SIGWINCH);
  act.sa_flags = SA_RESTART;

  act.sa_handler = signal_int;
  sigaction(SIGINT, &act, NULL);
  act.sa_handler = signal_winch;
  sigaction(SIGWINCH, &act, NULL);

  cursor_hide();
  set_keypad();
  set_altscr();
  termios_init();

  init_data(&data);
  if (term_size(&cols, &rows) == -1) goto FIN;

  redraw_all = 1;
  while (1) {
    if (redraw_all) {
      redraw_all = 0;
      window_free(win_section);
      window_free(win_entry);
      window_free(win_root);

      win_root = window_new(0, 0, cols - 1, rows - 1, 0, NULL);
      window_clear(win_root);

      win_section = section_window_new(win_root, &data);
      window_clear(win_section);

      win_entry = entry_window_new(win_section);
      window_clear(win_entry);
      display_section(win_section, &data);
      display_entry(win_entry, &data);
      flush_stdout();
    }
    switch (data.state) {
      case DS_SELECT:
        if (select_entry(&data)) {
          window_clear(win_entry); /* clear garbage */
          display_section(win_section, &data);
          display_entry(win_entry, &data);
          flush_stdout();
        }
        break;
      case DS_EDIT:
        if (edit_entry(win_entry, &data)) {
          window_clear(win_entry);             /* clear garbage */
          display_section(win_section, &data); /* change color */
          display_entry(win_entry, &data);
          flush_stdout();
        }
        break;
      case DS_CANCEL:
        switch (query_exit(win_entry)) {
          case 1:
            goto CANCEL;
          case 0:
            goto FIN;
          case -1:
            data.state = DS_SELECT;
            redraw_all = 1;
        }
        break;
    }
  }
CANCEL:
  /*XXX reset params to initial state*/
  data_reset(&data);
FIN:
  data_free(&data); /* destruct date tree. segv should be caught here if there
                       are bugs*/
  finalize();       /*recover terminal setting*/
  return 0;
}
