/*
 * xine_frontend_kbd.c: Forward (local) console key presses to VDR (server)
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 * $Id$
 *
 */

#include "features.h"

#include <inttypes.h>
#include <poll.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>


#define LOG_MODULENAME "[console]   "
#include "logdefs.h"

#include "xine_frontend.h"
#include "xine_frontend_kbd.h"

/*
 * stdin (keyboard/slave mode) reading
 */

struct input_kbd_s {
  pthread_t   kbd_thread;
  frontend_t *fe;
  int         gui_hotkeys;
};


/*
 * read_key()
 *
 * Try to read single char from stdin.
 *
 * Returns: >=0  char readed
 *           -1  nothing to read
 *           -2  fatal error
 */
#define READ_KEY_ERROR   -2
#define READ_KEY_EAGAIN  -1
static int read_key(void)
{
  unsigned char ch;
  int err;
  struct pollfd pfd;
  pfd.fd = STDIN_FILENO;
  pfd.events = POLLIN;

  errno = 0;
  pthread_testcancel();

  if (1 == (err = poll(&pfd, 1, 50))) {
    pthread_testcancel();

    if (1 == (err = read(STDIN_FILENO, &ch, 1)))
      return (int)ch;

    if (err < 0)
      LOGERR("read_key: read(stdin) failed");
    else
      LOGERR("read_key: read(stdin) failed: no stdin");
    return READ_KEY_ERROR;

  } else if (err < 0 && errno != EINTR) {
    LOGERR("read_key: poll(stdin) failed");
    return READ_KEY_ERROR;
  }

  pthread_testcancel();

  return READ_KEY_EAGAIN;
}

/*
 * read_key_seq()
 *
 * Read a key sequence from stdin.
 * Key sequence is either normal key or escape sequence.
 *
 * Returns the key or escape sequence as uint64_t.
 *
 * Originally copied from vdr:
 * remote.c: General Remote Control handling
 * Copyright (C) 2000, 2003, 2006, 2008 Klaus Schmidinger
 */
#define READ_KEY_SEQ_ERROR 0xffffffff
static uint64_t read_key_seq(void)
{
  /* from vdr, remote.c */
  uint64_t k = 0;
  int key1;

  if ((key1 = read_key()) >= 0) {
    k = key1;
    if (key1 == 0x1B) {
      /* Start of escape sequence */
      if ((key1 = read_key()) >= 0) {
        k <<= 8;
        k |= key1 & 0xFF;
        switch (key1) {
          case 0x4F: /* 3-byte sequence */
            if ((key1 = read_key()) >= 0) {
              k <<= 8;
              k |= key1 & 0xFF;
            }
            break;
          case 0x5B: /* 3- or more-byte sequence */
            if ((key1 = read_key()) >= 0) {
              k <<= 8;
              k |= key1 & 0xFF;
              switch (key1) {
                case 0x31 ... 0x3F: /* more-byte sequence */
                case 0x5B: /* strange, may apparently occur */
                  do {
                    if ((key1 = read_key()) < 0)
                      break; /* Sequence ends here */
                    k <<= 8;
                    k |= key1 & 0xFF;
                  } while (key1 != 0x7E);
                  break;
                default:;
              }
            }
            break;
          default:;
        }
      }
    }
  }

  if (key1 == READ_KEY_ERROR)
    return READ_KEY_SEQ_ERROR;

  return k;
}

/*
 * kbd_receiver_thread()
 *
 * Read key(sequence)s from stdin and pass those to frontend.
 */

#ifndef _WIN32
static void kbd_receiver_thread_cleanup(void *arg)
{
  struct termios *saved_tm = arg;
  if (isatty(STDIN_FILENO)) {
    tcsetattr(STDIN_FILENO, TCSANOW, saved_tm);
  }
  if (isatty(STDOUT_FILENO)) {
    int status;
    status = system("setterm -cursor on");
    if (status < 0)
      LOGDBG("system(\"setterm -cursor on\") failed\n");
  }
  LOGMSG("Keyboard thread terminated");
}
#endif

