/*
    This file is part of libtermui.

    libtermui 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 3 of the License, or
    (at your option) any later version.

    libtermui 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 libtermui.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2010, Alexandre Becoulet <alexandre.becoulet@free.fr>

*/

#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <assert.h>

#include "term_pv.h"
#include <termui/term_io.h>
#include <termui/term_keys.h>

# ifdef CONFIG_LIBTERMUI_IOCTL
#  include <sys/ioctl.h>
# endif

static TERMUI_TERMIO_FCN_GETSIZE(termui_tty_getsize)
{
# ifdef CONFIG_LIBTERMUI_IOCTL
#  ifdef TIOCGSIZE
  struct ttysize win;

  if (ioctl(tm->io_tty.fd_in, TIOCGSIZE, &win))
    return -EIO;

  *x = win.ts_cols;
  *y = win.ts_lines;

  return 0;

# elif defined(TIOCGWINSZ)
  struct winsize win;

  if (ioctl(tm->io_tty.fd_in, TIOCGWINSZ, &win))
    return -EIO;

  *x = win.ws_col;
  *y = win.ws_row;

  return 0;

# else
#  warning No terminal size ioctl available
# endif
#endif
  return -EIO;
}

static TERMUI_TERMIO_FCN_WRITE(termui_tty_write)
{
  while (size)
    {
      termui_ssize_t res;

      res = write(tm->io_rawfd.fd_out, data, size);

      if (res < 0)
	{
	  tm->ioerr = 1;
	  return;
	}

      assert(res <= size);
      size -= res;
    }
}

static struct termui_term_s *resize_tty = NULL;
static termui_bool_t resized = 0;

static TERMUI_TERMIO_FCN_READ(termui_tty_read)
{
  termui_ssize_t res = 0;
  uint8_t c;

  while (res <= 0)
    {
      res = read(tm->io_tty.fd_in, &c, 1);

      if (res < 0)
	{
	  if (errno == EINTR)
            {
              if (resized)
                return -2;
              else
                continue;
            }
	  tm->ioerr = errno;
	  return -1;
	}
    }

  return c;
}

static TERMUI_TERMIO_FCN_POLLFD(termui_tty_pollfd)
{
  return tm->io_tty.fd_in;
}

static TERMUI_TERMIO_FCN_CLEANUP(termui_tty_cleanup)
{
  if (isatty(tm->io_tty.fd_in))
    tcsetattr(tm->io_tty.fd_in, 0, &tm->io_tty.old_tio);

  if (tm == resize_tty)
    {
      resize_tty = NULL;
      signal(SIGWINCH, SIG_IGN);
    }
}

static void resize_sig(int sig)
{
  if (resize_tty)
    resized = 1;
}

termui_err_t
termui_tty_io_init(struct termui_term_s *tm, int32_t fd_in, int32_t fd_out)
{
  const char *ttype = getenv("TERM");
  struct termios	tio;

  memset(tm, 0, sizeof(*tm));
  tm->io_tty.fd_in = fd_in;
  tm->io_tty.fd_out = fd_out;

  /* register tty operations in term object */
  tm->s_write = termui_tty_write;
  tm->s_read = termui_tty_read;
  tm->s_pollfd = termui_tty_pollfd;
  tm->s_cleanup = termui_tty_cleanup;

  if (!isatty(fd_in))
    {
      tm->s_getsize = (termui_termio_getsize_t*)term_err;
      return 0;
    }

  if (fd_in == 0)
    {
      struct sigaction act = {};

      resize_tty = tm;
      act.sa_handler = resize_sig;
      sigaction(SIGWINCH, &act, NULL);
    }

  tm->s_getsize = termui_tty_getsize;

  /* switch tty to raw mode */

  if (!tcgetattr(fd_in, &tio))
    {
      tm->io_tty.old_tio = tio;

# if 1
      tio.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
      tio.c_oflag &= ~OPOST;
      tio.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
      tio.c_cflag &= ~(CSIZE | PARENB);
      tio.c_cflag |= CS8;
# else
      cfmakeraw(&tio);
# endif

      if (tcsetattr(fd_in, 0, &tio))
	return -EIO;
    }

  /* set terminal type and associated terminal operations */
  if (ttype)
    strncpy(tm->type, ttype, TERMUI_TYPESTR_MAXLEN);
  termui_term_set(tm);

  return 0;
}

