/* main.c
 *
 * Omnitty SSH Multiplexer
 * Copyright (c) 2004 Bruno Takahashi C. de Oliveira
 * All rights reserved.
 *
 * LICENSE INFORMATION:
 * This program 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 program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 * Copyright (c) 2002 Bruno T. C. de Oliveira
 */

#include <ncurses.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>

#include "curutil.h"
#include "machine.h"
#include "machmgr.h"
#include "minibuf.h"
#include "menu.h"

/* minimum terminal dimensions to run program */
#define MIN_REQUIRED_WIDTH 80
#define MIN_REQUIRED_HEIGHT 25

#define REMINDER_LINE "OmNiTTY-R v" OMNITTY_VERSION \
                      "  \007F1\007:menu  \006F2/3\007:sel  \003F4\007:tag" \
                      "  \002F5\007:add  \001F6\007:del" \
                      "  \005F7\007:mcast"

#define SPLASH_LINE_1 "OmNiTTY Reborn v" OMNITTY_VERSION
#define SPLASH_LINE_2 "Copyright (c) 2004 Bruno T. C. de Oliveira"

/* how many characters wide the list window will be, by default */
#define LISTWIN_DEFAULT_CHARS 8
#define TERMWIN_DEFAULT_CHARS 80
#define TERMWIN_MIN 80

#define RTFM "Syntax: omnitty [-W list_width] [-T term_width]\n" \
             "\n" \
             "     -W        specifies width of machine list area\n" \
             "               (default is 8 characters)\n" \
             "\n" \
             "     -T        specifies width of terminal area\n" \
             "               (default is 80 characters)\n" \
             "\n"

static WINDOW *listwin   = NULL;
static WINDOW *sumwin    = NULL;
static WINDOW *vtwin     = NULL;
static WINDOW *minibuf = NULL;
static volatile int zombie_count = 0;

void sigchld_handler(int signo) { zombie_count++; }

void curses_init() {
   int w, h, i = 0;

   initscr();
   start_color();
   noecho();
   keypad(stdscr, TRUE);
   timeout(200);
   raw();
   curutil_colorpair_init();
   clear();

   /* register some alternate escape sequences for the function keys,
    * to improve compatibility with several types of terminal emulators */
   define_key("\eOP",   KEY_F(1)); define_key("\eOQ",   KEY_F(2));
   define_key("\eOR",   KEY_F(3)); define_key("\eOS",   KEY_F(4));
   define_key("\e[11~", KEY_F(1)); define_key("\e[12~", KEY_F(2));
   define_key("\e[13~", KEY_F(3)); define_key("\e[14~", KEY_F(4));
   define_key("\e[15~", KEY_F(5)); define_key("\e[17~", KEY_F(6));
   define_key("\e[18~", KEY_F(7)); define_key("\e[19~", KEY_F(8));
   define_key("\e[20~", KEY_F(9)); define_key("\e[21~", KEY_F(10));

   getmaxyx(stdscr, h, w);
   if (h < MIN_REQUIRED_HEIGHT || w < MIN_REQUIRED_WIDTH) {
      endwin();
      fprintf(stderr, "ERROR: omnitty requires a %d x %d terminal to run.\n",
                        MIN_REQUIRED_WIDTH, MIN_REQUIRED_HEIGHT);
      exit(1);
   }
   
   wmove(stdscr, h/2, (w - strlen(SPLASH_LINE_1))/2);
   curutil_attrset(stdscr, 0x40);
   waddstr(stdscr, SPLASH_LINE_1);

   curutil_attrset(stdscr, 0x70);
   wmove(stdscr, h/2 + 1, (w - strlen(SPLASH_LINE_2))/2);
   waddstr(stdscr, SPLASH_LINE_2);

   wrefresh(stdscr);
   while (getch() < 0 && i < 10) i++;

   wclear(stdscr);
   wrefresh(stdscr);
}

/* Window layout:
 * 
 *      list    summary     terminal window
 *     window   window
 *    |-------|--------X|--------------------------------| 
 *    0       A        BC                             termcols-1
 *
 * A = list_win_chars + 2
 */