static void *kbd_receiver_thread(void *kbd_gen)
{
  input_kbd_t *kbd = kbd_gen;
  frontend_t  *fe = kbd->fe;
  uint64_t code = 0;
  char str[64];

#ifndef _WIN32
  struct termios saved_tm;
  if (isatty(STDOUT_FILENO)) {
    int status;
    status = system("setterm -cursor off");
    if (status < 0)
      LOGDBG("system(\"setterm -cursor off\") failed\n");
    status = system("setterm -blank off");
    if (status < 0)
      LOGDBG("system(\"setterm -blank off\") failed\n");
  }

  if (isatty(STDIN_FILENO)) {
    struct termios tm;
    /* Set stdin to deliver keypresses without buffering whole lines */
    tcgetattr(STDIN_FILENO, &saved_tm);
    if (tcgetattr(STDIN_FILENO, &tm) == 0) {
      tm.c_iflag = 0;
      tm.c_lflag &= ~(ICANON | ECHO);
      tm.c_cc[VMIN] = 0;
      tm.c_cc[VTIME] = 0;
      tcsetattr(STDIN_FILENO, TCSANOW, &tm);
    }
  }

  pthread_cleanup_push(kbd_receiver_thread_cleanup, &saved_tm);
#endif

  do {
    alarm(0);
    errno = 0;
    code = read_key_seq();
    alarm(3); /* watchdog */
    if (code == 0)
      continue;
    if (code == READ_KEY_SEQ_ERROR)
      break;
    if (code == 27) {
      fe->send_event(fe, "QUIT");
      break;
    }

    if (kbd->gui_hotkeys) {
      if (code == 'f' || code == 'F') {
        fe->send_event(fe, "TOGGLE_FULLSCREEN");
        continue;
      }
      if (code == 'p' || code == 'P') {
        fe->send_event(fe, "POWER_OFF");
        continue;
      }
      if (code == 'd' || code == 'D') {
        fe->send_event(fe, "TOGGLE_DEINTERLACE");
        continue;
      }
    }

    snprintf(str, sizeof(str), "%016" PRIX64, code);
    fe->send_input_event(fe, "KBD", str, 0, 0);

  } while (fe->xine_is_finished(fe, 0) != FE_XINE_EXIT);

  alarm(0);

  LOGDBG("Keyboard thread terminating");

#ifndef _WIN32
  pthread_cleanup_pop(1);
#endif

  pthread_exit(NULL);
  return NULL; /* never reached */
}

/*
 * slave_receiver_thread()
 *
 * Read slave mode commands from stdin
 * Interpret and execute valid commands
 */

#ifndef _WIN32
static void slave_receiver_thread_cleanup(void *arg)
{
  struct termios *saved_tm = arg;

  /* restore terminal settings */
  tcsetattr(STDIN_FILENO, TCSANOW, saved_tm);
  LOGDBG("Slave mode receiver terminated");
}
#endif

static void *slave_receiver_thread(void *kbd_gen)
{
  input_kbd_t *kbd = kbd_gen;
  frontend_t  *fe = kbd->fe;
  char str[128], *pt;

#ifndef _WIN32
  struct termios saved_tm;
  tcgetattr(STDIN_FILENO, &saved_tm);

  pthread_cleanup_push(slave_receiver_thread_cleanup, &saved_tm);
#endif

  do {
    errno = 0;
    str[0] = 0;

    pthread_testcancel();
    if (!fgets(str, sizeof(str), stdin))
      break;
    pthread_testcancel();

    if (NULL != (pt = strchr(str, '\r')))
      *pt = 0;
    if (NULL != (pt = strchr(str, '\n')))
      *pt = 0;

    if (!strncasecmp(str, "QUIT", 4)) {
      fe->send_event(fe, "QUIT");
      break;
    }
    if (!strncasecmp(str, "FULLSCREEN", 10)) {
      if (strpbrk(str + 10, "01"))
        fe->send_event(fe, str);
      else
        fe->send_event(fe, "TOGGLE_FULLSCREEN");
      continue;
    }
    if (!strncasecmp(str, "DEINTERLACE ", 12)) {
      fe->send_event(fe, str);
      continue;
    }
    if (!strncasecmp(str, "HITK ", 5)) {
      fe->send_input_event(fe, NULL, str+5, 0, 0);
      continue;
    }

    LOGMSG("Unknown slave mode command: %s", str);

  } while (fe->xine_is_finished(fe, 0) != FE_XINE_EXIT);

  LOGDBG("Slave mode receiver terminating");

#ifndef _WIN32
  pthread_cleanup_pop(1);
#endif

  pthread_exit(NULL);
  return NULL; /* never reached */
}

/*
 * kbd_start()
 *
 * Start keyboard/slave mode reader thread
 */
input_kbd_t *kbd_start(frontend_t *fe, int slave_mode, int gui_hotkeys)
{
  input_kbd_t *kbd = calloc(1, sizeof(*kbd));
  int err;

  if (!kbd) {
    return NULL;
  }

  kbd->fe          = fe;
  kbd->gui_hotkeys = gui_hotkeys;

  if ((err = pthread_create (&kbd->kbd_thread,
                             NULL,
                             slave_mode ? slave_receiver_thread : kbd_receiver_thread,
                             (void*)kbd)) != 0) {
    fprintf(stderr, "Can't create new thread for keyboard (%s)\n",
            strerror(err));
    free(kbd);
    kbd = NULL;
  }

  return kbd;
}

/*
 * kbd_stop()
 *
 * Stop keyboard/slave mode reader thread
 */
void kbd_stop(input_kbd_t **pkbd)
{
  if (*pkbd) {
    input_kbd_t *kbd = *pkbd;
    void        *p;

    *pkbd = NULL;

    pthread_cancel (kbd->kbd_thread);
    pthread_join (kbd->kbd_thread, &p);

    free(kbd);
  }
}


