/*

  screen.c

  Author: Pekka Riikonen <priikone@poseidon.pspt.fi>

  Copyright (C) 1997 - 2000 Pekka Riikonen

  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.

*/
/*
 * SILC client screen routines. These implement the user interface
 * on ncurses routines. Most of these routines were taken from the
 * old version of the SILC client dating back to 1997.
 */
/* XXX: Input line handling is really buggy! */
/* $Id$ */

#include "clientincludes.h"

SilcScreen silc_screen_init()
{
  SilcScreen new;

  new = silc_calloc(1, sizeof(*new));
  new->insert = TRUE;

  initscr();
  cbreak();
  nonl();
  noecho();

  silc_screen_create_output_window(new);
  silc_screen_create_input_window(new);

  return new;
}

/* Creates one (main) output window. Returns new created physical 
   window. */

WINDOW *silc_screen_create_output_window(SilcScreen screen)
{
  assert(screen != NULL);

  screen->output_win = silc_calloc(1, sizeof(*screen->output_win));
  screen->output_win_count = 1;
  screen->output_win[0] = newwin(LINES - 3, COLS, 1, 0);
  scrollok(screen->output_win[0], TRUE);
  idlok(screen->output_win[0], TRUE);
  wrefresh(screen->output_win[0]);

  return screen->output_win[0];
}

/* Adds new output window. Return new created physical window. */

WINDOW *silc_screen_add_output_window(SilcScreen screen)
{
  int i;

  assert(screen != NULL);

  screen->output_win = silc_realloc(screen->output_win, 
				    (screen->output_win_count + 1) *
				    sizeof(*screen->output_win));
  i = screen->output_win_count;
  screen->output_win[i] = newwin(LINES - 3, COLS, 1, 0);
  scrollok(screen->output_win[i], TRUE);
  idlok(screen->output_win[i], TRUE);
  wrefresh(screen->output_win[i]);
  screen->output_win_count++;

  return screen->output_win[i];
}

void silc_screen_create_input_window(SilcScreen screen)
{
  assert(screen != NULL);

  screen->input_win = newwin(0, COLS, LINES - 1, 0);
  scrollok(screen->input_win, TRUE);
  keypad(screen->input_win, TRUE);
  wrefresh(screen->input_win);
}

void silc_screen_init_upper_status_line(SilcScreen screen)
{
  assert(screen != NULL);

  /* Create upper status line */
  screen->upper_stat_line = newwin(0, COLS, 0, 0);
  scrollok(screen->upper_stat_line, FALSE);
  wattrset(screen->upper_stat_line, A_REVERSE);

  silc_screen_print_upper_stat_line(screen);
}

void silc_screen_print_upper_stat_line(SilcScreen screen)
{
  int i;
  int justify;
  
  /* Print empty line */
  for (i = 0; i < COLS - 1; i++)
    mvwprintw(screen->upper_stat_line, 0, i, " ");
  
  /* Print stuff with justify */
  justify = COLS / 5;
  mvwprintw(screen->upper_stat_line, 0, 1, "%s %s", 
	    screen->u_stat_line.program_name, 
	    screen->u_stat_line.program_version);

  /* Prints clock on upper stat line */	
  silc_screen_print_clock(screen);
  wrefresh(screen->upper_stat_line);
}

void silc_screen_init_output_status_line(SilcScreen screen)
{
  int i;

  assert(screen != NULL);

  screen->output_stat_line = silc_calloc(1, sizeof(*screen->output_stat_line));
  
  screen->output_stat_line[0] = newwin(1, COLS, LINES - 2, 0);
  scrollok(screen->output_stat_line[0], FALSE);
  wattrset(screen->output_stat_line[0], A_REVERSE);
  
  /* print first just blank line */
  for (i = 0; i < COLS - 1; i++)
    mvwprintw(screen->output_stat_line[0], 0, i, " ");

  /* Allocate bottom line */
  screen->bottom_line = silc_calloc(1, sizeof(*screen->bottom_line));

  wattrset(screen->output_stat_line[0], A_NORMAL);
  wrefresh(screen->output_stat_line[0]);
}

