#include "smallutils.h"
#include "sexpr.h"
#include "gui.C"

#define MARGIN 20
#define FONTNAME_SMALL FIXED_MEDIUM_18
#define FONTNAME_LARGE FIXED_20

char *state_filename;
window *the_window;
font_t *the_font, *the_big_font;

typedef enum {
  target_t_all = 0, target_t_domain, target_t_fqdn
} target_type;

struct target_t {
  target_type type;
  char *data;
};

struct permit_t {
  char *key;
  char *requestor;
  target_t target;
};

vec<permit_t *> the_permits;
vec<permit_t *> the_saved_permits;

static gsymbol *s_all = g_symbol ("all");
static gsymbol *s_domain = g_symbol ("domain");
static gsymbol *s_fqdn = g_symbol ("fqdn");

static gsymbol * type_syms[] = { s_all, s_domain, s_fqdn };

permit_t *
obj2permit (gob *o)
{
  gstring *key = togstring (g_nth (o, 0));
  gstring *requestor = togstring (g_nth (o, 1));
  gsymbol *targettype = togsymbol (g_nth (o, 2));
  gstring *targetdata = togstring (g_nth (o, 3));

  if (!(key && requestor && targettype))
    return NULL;

  permit_t *permit = New permit_t;
  permit->key = key->cstr ();
  permit->requestor = requestor->cstr ();

  if (g_equal (targettype, s_all)) {
    permit->target.type = target_t_all;
    permit->target.data = NULL;
  }
  else if (g_equal (targettype, s_domain)) {
    if (!targetdata)
      return NULL;
    permit->target.type = target_t_domain;
    permit->target.data = targetdata->cstr ();
  }
  else if (g_equal (targettype, s_fqdn)) {
    if (!targetdata)
      return NULL;
    permit->target.type = target_t_fqdn;
    permit->target.data = targetdata->cstr ();
  }
  else
    return NULL;

  return permit;
}

gob *
permit2obj (permit_t *permit)
{
  return g_cons (New gstring (permit->key),
                 g_cons (New gstring (permit->requestor),
                         g_cons (type_syms[permit->target.type],
                                 (permit->target.data)
                                 ? g_cons (New gstring (permit->target.data),
                                           g_nil)
                                 : g_nil)));
}

bool
list2permits (vec<permit_t *> &permits, gob *permits_obj)
{
  while (!g_null (permits_obj)) {
    gpair *plist = togpair (permits_obj);
    if (!plist)
      return false;
    permit_t *permit = obj2permit (plist->car);
    if (!plist)
      return false;
    permits.append (permit);
    permits_obj = plist->cdr;
  }
  return true;
}

gob *
permits2list (vec<permit_t *> &permits)
{
  gob *plist = g_nil;
  for (size_t i = 0; i < permits.length (); i++)
    plist = g_cons (permit2obj (permits[i]), plist);
  return plist;
}

gsymbol *version1 = g_symbol ("confirm-state-v1");

void
get_permits (vec<permit_t *> &permits, const char *filename) 
{
  permits.clear ();

  stream *s = open_file_stream (filename);
  if (!s)
    fatal ("%s: couldn't open permits file\n", filename);

  gob *state = g_read (s);
  if (!state)
    fatal ("%s: error reading s-expression\n", filename);

  gpair *statelist = togpair (state);
  gsymbol *version = statelist ? togsymbol (statelist->first ()) : NULL;
  if (! (version && g_equal (version, version1)))
    fatal ("%s: unrecognized confirm-state format\n", filename);

  if (! list2permits (the_permits, statelist->rest ()))
    fatal ("%: could not understand permits data\n", filename);
}

bool
save_permits (vec<permit_t *> &permits, const char *filename)
{
#ifndef PERMITS_FAKESAVE
  FILE *f = fopen (filename, "w");
  if (!f) {
    warn ("%s: couldn't write to file\n", filename);
    return false;
  }
#endif

  gob *state = g_cons (version1, permits2list (permits));
#ifdef PERMITS_FAKESAVE
  state->fwrite (stderr);
  fprintf (stderr, "\n");
#else
  if (state->fwrite (f) == -1
      || fprintf (f, "\n") == -1
      || fclose (f) == EOF)
  {
    warn ("%s: error writing state to file\n", filename);
    return false;
  }
#endif

  return true;
}