void wins_init(int *vtrows, int *vtcols, int list_win_chars, 
                                                int term_win_chars) {
   int termcols, termrows, A, B, C;
   const char *p;

   /* obtain terminal dimensions */
   getmaxyx(stdscr, termrows, termcols);

   /* the geometry is hard-coded here, but nowhere else... so I don't
    * see a lot of point using #defines or anything any more sophisticated */
   A = list_win_chars + 2;
   C = termcols - term_win_chars;
   B = C-1;
   if (B < A) B = A, C = B + 1;
   
   *vtcols = termcols - C;
   *vtrows = termrows - 3;

   /* actually create the windows */
   listwin = newwin(termrows-3, A - 0, 1, 0);
   sumwin  = (B - A >= 3) ? newwin(termrows-3, B - A, 1, A) : NULL;
   vtwin   = newwin(termrows-3, *vtcols, 1, C);
   minibuf = newwin(1, termcols, termrows-1, 0);

   /* draw the top decoration line */
   wattrset(stdscr, COLOR_PAIR(3) | A_BOLD);
   wmove(stdscr, 0, 0);
   whline(stdscr, ACS_HLINE | A_NORMAL, termcols);
   
   /* draw instruction line */
   wattrset(stdscr, COLOR_PAIR(4*8) | A_BOLD);
   wmove(stdscr, termrows-2, 0);
   whline(stdscr, ' ', termcols);
   wmove(stdscr, termrows-2, 0);
   p = REMINDER_LINE;
   while (*p) {
      if (*p >= 0 && *p <= 7)
         wattrset(stdscr, COLOR_PAIR(4*8 + 7 - *p) | A_BOLD);
      else 
         waddch(stdscr, *p);
      p++;
   }
   
   /* draw the separator at column B */
   wattrset(stdscr, COLOR_PAIR(3) | A_BOLD);
   wmove(stdscr, 0, B);
   wvline(stdscr, ACS_VLINE | A_NORMAL, termrows - 2);
   wmove(stdscr, 0, B);          waddch(stdscr, ACS_TTEE);
   wrefresh(stdscr);

   /* draw window titles */
   if (termcols > 90) { 
      wmove(stdscr, 0, 2);
      waddstr(stdscr, "[Machines]");
      wmove(stdscr, 0, B+2);
      waddstr(stdscr, "[Terminal]");
   }

   /* make the cursor position be irrelevant for all windows except
    * the terminal window */
   leaveok(listwin, TRUE);
   if (sumwin) leaveok(sumwin, TRUE);

   /* draw all windows */
   touchwin(listwin);   wclear(listwin);
   touchwin(vtwin);     wclear(vtwin);
   if (sumwin) { touchwin(sumwin); wclear(sumwin); }
   touchwin(minibuf); wclear(minibuf);
}

void update_cast_label() {
   /* draws the label that says 'single cast' or 'multicast' on minibuffer */
   int termwidth, termheight;
   const char *msg;
   getmaxyx(minibuf, termheight, termwidth);
   
   if (machmgr_is_multicast()) {
      curutil_attrset(minibuf, 0xF9); /* bright blinking white over red */
      msg = "!!! MULTICAST MODE !!!";
   }
   else {
      curutil_attrset(minibuf, 0x40);
      msg = "singlecast mode";
   }
   
   werase(minibuf);
   wmove(minibuf, 0, termwidth - strlen(msg));
   waddstr(minibuf, msg);

   leaveok(minibuf, TRUE);  /* prevent cursor movement */
   wrefresh(minibuf);
   leaveok(minibuf, FALSE);
}

void redraw(bool force_full_redraw) {
   if (force_full_redraw) {
      touchwin(stdscr);
      wrefresh(stdscr);
   }

   /* draw machine list */
   machmgr_draw_list(); 
   if (force_full_redraw) touchwin(listwin);
   wrefresh(listwin);
   
   /* draw summary window, if there is one */
   if (sumwin) {
      machmgr_draw_summary(sumwin);
      if (force_full_redraw) touchwin(sumwin);
      wrefresh(sumwin);
   }

   /* draw vt window */
   machmgr_draw_vt(vtwin); 
   if (force_full_redraw) touchwin(vtwin);
   wrefresh(vtwin);

   /* draw the 'multicast/singlecast' label */
   update_cast_label();
}

