/*
 * Atom-4 ncurses interface
 *
 * $Id: textui.cc,v 1.33 2003/04/09 18:47:36 hsteoh Exp hsteoh $
 */

#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>			// for STDIN_FILENO
#include "exception.h"
#include "game.h"
#include "textui.h"


#define NORMAL_COLOR		9

void ncurses_ui::keyhandler::read_ready(eventloop *src, int fd) {
  ui->handlekey();			// process event
  ui->refresh();			// update screen with any changes
}

void ncurses_ui::keyhandler::write_ready(eventloop *src, int fd) {}

void ncurses_ui::changehandler::notify_move(atom4 *src, int player,
                                            elist<boardchange> &changes) {
  // First change is always player's actual move
  boardchange move = *changes.headp();
  ui->message("Player %d moves (%d,%d)\n", player, move.x, move.y);
  notify_clear(src);
}

void ncurses_ui::changehandler::notify_clear(atom4 *src) {
  int winner = ui->game->winner();

  if (winner!=-1) {
    if (winner!=STALEMATE) {
      ui->message("Player %d WINS!!\n", winner);
    } else {
      ui->message("STALEMATE: no more moves left\n");
    }
  }
  ui->refresh();			// update with changes
}

void ncurses_ui::handlekey() {
  int pl = game->current_player();
  int ch = wgetch(bwin);

  if (ch==-1)
    throw exception("Ncurses error: aborting");

  // Global keybindings
  switch (ch) {
  case 0x0C:
    redrawwin(stdscr);			// force repaint of entire screen
    return;
  }

  // Process key differently depending on game state
  if (!game->round_over()) {
    switch (ch) {
    case KEY_UP:	if (last_y>0) last_y--;				break;
    case KEY_DOWN:	if (last_y<game->board_height()-1) last_y++;	break;
    case KEY_LEFT:	if (last_x>0) last_x--;				break;
    case KEY_RIGHT:	if (last_x<game->board_width()-1) last_x++;	break;
    case KEY_ENTER:
    case '\n':	case '\r':	case ' ':
      if (!game->is_local_turn()) {
        message("It's not your turn to move\n");
        break;
      }

      // Attempt the move
      if (!game->move(pl, last_x, last_y)) {
        message("You can't put a marble here");
      } else {
        if (!game->round_over()==-1) {
          message("Player %d moves (%d,%d)", pl, last_x, last_y);
        }
      }
      break;
    case 'q':
      if (ask_yn("Quit current game?"))
        *exitflag=1;			// quit (TBD: should resign player)
      break;
    default:
      message("Unknown key (%d)", ch);
    }
  } else {				// not in playing mode
    switch (ch) {
    case 'n':
      game->newround();			// start next round
      last_x=game->board_width()/2;	// reset cursor pos
      last_y=game->board_height()/2;
      message("");			// clean up stale messages
      break;
    case 'q':
      *exitflag=1;			// quit
      break;
    default:
      message("Press 'n' to proceed to the next round, 'q' to quit.");
    }
  }
}

int ncurses_ui::formatstr(char *fmt, ...) {
  va_list args;
  int rc;

  va_start(args,fmt);
  rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args);
  va_end(args);

  return rc;
}

int ncurses_ui::message(char *fmt, ...) {
  va_list args;
  int rc;

  va_start(args,fmt);
  rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args);
  mvwaddstr(msgwin, 0, 0, strbuf);
  wclrtoeol(msgwin);
  wrefresh(msgwin);
  va_end(args);

  return rc;
}

void ncurses_ui::draw_tile(WINDOW *w, celltype cell) {
  char *glyph;
  int color, bold=0;

  if (cell>='a' && cell<='h') {
    glyph="()";
    color = cellcolors[cell-'a'];
    bold = cellbold[cell-'a'];
  } else {
    // NOTE: this is an ugly hack to force ncurses to update every char
    // position with the white background; otherwise on some terminals they
    // use the default blank which may not have the right background.
    // Basically, we alternate between cell colors 0 and 1 (red); it doesn't
    // really matter because we're drawing blanks anyway. We take care to
    // use 0 for the first character so that the cursor doesn't show up with
    // funny colors on xterm.
    wcolor_set(w, cellcolors[0], NULL);
    waddch(w, ' ');
    color = cellcolors[1];
    glyph=" ";
  }

  wcolor_set(w, color, NULL);
  if (bold) wattron(w, A_BOLD);		// so that white cells will show
  waddstr(w, glyph);
  if (bold) wattroff(w, A_BOLD);	// normal for everything else
  wcolor_set(w, NORMAL_COLOR, NULL);	// just need the white background
}