void silc_screen_print_clock(SilcScreen screen)
{
  time_t curtime;
  struct tm *tp;

  curtime = time(0);
  tp = localtime(&curtime);

  mvwprintw(screen->upper_stat_line, 0, COLS - 8, "[%02d:%02d] ", 
	    tp->tm_hour, tp->tm_min);
  wrefresh(screen->upper_stat_line);
}

/* Prints current cursor coordinates on some output stat line */

void silc_screen_print_coordinates(SilcScreen screen, int win_index)
{
  wattrset(screen->output_stat_line[win_index], A_REVERSE);
  mvwprintw(screen->output_stat_line[win_index], 0, COLS - 10,
	    "[%4d,%3d]", screen->input_pos, LINES);
  wrefresh(screen->output_stat_line[win_index]);
  wattrset(screen->output_stat_line[win_index], A_NORMAL);
}

/* Prints bottom line (the status line) of the screen. */

void silc_screen_print_bottom_line(SilcScreen screen, int win_index)
{
  char buf[512];
  SilcScreenBottomLine line = screen->bottom_line;
  int i, len;

  memset(buf, 0, sizeof(buf));

  if (line->mode) {
    len = strlen(line->mode);
    strncat(buf, line->mode, len);
  }

  if (line->nickname) {
    len = strlen(line->nickname);
    strncat(buf, line->nickname, len > SILC_SCREEN_MAX_NICK_LEN ? 
	    SILC_SCREEN_MAX_NICK_LEN : len);
  }

  if (line->connection) {
    len = strlen(line->connection);
    strncat(buf, " via ", 5);
    strncat(buf, line->connection, len > SILC_SCREEN_MAX_CONN_LEN ? 
	    SILC_SCREEN_MAX_CONN_LEN : len);
  }

  if (line->channel) {
    len = strlen(line->channel);
    strncat(buf, " ", 1);
    strncat(buf, line->channel, len > SILC_SCREEN_MAX_CHANNEL_LEN ?
	    SILC_SCREEN_MAX_CHANNEL_LEN : len);
  }

  if (line->channel_mode) {
    len = strlen(line->channel_mode);
    strncat(buf, " (+", 3);
    strncat(buf, line->channel_mode, len > SILC_SCREEN_MAX_CHANNEL_LEN ?
	    SILC_SCREEN_MAX_CHANNEL_LEN : len);
    strncat(buf, ")", 2);
  }

  if (line->umode) {
    len = strlen(line->umode);
    strncat(buf, " [", 2);
    strncat(buf, line->umode, len > SILC_SCREEN_MAX_UMODE_LEN ?
	    SILC_SCREEN_MAX_UMODE_LEN : len);
    strncat(buf, "]", 2);
  }

  if (line->away)
    strncat(buf, " (away)", 8);

  wattrset(screen->output_stat_line[win_index], A_REVERSE);

  for (i = 0; i < COLS - 10; i++)
    mvwprintw(screen->output_stat_line[win_index], 0, i, " ");

  mvwprintw(screen->output_stat_line[win_index], 0, 0, " %s", buf);
  silc_screen_print_coordinates(screen, win_index);
  wrefresh(screen->output_stat_line[win_index]);
  wattrset(screen->output_stat_line[win_index], A_NORMAL);
}

/* Refresh all windows */

void silc_screen_refresh_all(SilcScreen screen)
{
  int i;

  assert(screen != NULL);

  wclear(screen->upper_stat_line);
  silc_screen_print_upper_stat_line(screen);

  wclear(screen->output_stat_line[0]);
  silc_screen_print_bottom_line(screen, 0);
  silc_screen_print_coordinates(screen, 0);

  for (i = 0; i < screen->output_win_count; i++) {
    wclear(screen->output_win[i]);
    wrefresh(screen->output_win[i]);
  }

  wclear(screen->input_win);
  wrefresh(screen->input_win);
}

