/*
 * dselect - Debian package maintenance user interface
 * main.cc - main program
 *
 * Copyright © 1994,1995 Ian Jackson <ian@chiark.greenend.org.uk>
 * Copyright © 2000,2001 Wichert Akkerman <wakkerma@debian.org>
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <compat.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <limits.h>
#if HAVE_LOCALE_H
#include <locale.h>
#endif
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#if defined(HAVE_NCURSESW_TERM_H)
#include <ncursesw/term.h>
#elif defined(HAVE_NCURSES_TERM_H)
#include <ncurses/term.h>
#else
#include <term.h>
#endif

#include <dpkg/i18n.h>
#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>
#include <dpkg/myopt.h>

#include "dselect.h"
#include "bindings.h"
#include "pkglist.h"

const char thisname[]= DSELECT;
const char printforhelp[]= N_("Type dselect --help for help.");

modstatdb_rw readwrite;
const char *admindir= ADMINDIR;
FILE *debug;
int expertmode= 0;

static keybindings packagelistbindings(packagelist_kinterps,packagelist_korgbindings);

struct table_t {
  const char *name;
  const int num;
};

static const struct table_t colourtable[]= {
  {"black",	COLOR_BLACK	},
  {"red",	COLOR_RED	},
  {"green",	COLOR_GREEN	},
  {"yellow",	COLOR_YELLOW	},
  {"blue",	COLOR_BLUE	},
  {"magenta",	COLOR_MAGENTA	},
  {"cyan",	COLOR_CYAN	},
  {"white",	COLOR_WHITE	},
  {NULL, 0},
};

static const struct table_t attrtable[]= {
  {"normal",	A_NORMAL	},
  {"standout",	A_STANDOUT	},
  {"underline",	A_UNDERLINE	},
  {"reverse",	A_REVERSE	},
  {"blink",	A_BLINK		},
  {"bright",	A_BLINK		}, // on some terminals
  {"dim",	A_DIM		},
  {"bold",	A_BOLD		},
  {NULL, 0},
};

/* A slightly confusing mapping from dselect's internal names to
 * the user-visible names.*/
static const struct table_t screenparttable[]= {
  {"list",		list		},
  {"listsel",		listsel		},
  {"title",		title		},
  {"infohead",		thisstate	},
  {"pkgstate",		selstate	},
  {"pkgstatesel",	selstatesel	},
  {"listhead",		colheads	},
  {"query",		query		},
  {"info",		info		},
  {"infodesc",		info_head	},
  {"infofoot",		whatinfo	},
  {"helpscreen",	helpscreen	},
  {NULL, 0},
};

/* Historical (patriotic?) colours. */
struct colordata color[]= {
  /* fore      back            attr */
  {COLOR_WHITE,        COLOR_BLACK,    0			}, // default, not used
  {COLOR_WHITE,        COLOR_BLACK,    0			}, // list
  {COLOR_WHITE,        COLOR_BLACK,    A_REVERSE		}, // listsel
  {COLOR_WHITE,        COLOR_RED,      0			}, // title
  {COLOR_WHITE,        COLOR_BLUE,     0			}, // thisstate
  {COLOR_WHITE,        COLOR_BLACK,    A_BOLD			}, // selstate
  {COLOR_WHITE,        COLOR_BLACK,    A_REVERSE | A_BOLD	}, // selstatesel
  {COLOR_WHITE,        COLOR_BLUE,     0			}, // colheads
  {COLOR_WHITE,        COLOR_RED,      0			}, // query
  {COLOR_WHITE,        COLOR_BLACK,    0			}, // info
  {COLOR_WHITE,        COLOR_BLACK,    A_BOLD			}, // info_head
  {COLOR_WHITE,        COLOR_BLUE,     0			}, // whatinfo
  {COLOR_WHITE,        COLOR_BLACK,    0			}, // help
};

struct menuentry {
  const char *command;
  const char *key;
  const char *option;
  const char *menuent;
  urqfunction *fn;
};