void render_cell(int x, int y, celltype cell, void *context) {
  ncurses_ui *iface = (ncurses_ui*)context;

  wmove(iface->bwin, y, x*2 + (y%2) + 1);
  iface->draw_tile(iface->bwin, cell);
}

void ncurses_ui::render_board() {
  int i, wd;

  // Draw board
  game->get_board()->mapcell(render_cell, (void*)this);

  // Draw board borders
  wcolor_set(bwin, cellcolors[0], NULL); // just need the white background
  wd = game->board_width();
  for (i=0; i<game->board_height(); i++) {
    mvwaddch(bwin, i, 0, '|');

    if (i%2) {
      waddch(bwin, ' ');		// so we don't get color gaps in bckgnd
      mvwaddch(bwin, i, wd*2+2, '|');
    } else {
      mvwaddstr(bwin, i, wd*2+1, " |");
    }
  }
  wcolor_set(bwin, NORMAL_COLOR, NULL);	// just need the white background
}

void ncurses_ui::render_scorepanel(int player) {
  int plidx = player-1;
  int start_y;				// ycoor of this player's scoreboard

  start_y = IOWIN_PLPANEL_Y + plidx*IOWIN_PLPANEL_HEIGHT;

  if (use_color()) wcolor_set(iowin, colors[plidx], NULL);
  formatstr("** Player %d **", player);
  mvwaddstr(iowin, start_y+IOWIN_PANEL_TITLE_Y, 0, strbuf);
  if (use_color()) wcolor_set(iowin, NORMAL_COLOR, NULL);

  formatstr("Score: %d", game->score(player));
  mvwaddstr(iowin, start_y+IOWIN_PANEL_SCORE_Y, 0, strbuf);
}

void ncurses_ui::render_iowin() {
  int i;

  werase(iowin);
  wattron(iowin, A_REVERSE);
  mvwaddstr(iowin, IOWIN_TITLE_Y, 0, " Welcome to Atom-4 " VERSION_STRING " ");
  wclrtoeol(iowin);
  wattroff(iowin, A_REVERSE);

  formatstr("=== Round %d ===", game->current_round());
  mvwaddstr(iowin, IOWIN_ROUND_Y, 0, strbuf);

  for (i=1; i<=NUM_PLAYERS; i++) {
    render_scorepanel(i);
  }

  // Output a prompt for current player
  // Note: this should come last, so that the cursor is left at the right
  // place when wait_key() is called.
  if (!game->round_over()) {
    mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y +
              (game->current_player()-1)*IOWIN_PLPANEL_HEIGHT, 0,
              "-- Your turn: ");
    draw_tile(iowin, game->current_tile());
    waddstr(iowin, " --");
  } else {
    if (game->winner() != STALEMATE) {
      mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y +
                (game->winner()-1)*IOWIN_PLPANEL_HEIGHT, 0,
                "!!! WINNER !!!");
    }
  }
}

void ncurses_ui::refresh() {
  render_board();
  render_iowin();
  update_panels();
  doupdate();

  if (game->is_local_turn()) {		// update cursor pos
    wmove(bwin, last_y, last_x*2+(last_y%2)+1);
    wrefresh(bwin);
  }
}

int ncurses_ui::wait_key() {
  refresh();				// now is a good time to refresh
  return wgetch(iowin);
}

int ncurses_ui::ask_yn(char *question) {
  int ch;

  message("%s (y/n) ", question);
  for (;;) {
    ch=wgetch(msgwin);
    if (ch=='n' || ch=='N') {
      waddstr(msgwin, "no");
      wrefresh(msgwin);
      return 0;
    }
    if (ch=='y' || ch=='Y') {
      waddstr(msgwin, "yes");
      wrefresh(msgwin);
      return 1;
    }
  }

  // Never reached
  return 0;
}

