#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#ifndef NLL_TERM_DISABLE
#ifndef WIN32
#include <termios.h>
#else
#include <windows.h>
#include <conio.h>
#endif
#endif
#ifdef USE_CURSES
#include <curses.h>
#endif

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "const.h"
#include "nlllib.h"
#include "term.h"

static int savenum = 0;

#ifdef USE_CURSES
static int term_init_curses(void)
{
  return 0;
}

static int term_done_curses(void)
{
  return 0;
}
#endif

static int term_init(void)
{
  return 0;
}

static int term_done(void)
{
  return 0;
}

#ifdef WIN32
static DWORD win_mode;

static int term_init_win(void)
{
  HANDLE handle = (HANDLE)_get_osfhandle(STDOUT_FILENO);
  DWORD mode = 0;

  if (!GetConsoleMode(handle, &mode))
    return -1;

  win_mode = mode;

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

  if (!SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
    return -1;

  return 0;
}

static int term_done_win(void)
{
  HANDLE handle = (HANDLE)_get_osfhandle(STDOUT_FILENO);

  if (!SetConsoleMode(handle, win_mode))
    return -1;

  return 0;
}
#endif

#ifdef USE_CURSES
#ifdef USE_FRAMEBUF
static int curses_start(void)
{
  initscr();

  noecho();
  cbreak();
  nodelay(stdscr, 1);

  return 0;
}

static int curses_restore(void)
{
  refresh();
  nodelay(stdscr, 0);
  nocbreak();
  echo();

  endwin();

  return 0;
}
#endif
#endif

#ifndef NLL_TERM_DISABLE
#ifndef WIN32
static struct termios save;
static int savefl;

static int term_start(void)
{
  struct termios sets;

  if (!nll_is_tty())
    return 0;

  tcgetattr(STDIN_FILENO, &sets);

  save = sets;
  sets.c_lflag &= ~(ECHO | ICANON);
  sets.c_cc[VTIME] = 0;
  sets.c_cc[VMIN]  = 1;
  tcsetattr(STDIN_FILENO, TCSANOW, &sets);

  savefl = fcntl(STDIN_FILENO, F_GETFL, 0);
  fcntl(STDIN_FILENO, F_SETFL, savefl | O_NONBLOCK);

  savenum = 0;

  return 0;
}

static int term_restore(void)
{
  if (!nll_is_tty())
    return 0;
  fcntl(STDIN_FILENO, F_SETFL, savefl);
  tcsetattr(STDIN_FILENO, TCSANOW, &save);
  return 0;
}
#else
static int term_start(void)
{
  return 0;
}

static int term_restore(void)
{
  return 0;
}
#endif
#endif

int nll_term_init(void)
{
  int r;
#ifdef WIN32
  if ((r = term_init_win()) < 0)
    return r;
#endif
  if ((r = term_init()) < 0)
    return r;
#ifdef USE_CURSES
  if ((r = term_init_curses()) < 0)
    return r;
#endif
  return 0;
}

int nll_term_done(void)
{
  int r;
#ifdef USE_CURSES
  if ((r = term_done_curses()) < 0)
    return r;
#endif
  if ((r = term_done()) < 0)
    return r;
#ifdef WIN32
  if ((r = term_done_win()) < 0)
    return r;
#endif
  return 0;
}

int nll_term_start(int flags)
{
#ifdef USE_CURSES
#ifdef USE_FRAMEBUF
  if (flags & NLL_F_CURSES)
    return curses_start();
#endif
#endif
#ifndef NLL_TERM_DISABLE
  return term_start();
#else
  return 0;
#endif
}

int nll_term_restore(int flags)
{
#ifdef USE_CURSES
#ifdef USE_FRAMEBUF
  if (flags & NLL_F_CURSES)
    return curses_restore();
#endif
#endif
#ifndef NLL_TERM_DISABLE
  return term_restore();
#else
  return 0;
#endif
}

#ifdef USE_CURSES
#ifdef USE_FRAMEBUF
static int curses_key(void)
{
  int r;
  unsigned char c;

  r = getch();
  if (r == ERR)
    return -1;

  switch (r) {
  case KEY_UP:        c = 0x10; break;
  case KEY_DOWN:      c = 0x0e; break;
  case KEY_LEFT:      c = 0x02; break;
  case KEY_RIGHT:     c = 0x06; break;
  case KEY_BACKSPACE: c = 0x08; break;
  case KEY_DC:        c = 0x04; break;
  case KEY_ENTER:     c = '\n'; break;
  default:            c = r;    break;
  }

  return c;
}
#endif
#endif

static int term_key(void)
{
  int r;
  unsigned char c;

#ifndef WIN32
  r = read(STDIN_FILENO, &c, 1);
#else
  if (_kbhit()) {
    c = _getch();
    r = 1;
    switch (c) {
    case 0x0d: c = '\n'; break;
    default: break;
    }
  } else {
    r = 0;
  }
#endif

  if (r <= 0)
    return -1;

  return c;
}

int nll_term_key(int flags)
{
  int i, escape = 0, k;
#define KEY_SAVENUM 8
  static int key[KEY_SAVENUM];

  for (i = 0; i < 64; i++) {
#ifdef USE_CURSES
#ifdef USE_FRAMEBUF
    if (flags & NLL_F_CURSES)
      k = curses_key();
    else
#endif
#endif
    k = term_key();
    if (k < 0)
      break;

    switch (escape) {
    case 0:
      if (k == NLL_TERM_KEY_ESCAPE) {
	escape = k;
	continue;
      }
      break;

    default:
      switch (escape) {
      case NLL_TERM_KEY_ESCAPE:
	escape = 0;
	/* fall through */

#ifdef NLL_TERM_KEY_START1
      case NLL_TERM_KEY_START1:
#endif
#ifdef NLL_TERM_KEY_START2
      case NLL_TERM_KEY_START2:
#endif
#ifdef NLL_TERM_KEY_START3
      case NLL_TERM_KEY_START3:
#endif
#ifdef NLL_TERM_KEY_START4
      case NLL_TERM_KEY_START4:
#endif
	escape = (escape << 8) | k;
	switch (escape) {
	case NLL_TERM_KEY_END:
	case NLL_TERM_KEY_UP:
	case NLL_TERM_KEY_DOWN:
	case NLL_TERM_KEY_RIGHT:
	case NLL_TERM_KEY_LEFT:
	case NLL_TERM_KEY_PAGEUP:
	case NLL_TERM_KEY_PAGEDOWN:
	  k = escape;
	  escape = 0;
	  break;
	default:
	  continue;
	}
	break;

      default:
	escape = 0;
	continue;
      }
      break;
    }

    if ((!savenum || (k != key[savenum - 1])) && (savenum < KEY_SAVENUM))
      key[savenum++] = k;
  }

  if (!savenum)
    return 0;

  k = key[0];
  for (i = 0; i < savenum - 1; i++)
    key[i] = key[i + 1];
  savenum--;

  return k;
}