static const menuentry menuentries[]= {
  { "access",	N_("a"),	N_("[A]ccess"),	N_("Choose the access method to use."),			&urq_setup   },
  { "update",	N_("u"),	N_("[U]pdate"),	N_("Update list of available packages, if possible."),	&urq_update  },
  { "select",	N_("s"),	N_("[S]elect"),	N_("Request which packages you want on your system."),	&urq_list    },
  { "install",	N_("i"),	N_("[I]nstall"),N_("Install and upgrade wanted packages."),		&urq_install },
  { "config",	N_("c"),	N_("[C]onfig"),	N_("Configure any packages that are unconfigured."),	&urq_config  },
  { "remove",	N_("r"),	N_("[R]emove"),	N_("Remove unwanted software."),			&urq_remove  },
  { "quit",	N_("q"),	N_("[Q]uit"),	N_("Quit dselect."),					&urq_quit    },
  { 0,		0,  		N_("menu"),	0,							&urq_menu    },
  { 0 }
};

static const char programdesc[]=
      N_("Debian `%s' package handling frontend version %s.\n");

static const char copyrightstring[]= N_(
      "Copyright (C) 1994-1996 Ian Jackson.\n"
      "Copyright (C) 2000,2001 Wichert Akkerman.\n");

static const char licensestring[]= N_(
      "This is free software; see the GNU General Public License version 2 or\n"
      "later for copying conditions. There is NO warranty.\n");

static void DPKG_ATTR_NORET
printversion(const struct cmdinfo *ci, const char *value)
{
  printf(gettext(programdesc), DSELECT, DPKG_VERSION_ARCH);
  printf(gettext(copyrightstring));
  printf(gettext(licensestring), DSELECT);

  m_output(stdout, _("<standard output>"));

  exit(0);
}

static void DPKG_ATTR_NORET
usage(const struct cmdinfo *ci, const char *value)
{
  int i;

  printf(_(
"Usage: %s [<option> ...] [<action> ...]\n"
"\n"), DSELECT);

  printf(_(
"Options:\n"
"  --admindir <directory>     Use <directory> instead of %s.\n"
"  --expert                   Turn on expert mode.\n"
"  --debug <file> | -D<file>  Turn on debugging, sending output to <file>.\n"
"  --colour | --color screenpart:[foreground],[background][:attr[+attr+..]]\n"
"                             Configure screen colours.\n"
"\n"), ADMINDIR);

  printf(_(
"  --help                     Show this help message.\n"
"  --version                  Show the version.\n"
"\n"));

  printf(_(
"Actions:\n"
"  access update select install config remove quit\n"
"\n"));

  printf(_("Screenparts:\n"));
  for (i=0; screenparttable[i].name; i++)
    printf("  %s", screenparttable[i].name);
  fputs("\n\n", stdout);

  printf(_("Colours:\n"));
  for (i=0; colourtable[i].name; i++)
    printf("  %s", colourtable[i].name);
  fputs("\n\n", stdout);

  printf(_("Attributes:\n"));
  for (i=0; attrtable[i].name; i++)
    printf("  %s", attrtable[i].name);
  fputs("\n\n", stdout);

  m_output(stdout, _("<standard output>"));

  exit(0);
}