struct section_t {
  w_table *region;
  w_table *elts;
  size_t permits_begin;
  size_t permits_count;

  section_t (w_table *r, w_table *e, size_t pb)
    : region (r), elts (e), permits_begin (pb), permits_count (0) {}
};

section_t *the_section;

bool hostname_inc (char *s, char *&sp, char *&sdot)
{
  if (*sp == '\0' || (*sp == '.' && sp != sdot)) {
    if (!sdot)
      return false;
    while (sdot > s) {
      sdot--;
      if (*sdot == '.')
        break;
    }
    if (sdot == s) {
      sp = sdot;
      sdot = NULL;
    }
    else
      sp = sdot + 1;
  }
  else
    sp++;

  return true;
}

int hostname_cmp (char *a, char *b)
{
  char *adot = a + strlen (a), *bdot = b + strlen (b);
  char *ap = adot, *bp = bdot;

  while (1) {
    bool agood = hostname_inc (a, ap, adot);
    bool bgood = hostname_inc (b, bp, bdot);

    if (!agood && bgood)
      return -1;
    if (agood && !bgood)
      return 1;
    if (!agood && !bgood)
      return 0;

    if (*ap != *bp)
      return *ap - *bp;
  }
}

int key_cmp (char *a, char *b)
{
  char *a_at = strchr (a, '@');
  char *b_at = strchr (b, '@');

  if (!a_at && b_at)
    return -1;
  if (a_at && !b_at)
    return 1;
  if (!a_at && !b_at)
    return strcmp (a, b);

  int res = hostname_cmp (a_at + 1, b_at + 1);
  if (res)
    return res;

  res = strncmp (a, b, min (a_at - a, b_at - b));
  if (res)
    return res;

  if (a_at - a < b_at - b)
    return -1;
  if (a_at -a > b_at - b)
    return 1;
  return 0;
}

struct field_t {
  virtual char *name () = 0;
  virtual char *value (permit_t &p) = 0;
  virtual int cmp (const permit_t &a, const permit_t &b) = 0;
  virtual bool alignright () { return true; }
  virtual bool alignbottom () { return false; }
};

vec<field_t *> the_fields;

struct field_key_t : public field_t {
  virtual char *name () { return "Key"; }
  virtual char *value (permit_t &p) {
    return p.key;
  }
  virtual int cmp (const permit_t &a, const permit_t &b) {
    return key_cmp (a.key, b.key);
  }
} the_key_field;

struct field_requestor_t : public field_t {
  virtual char *name () { return "Requestor"; }
  virtual char *value (permit_t &p) {
    return p.requestor;
  }
  virtual int cmp (const permit_t &a, const permit_t &b) {
    return strcmp (a.requestor, b.requestor);
  }
} the_requestor_field;

struct field_target_t : public field_t {
  virtual char *name () { return "Target"; }
  virtual char *value (permit_t &p)
  {
    str *s = New str;
    switch (p.target.type) {
      case target_t_all:
        *s << "ALL";
        break;
      case target_t_domain:
        *s << "*.";
      default:
        *s << p.target.data;
    }
    return s->cstr ();
  }
  virtual int cmp (const permit_t &a, const permit_t &b)
  {
    if (a.target.type != target_t_all && b.target.type != target_t_all) {
      int res = hostname_cmp (a.target.data, b.target.data);
      return res ? res : a.target.type - b.target.type;
    }
    else
      return a.target.type - b.target.type;
  }
} the_target_field;

int permit_cmp (const void *aa, const void *bb)
{
  const permit_t **a = static_cast<const permit_t **> (aa);
  const permit_t **b = static_cast<const permit_t **> (bb);
  for (size_t i = 0; i < the_fields.length (); i++)
  {
    int res = the_fields[i]->cmp (**a, **b);
    if (res != 0)
      return res;
  }
  return 0;
}

widg *
hspace (dist width) {
  return New w_space (width, 0);
}

widg *
vspace (dist height) {
  return New w_space (0, height);
}