/* Refreshes a window */

void silc_screen_refresh_win(WINDOW *win)
{
  assert(win != NULL);

  redrawwin(win);
  wrefresh(win);
}

/* Resets input window */

void silc_screen_input_reset(SilcScreen screen)
{
  int i;

  assert(screen != NULL);
  for (i = 0; i < COLS - 1; i++)
    mvwprintw(screen->input_win, 0, i, " ");
  mvwprintw(screen->input_win, 0, 0, "");
  wrefresh(screen->input_win);
  screen->input_pos = 0;
  screen->input_end = 0;
  screen->cursor_pos = 0;
  screen->virtual_window = 0;
}

/* Backspace. Removes one character from input windows. */

void silc_screen_input_backspace(SilcScreen screen)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  /* Return directly if at the start of input line */
  if (screen->input_pos == 0)
    return;

  if (screen->virtual_window) {
    if (screen->cursor_pos <= 10) {
      int i;

      /* Clear line */
      for (i = 0; i < COLS; i++)
        mvwprintw(win, 0, i, " ");
      mvwprintw(win, 0, 0, "");

      screen->virtual_window--;
      
      waddnstr(win, &buffer[screen->virtual_window * (COLS - 5)], COLS);
      screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5));
      screen->input_end = ((screen->virtual_window + 1) * (COLS - 5));
      screen->cursor_pos = (COLS - 5);
      wrefresh(win);
    }
  }

  screen->cursor_pos--;
  screen->input_pos--;
  screen->input_end--;
  mvwdelch(win, 0, screen->cursor_pos);

  if (screen->input_pos < screen->input_end)
    /* Delete from inside the input line */
    SILC_SCREEN_INPUT_DELETE(buffer, screen->input_pos, screen->input_end);
  else
    /* Delete from the end of the input line */
    buffer[screen->input_pos] = 0;

  wrefresh(win);
}

/* Switches insert on input window on/off */

void silc_screen_input_insert(SilcScreen screen)
{
  assert(screen != NULL);

  screen->insert = screen->insert == TRUE ? FALSE : TRUE;
}

/* Moves cursor one character length to rightward */

void silc_screen_input_cursor_right(SilcScreen screen)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  /* Return directly if we are at the end of input line */
  if (screen->cursor_pos >= SILC_SCREEN_INPUT_WIN_SIZE)
    return;

  /* Make sure cursor doesn't advance over the end of the line */
  if (screen->input_pos >= screen->input_end)
    return;

  /* When cursor advances enough we switch to new window and show
     rest of the typed characters on the screen. */
  if (screen->cursor_pos >= (COLS - 5)) {
    int i;

    /* Clear line */
    for (i = 0; i < COLS; i++)
      mvwprintw(win, 0, i, " ");
    mvwprintw(win, 0, 0, "");

    waddnstr(win, &buffer[screen->input_pos - 10], 
	     ((screen->input_pos - 10) - screen->input_end >= COLS) ?
	     COLS : (screen->input_pos - 10) - screen->input_end);
    screen->cursor_pos = 10;
    wrefresh(win);

    screen->virtual_window++;
  }

  screen->cursor_pos++;
  screen->input_pos++;
  wmove(win, 0, screen->cursor_pos);
  wrefresh(win);
}

/* Moves cursor one character length to leftward */

void silc_screen_input_cursor_left(SilcScreen screen)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  /* Return directly if at the start of input line */
  if (screen->input_pos == 0)
    return;

  /* When cursor advances enough we switch to new window and show
     rest of the typed characters on the screen. */
  if (screen->virtual_window) {
    if (screen->cursor_pos <= 10) {
      int i;

      /* Clear line */
      for (i = 0; i < COLS; i++)
        mvwprintw(win, 0, i, " ");
      mvwprintw(win, 0, 0, "");

      screen->virtual_window--;
      
      waddnstr(win, &buffer[screen->virtual_window * (COLS - 5)], COLS);
      screen->input_pos = ((screen->virtual_window + 1) * (COLS - 5));
      screen->cursor_pos = (COLS - 5);
      wrefresh(win);
    }
  }

  screen->cursor_pos--;
  screen->input_pos--;
  wmove(win, 0, screen->cursor_pos);
  wrefresh(win);
}