int ncurses_ui::init_color() {
  if (start_color()==ERR) return 0;
  if (has_colors()==FALSE) return 0;
  if (use_default_colors()==ERR) return 0;

  // Insufficient color pairs
  if (COLOR_PAIRS <= 9) return 0;

  if (init_pair(NORMAL_COLOR, -1, -1)==ERR) return 0;

  if (init_pair(1, COLOR_BLACK,   COLOR_WHITE)==ERR) return 0;
  if (init_pair(2, COLOR_RED,     COLOR_WHITE)==ERR) return 0;
  if (init_pair(3, COLOR_GREEN,   COLOR_WHITE)==ERR) return 0;
  if (init_pair(4, COLOR_BLUE,    COLOR_WHITE)==ERR) return 0;
  if (init_pair(5, COLOR_YELLOW,  COLOR_WHITE)==ERR) return 0;
  if (init_pair(6, COLOR_CYAN,    COLOR_WHITE)==ERR) return 0;
  if (init_pair(7, COLOR_MAGENTA, COLOR_WHITE)==ERR) return 0;
  if (init_pair(8, COLOR_WHITE,   COLOR_WHITE)==ERR) return 0;

  // Bold the secondary colors so that they are easily distinguished from
  // the primary colors (blue and cyan are esp. easy to confuse)
  cellcolors[0] = 1; cellbold[0] = 0;
  cellcolors[1] = 2; cellbold[1] = 0;
  cellcolors[2] = 3; cellbold[2] = 0;
  cellcolors[3] = 5; cellbold[3] = 1;
  cellcolors[4] = 4; cellbold[4] = 0;
  cellcolors[5] = 7; cellbold[5] = 1;
  cellcolors[6] = 6; cellbold[6] = 1;
  cellcolors[7] = 8; cellbold[7] = 1;

  // Player colors: maybe we should just revert to normal color?
  colors[0] = NORMAL_COLOR;
  colors[1] = NORMAL_COLOR;

  return 1;
}

ncurses_ui::ncurses_ui(atom4 *gm, eventloop *eloop, int *indicator, int opts) :
	interface(gm), options(opts), loop(eloop), exitflag(indicator),
	khandler(this), notifier(this) {
  int bwd, bht;
  int i;

  /* Init ncurses */
  initscr();

  /* Color support -- now it's mandatory since we can't easily represent
   * 8 colors in monochrome! */
  if (!init_color()) {
      throw exception("Cannot initialize color subsystem\n");
//      options &= ~ENABLE_COLOR;		// color initialization failed
  }

  /* General options */
  cbreak();
  noecho();
  nonl();

  /* Setup ncurses window for board */
  bwd = game->board_width()*2+3;
  bht = game->board_height();
  bwin = newwin(bht, bwd, BOARDWIN_Y, BOARDWIN_X);
  bpanel = new_panel(bwin);
  scrollok(bwin, FALSE);
  keypad(bwin, TRUE);

  /* Put selection cursor at roughly the center of the board */
  last_x = game->board_width()/2;
  last_y = game->board_height()/2;

  /* Setup ncurses window for displaying player stats */
  iowin = newwin(LINES-IOWIN_Y, COLS-bwd-1, IOWIN_Y, BOARDWIN_X+bwd+1);
  iopanel = new_panel(iowin);
  scrollok(bwin, FALSE);
  keypad(iowin, TRUE);			// else ESC's get lost when we switch
					// between panes

  /* Setup general message panel */
  msgwin = newwin(1, COLS, BOARDWIN_Y + bht + 1, 0);
  msgpanel = new_panel(msgwin);
  scrollok(msgwin, FALSE);
  keypad(msgwin, TRUE);			// else ESC's get lost when we switch
					// between panes

  /* Initial screen refresh */
  refresh();

  /* Setup async callbacks */
  loop->register_handler(eventloop::READER, STDIN_FILENO, &khandler);
  game->add_notifier(&notifier);
}

ncurses_ui::~ncurses_ui() {
  /* Get rid of message panel */
  del_panel(msgpanel);
  delwin(msgwin);

  /* Get rid of scoreboard panel */
  del_panel(iopanel);
  delwin(iowin);

  /* Get rid of board panel */
  del_panel(bpanel);
  delwin(bwin);

  /* Exit ncurses mode */
  endwin();
}