widg *
padded (widg *contents, dist left, dist right, dist top, dist bottom)
{
  w_table *vbox = New w_table (the_window, vertical);
  vbox->add_row (vspace (top));

  w_table *hbox = New w_table (the_window, horizontal);
  hbox->add_col (hspace (left));
  hbox->add_col (contents);
  hbox->add_col (hspace (right));

  vbox->add_row (hbox);
  vbox->add_row (vspace (bottom));
  return vbox;
}

struct cell : public widg {
  section_t *const section;
  w_table *bounds;
  widg *contents;
  color_t hilite_color;
  bool alignright, alignbottom;

  cell (section_t *section, w_table *bounds, widg *contents,
        bool alignright = false, bool alignbottom = false)
    : widg (contents->w), section (section), bounds (bounds),
      contents (contents), hilite_color (contents->w->get_color ("lightblue")),
      alignright (alignright), alignbottom (alignbottom)
      {}

  virtual void draw () {
    w_table *region = section->region;

    w->default_gcvalues ();
    if (the_section == section) {
      XSetForeground (w->display, w->gc, hilite_color);
      XFillRectangle (w->display, w->pix, w->gc, region->pos.x, region->pos.y,
                      region->width (), region->height ());
    }

    w->default_gcvalues ();
    XDrawRectangle (w->display, w->pix, w->gc, bounds->pos.x, bounds->pos.y,
                    bounds->width (), bounds->height ());

    contents->draw ();
  }

  virtual void mouseup (point p) {
    if (the_section == section)
      the_section = NULL;
    else
      the_section = section;

    dirty ();
  }

  virtual void setpos (point p)
  {
    contents->setpos (point (alignright
                             ? p.x + bounds->width () - contents->width ()
                             : p.x,
                             alignbottom
                             ? p.y + bounds->height () - contents->height ()
                             : p.y));
  }
  virtual dist width () { return contents->width (); }
  virtual dist height () { return contents->height (); }
};

widg *
make_cell (section_t *section, char *text, bool alignright, bool alignbottom)
{
  w_table *box = New w_subtable (the_window, vertical);
  box->add_elt (New cell (section, box,
                          padded (New w_text (the_window, the_font, text),
                                  8, 8, 4, 4), alignright, alignbottom));
  return box;
}

void refresh_table ();

struct hcell : public widg {
  widg *bounds;
  widg *contents;
  field_t *field;

  hcell (widg *bounds, widg *contents, field_t *field)
    : widg (contents->w), bounds (bounds), contents (contents), field (field)
  {}

  virtual void setpos (point p) { contents->setpos (p); }
  virtual dist width () { return contents->width (); }
  virtual dist height () { return contents->height (); }
  virtual void draw () { 
    w->default_gcvalues ();
    XDrawRectangle (w->display, w->pix, w->gc, bounds->pos.x, bounds->pos.y,
                    bounds->width (), bounds->height () - 1);
    XSetLineAttributes (w->display, w->gc, 3, w->gcvalues.line_style,
                        w->gcvalues.cap_style, w->gcvalues.join_style);
    XDrawLine (w->display, w->pix, w->gc, bounds->pos.x, bounds->bottom () - 2,
               bounds->right (), bounds->bottom () - 2);

    contents->draw ();
  }

  virtual void mouseup (point p)
  {
    for (size_t f = 0; f < the_fields.length (); f++)
      if (the_fields[f] == field) {
        if (f > 0) {
          the_fields[f] = the_fields[f - 1];
          the_fields[f - 1] = field;
          the_permits.qsort (&permit_cmp);
          refresh_table ();
        }
        break;
      }
  }
};

widg *
make_header (field_t *field)
{
  w_table *box = New w_subtable (the_window, vertical);
  box->add_elt (New hcell (box, padded (New w_text (the_window, the_big_font,
                                                    field->name ()),
                                        8, 8, 4, 4), field));
  return box;
}