/* These are called by C code, so need to have C calling convention */
extern "C" {

  static void setdebug(const struct cmdinfo*, const char *v) {
    debug= fopen(v,"a");
    if (!debug) ohshite(_("couldn't open debug file `%.255s'\n"),v);
    setvbuf(debug,0,_IONBF,0);
  }

  static void setexpert(const struct cmdinfo*, const char *v) {
    expertmode= 1;
  }

  static int
  findintable(const struct table_t *table, const char *item, const char *tablename)
  {
    int i;

    for (i= 0;  item && (table[i].name!=NULL); i++)
      if (strcasecmp(item, table[i].name) == 0)
        return table[i].num;

    ohshit(_("Invalid %s `%s'\n"), tablename, item);
  }

  /*
   *  The string's format is:
   *    screenpart:[forecolor][,backcolor][:[<attr>, ...]
   *  Examples: --color title:black,cyan:bright+underline
   *            --color list:red,yellow
   *            --color colheads:,green:bright
   *            --color selstate::reverse  // doesn't work FIXME
   */
  static void setcolor(const struct cmdinfo*, const char *string) {
    char *s;
    char *colours, *attributes, *attrib, *colourname;
    int screenpart, aval;

    s = m_strdup(string); // strtok modifies strings, keep string const
    screenpart= findintable(screenparttable, strtok(s, ":"), _("screen part"));
    colours= strtok(NULL, ":");
    attributes= strtok(NULL, ":");

    if ((colours == NULL || ! strlen(colours)) &&
        (attributes == NULL || ! strlen(attributes))) {
       ohshit(_("Null colour specification\n"));
    }

    if (colours != NULL && strlen(colours)) {
      colourname= strtok(colours, ",");
      if (colourname != NULL && strlen(colourname)) {
        // normalize attributes to prevent confusion
        color[screenpart].attr= A_NORMAL;
       color[screenpart].fore=findintable(colourtable, colourname, _("colour"));
      }
      colourname= strtok(NULL, ",");
      if (colourname != NULL && strlen(colourname)) {
        color[screenpart].attr= A_NORMAL;
        color[screenpart].back=findintable(colourtable, colourname, _("colour"));
      }
    }

    if (attributes != NULL && strlen(attributes)) {
      for (attrib= strtok(attributes, "+");
           attrib != NULL && strlen(attrib);
          attrib= strtok(NULL, "+")) {
               aval=findintable(attrtable, attrib, _("colour attribute"));
               if (aval == A_NORMAL) // set to normal
                       color[screenpart].attr= aval;
               else // add to existing attribs
                       color[screenpart].attr= color[screenpart].attr | aval;
      }
    }
  }

} /* End of extern "C" */

static const struct cmdinfo cmdinfos[]= {
  { "admindir",     0,   1,  0,  &admindir,  0               },
  { "debug",       'D',  1,  0,  0,          setdebug        },
  { "expert",      'E',  0,  0,  0,          setexpert       },
  { "help",        'h',  0,  0,  0,          usage           },
  { "version",      0,   0,  0,  0,          printversion    },
  { "color",        0,   1,  0,  0,          setcolor        }, /* US spelling */
  { "colour",       0,   1,  0,  0,          setcolor        }, /* UK spelling */
  { 0,              0,   0,  0,  0,          0               }
};

static int cursesareon= 0;
void curseson() {
  if (!cursesareon) {
    const char *cup, *smso;
    initscr();
    cup= tigetstr("cup");
    smso= tigetstr("smso");
    if (!cup || !smso) {
      endwin();
      if (!cup)
        fputs(_("Terminal does not appear to support cursor addressing.\n"),stderr);
      if (!smso)
        fputs(_("Terminal does not appear to support highlighting.\n"),stderr);
      fprintf(stderr,
	      _("Set your TERM variable correctly, use a better terminal,\n"
	        "or make do with the per-package management tool %s.\n"),
	      DPKG);
      ohshit(_("terminal lacks necessary features, giving up"));
    }
  }
  cursesareon= 1;
}

void cursesoff() {
  if (cursesareon) {
    clear();
    refresh();
    endwin();
  }
  cursesareon=0;
}

extern void *operator new(size_t size) {
  void *p;
  p= m_malloc(size);
  assert(p);
  return p;
}

extern void operator delete(void *p) {
  free(p);
}

urqresult urq_list(void) {
  readwrite= modstatdb_init(admindir,msdbrw_writeifposs);

  curseson();

  packagelist *l= new packagelist(&packagelistbindings);
  l->resolvesuggest();
  l->display();
  delete l;

  modstatdb_shutdown();
  resetpackages();
  return urqr_normal;
}

static void
dme(int i, int so)
{
  char buf[120];
  const menuentry *me= &menuentries[i];
  sprintf(buf," %c %d. %-11.11s %-80.80s ",
          so ? '*' : ' ', i,
          gettext(me->option),
          gettext(me->menuent));
  
  int y,x;
  getmaxyx(stdscr,y,x);

  attrset(so ? A_REVERSE : A_NORMAL);
  mvaddnstr(i+2,0, buf,x-1);
  attrset(A_NORMAL);
}