static void add_machines_from_file(const char *file) {
   static char buf[128];
   bool pipe = false;
   FILE *f;

   if (getenv("OMNITTY_AT_COMMAND")) {
      /* popen() a command */
      pipe = true;
      strcpy(buf, getenv("OMNITTY_AT_COMMAND"));
      strcat(buf, " ");
      strcat(buf, file);
      strcat(buf, " 2>/dev/null");
      f = popen(buf, "r");
   }
   else f = fopen(file, "r");
   
   if (!f) {
      minibuf_msg(minibuf, pipe ? 
         "Can't execute command specified by OMNITTY_AT_COMMAND" : 
         "Can't read that file.", 0xF1);
      return;
   }

   minibuf_put(minibuf, pipe ? "Adding machines supplied by command..." :
                               "Adding machines from file...", 0x70);

   while (1 == fscanf(f, "%s", buf)) machmgr_add(buf);

   if (pipe) {
      if (0 != pclose(f)) 
         minibuf_msg(minibuf, "Command given by OMNITTY_AT_COMMAND exited "
                              "with error.", 0xF1);
      /* at this point SIGCHLD will have caused zombie_count to be one more
       * than it should, since the child command has already been reaped
       * by pclose(). If we don't correct zombie_count, wait() will block
       * in the main loop, since it will try to reap a zombie that does not yet 
       * exist. */
      zombie_count--;
   }
   else
      fclose(f);

   minibuf_put(minibuf, NULL, 0x70);
}

static void add_machine() {
   static char buf[32];

   *buf = 0;
   if (minibuf_prompt(minibuf, "Add: ", 0xE0, buf, 32)) {
      if (*buf == '@') add_machines_from_file(buf+1);
      else machmgr_add(buf);
   }
}

static void delete_machine() {
   static char buf[2];
   *buf = 0;
   if (minibuf_prompt(minibuf, "Really delete it [y/n]?", 0x90, buf, 2)
       && (*buf == 'y' || *buf == 'Y')) machmgr_delete_current();
}

int main(int argc, char **argv) {
   int vtcols, vtrows, ch = 0;
   int list_win_chars = LISTWIN_DEFAULT_CHARS;
   int term_win_chars = TERMWIN_DEFAULT_CHARS;
   bool quit = false;
   pid_t chldpid;

   /* process command-line options */
   while ( 0 < (ch = getopt(argc, argv, "W:T:")) ) {
      switch (ch) {
         case 'W': list_win_chars = atoi(optarg); break;
	 case 'T': term_win_chars = atoi(optarg);
		   if( term_win_chars < TERMWIN_MIN ) {
		       fprintf(stderr, " terminal area too narrow: %d\n", 
                                                        term_win_chars);
		       fputs(RTFM, stderr);
		       exit(2);
		   }
		   break;
         default: fputs(RTFM, stderr); exit(2);
      }
   }
   signal(SIGCHLD, sigchld_handler);
   curses_init();
   wins_init(&vtrows, &vtcols, list_win_chars, term_win_chars);
   menu_init(minibuf);

   machmgr_init(listwin, vtrows, vtcols);

   while (!quit) {
      if (zombie_count) {
         zombie_count--;
         chldpid = wait(NULL);
         machmgr_handle_death(chldpid);
      }

      machmgr_update();
      redraw(false);

      ch = getch();
      if (ch < 0) continue;

      switch (ch) {
         case KEY_F(1): menu_show(); redraw(true); break;
         case KEY_F(2): machmgr_prev_machine(); break;
         case KEY_F(3): machmgr_next_machine(); break;
         case KEY_F(4): machmgr_toggle_tag_current(); break;
         case KEY_F(5): add_machine(); break;
         case KEY_F(6): delete_machine(); break;
         case KEY_F(7): machmgr_toggle_multicast(); break;
         default: machmgr_forward_keypress(ch); break;
      }
   }

   endwin();
   return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1