w_table *
make_table (vec<permit_t *> &permits, vec<field_t *> &fields)
{
  w_table *headers, *sub = NULL;
  for (size_t f = 0; f < fields.length (); f++) {
    w_table *tmp = New w_subtable (the_window, horizontal);
    if (sub)
      sub->add_col (tmp);
    else
      headers = tmp;
    sub = tmp;
    sub->add_col (make_header (fields[f]));
  }

  w_table *table = New w_table (the_window, vertical);
  table->add_row (headers);

  w_table *data_table = New w_subtable (the_window, vertical);
  #if 0
  w_scroll_submeasured *scrolling_data_table =
    New w_scroll_submeasured (data_table, vertical, 200);
  table->add_row (scrolling_data_table);
  #else
  table->add_row (data_table);
  #endif

  vec<section_t *> sections;
  sections.set_len (fields.length () + 1);
  sections[0] = New section_t (NULL, data_table, 0);

  permit_t *lastp = NULL;
  for (size_t p = 0; p < permits.length (); p++) {
    permit_t *permit = permits[p];

    bool fresh = false;
    for (size_t f = 0; f < fields.length (); f++) {
      const size_t s = f + 1;
      if (fresh || !lastp || fields[f]->cmp (*permit, *lastp) != 0) {
        w_table *row = New w_subtable (the_window, horizontal);
        w_table *elts = (f < fields.length () - 1)
                        ? New w_subtable (the_window, vertical) : NULL;
        sections[s] = New section_t (row, elts, p);
        row->add_col (make_cell (sections[s], fields[f]->value (*permit),
                                 fields[f]->alignright (),
                                 fields[f]->alignbottom ()));
        if (elts)
          row->add_col (elts);
        sections[s - 1]->elts->add_row (row);
        fresh = true;
      }
    }
    for (size_t i = 0; i < sections.length (); i++)
      sections[i]->permits_count++;

    lastp = permit;
  }
  assert (sections[0]->permits_count == permits.length ());

  return table;
}

w_box *the_container;
widg *the_table;

void
refresh_table ()
{
  the_section = NULL;
  // assert (the_window->remwidg (the_table) == 1);

  the_table = make_table (the_permits, the_fields);
  the_container->replace (the_table);
  the_container->setpos (the_container->pos);
}

void
do_delete ()
{
  if (the_section) {
    the_permits.remove (the_section->permits_begin,
                        the_section->permits_count);
    refresh_table ();
  }
  else
    warn ("null delete\n");
}

void
do_revert ()
{
  the_permits = the_saved_permits;
  the_permits.qsort (&permit_cmp);

  refresh_table ();
}

void
do_cancel ()
{
  exit (0);
}

void
do_save ()
{
  if (save_permits (the_permits, state_filename))
    exit (0);
}

int
main (int argc, char **argv)
{
  smallutils_init ();

  argv++; argc--;
  if (argc > 0) {
    state_filename = argv[0];
    argv++; argc--;
  }
  else {
    char *home = getenv ("HOME");
    assert (home);
    static str fstr;
    fstr << home << "/.sfs/confirm_state_0";
    state_filename = fstr.cstr ();
  }
  warn ("file: %s\n", state_filename);

  the_fields.append (&the_requestor_field);
  the_fields.append (&the_key_field);
  the_fields.append (&the_target_field);

  get_permits (the_permits, state_filename);

  the_saved_permits = the_permits;
  the_permits.qsort (&permit_cmp);

  window *w = the_window = New window ("Manage Permits");

  the_font = w->get_font (FONTNAME_SMALL);
  the_big_font = w->get_font (FONTNAME_LARGE);

  the_section = NULL;
  the_table = make_table (the_permits, the_fields);

  w_table *contents = New w_table (w, vertical);

  the_container = New w_box (the_table, w->gcvalues.background);

  contents->add_row (the_container);
  contents->add_row (vspace (10));
  w_table *buttons = New w_table (w, horizontal);
  buttons->add_col (New w_button (w, the_big_font, "Delete", &do_delete));
  buttons->add_col (hspace (10));
  buttons->add_col (New w_button (w, the_big_font, "Revert", &do_revert));
  buttons->add_col (hspace (10));
  buttons->add_col (New w_button (w, the_big_font, "Save", &do_save));
  buttons->add_col (hspace (10));
  buttons->add_col (New w_button (w, the_big_font, "Cancel", &do_cancel));
  contents->add_row (buttons);

  widg *winbox = New w_box (padded (contents, MARGIN, MARGIN, MARGIN, MARGIN),
                            w->get_color ("lightgray"));
  winbox->setpos (point (0, 0));

  w->addwidg (winbox);
  w->resize (winbox->right (), winbox->bottom ());

  w->start ();
  w->flush ();
}