static int
refreshmenu(void)
{
  char buf[2048];
  static int l,lockfd;
  static char *lockfile;

  curseson(); cbreak(); noecho(); nonl(); keypad(stdscr,TRUE);

  int y,x;
  getmaxyx(stdscr,y,x);

  clear();
  attrset(A_BOLD);
  sprintf(buf, gettext(programdesc), DSELECT, DPKG_VERSION_ARCH);
  mvaddnstr(0,0,buf,x-1);

  attrset(A_NORMAL);
  const struct menuentry *mep; int i;
  for (mep=menuentries, i=0; mep->option && mep->menuent; mep++, i++)
    dme(i,0);

  attrset(A_BOLD);
  addstr(_("\n\n"
         "Move around with ^P and ^N, cursor keys, initial letters, or digits;\n"
         "Press <enter> to confirm selection.   ^L redraws screen.\n\n"));

  attrset(A_NORMAL);
  addstr(gettext(copyrightstring));
  sprintf(buf, gettext(licensestring), DSELECT);
  addstr(buf);

  l= strlen(admindir);
  lockfile= new char[l+sizeof(LOCKFILE)+2];
  strcpy(lockfile,admindir);
  strcpy(lockfile+l, "/" LOCKFILE);
  lockfd= open(lockfile, O_RDWR|O_CREAT|O_TRUNC, 0660);
  if (errno == EACCES || errno == EPERM)
    addstr(_("\n\n"
             "Read-only access: only preview of selections is available!"));

  return i;
}

urqresult urq_menu(void) {
#define C(x) ((x)-'a'+1)
  int entries, c;
  entries= refreshmenu();
  int cursor=0;
  dme(0,1);
  for (;;) {
    refresh();
    do
      c= getch();
    while (c == ERR && errno == EINTR);
    if (c==ERR)  {
      if(errno != 0)
        ohshite(_("failed to getch in main menu"));
      else {
        clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1); 
      }
    }

    if (c==C('n') || c==KEY_DOWN || c==' ' || c=='j') {
      dme(cursor,0); cursor++; cursor %= entries; dme(cursor,1);
    } else if (c==C('p') || c==KEY_UP || c==C('h') ||
               c==KEY_BACKSPACE || c==KEY_DC || c=='k') {
      dme(cursor,0); cursor+= entries-1; cursor %= entries; dme(cursor,1);
    } else if (c=='\n' || c=='\r' || c==KEY_ENTER) {
      clear(); refresh();
      switch (menuentries[cursor].fn()) { /* FIXME: trap errors in urq_... */
      case urqr_quitmenu:
        return urqr_quitmenu;
      case urqr_normal:
        cursor++; cursor %= entries;
      case urqr_fail:
        break;
      default:
        internerr("unknown menufn");
      }
      refreshmenu(); dme(cursor,1);
    } else if (c==C('l')) {
      clearok(stdscr,TRUE); clear(); refreshmenu(); dme(cursor,1);
    } else if (isdigit(c)) {
      char buf[2]; buf[0]=c; buf[1]=0; c=atoi(buf);
      if (c < entries) {
        dme(cursor,0); cursor=c; dme(cursor,1);
      } else {
        beep();
      }
    } else if (isalpha(c)) {
      c= tolower(c);
      int i = 0;
      while (i < entries && gettext(menuentries[i].key)[0] != c)
        i++;
      if (i < entries) {
        dme(cursor,0); cursor=i; dme(cursor,1);
      } else {
        beep();
      }
    } else {
      beep();
    }
  }
}

urqresult urq_quit(void) {
  /* FIXME: check packages OK. */
  return urqr_quitmenu;
}

int main(int, const char *const *argv) {
  jmp_buf ejbuf;

  setlocale(LC_ALL, "");
  bindtextdomain(DSELECT, LOCALEDIR);
  textdomain(DSELECT);

  if (setjmp(ejbuf)) { /* expect warning about possible clobbering of argv */
    cursesoff();
    error_unwind(ehflag_bombout); exit(2);
  }
  push_error_handler(&ejbuf,print_error_fatal,0);

  loadcfgfile(DSELECT, cmdinfos);
  myopt(&argv,cmdinfos);

  if (*argv) {
    const char *a;
    while ((a= *argv++) != 0) {
      const menuentry *me = menuentries;
      while (me->command && strcmp(me->command, a))
        me++;
      if (!me->command) badusage(_("unknown action string `%.50s'"),a);
      me->fn();
    }
  } else {
    urq_menu();
  }

  cursesoff();
  standard_shutdown();
  return(0);
}