/* Moves cursor at the very start of the input line */

void silc_screen_input_cursor_home(SilcScreen screen)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  wclear(win);
  waddnstr(win, &buffer[0], COLS);
  wrefresh(win);

  screen->input_pos = 0;
  screen->cursor_pos = 0;
  screen->virtual_window = 0;
}

/* Moves cursor at the very end of the input line */

void silc_screen_input_cursor_end(SilcScreen screen)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  wclear(win);
  waddnstr(win, &buffer[screen->input_end - 10], 10);
  wrefresh(win);

  screen->input_pos = screen->input_end;
  screen->cursor_pos = 10;
  /* XXX */
  screen->virtual_window = 0;
}

/* Prints typed character into the input window for user to see. Character 
   attributes must be set separately outside this function. */

void silc_screen_input_print(SilcScreen screen, unsigned char c)
{
  WINDOW *win;
  char *buffer;

  assert(screen != NULL);
  buffer = screen->input_buffer;
  win = screen->input_win;

  /* Return directly if input window is full */
  if (screen->input_pos >= SILC_SCREEN_INPUT_WIN_SIZE)
    return;

  /* The input window is COLS wide but one can type into it at most
     SILC_SCREEN_INPUT_SIZE characters. When COLS - 5 characters is
     typed the window is cleared and the cursor is moved at the tenth
     character in the input window. Ten last typed character is then
     showed at the start of the window. */
  if (screen->cursor_pos >= (COLS - 5)) {
    int i;

    /* Clear line */
    for (i = 0; i < COLS; i++)
      mvwprintw(win, 0, i, " ");
    mvwprintw(win, 0, 0, "");

    /* Show ten last typed characters from the buffer on the screen */
    waddnstr(win, &buffer[screen->input_pos - 10], 10);
    screen->cursor_pos = 10;
    wrefresh(win);

    screen->virtual_window++;
  }

  if (screen->input_pos < screen->input_end) {
    /* User moved cursor into the typed line. We are not adding 
       character at the end of the line anymore */

    if (screen->insert == FALSE) {
      /* Add new character somewhere inside typed line. The input
	 line position is not advanced since a character was replaced
	 by the new character. */
      waddch(win, c);
      buffer[screen->input_pos] = c;
      screen->cursor_pos++;
      screen->input_pos++;
      screen->input_end = screen->input_pos;
    } else {
      /* Insert new character somewhere inside typed line. Other
	 characters are moved forward. We must advance the input line
	 posititon. */
      winsch(win, c);
      wmove(win, 0, screen->cursor_pos + 1);
      SILC_SCREEN_INPUT_INSERT(buffer, screen->input_pos, 
			       c, screen->input_end);
      screen->cursor_pos++;
      screen->input_pos++;
      screen->input_end++;
    }
  } else {
    /* Add new character at the end of input line */
    waddch(win, c);
    buffer[screen->input_pos] = c;
    screen->input_pos++;
    screen->cursor_pos++;
    screen->input_end = screen->input_pos;
  }

  /* Advance the cursor position. Cursor moves one to rightward always */
  wrefresh(win);
}

/* Prints prompt to the input window. Cursors position aftern printing
   is length of the prompt. */

void silc_screen_input_print_prompt(SilcScreen screen, char *prompt)
{
  WINDOW *win;

  assert(screen != NULL);
  win = screen->input_win;

  wclear(win);
  waddnstr(win, prompt, strlen(prompt));
  wrefresh(win);

  screen->input_pos = strlen(prompt);
  screen->cursor_pos = strlen(prompt);
  screen->virtual_window = 0;
}
