/****************************************************************************
 * examples/pty_test/pty_test.c
 *
 *   Copyright (C) 2016, Gregory Nutt. All rights reserved.
 *   Author: Alan Carvalho de Assisi <acassis@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termios.h>
#include <poll.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

#include <nuttx/serial/pty.h>

#include "nshlib/nshlib.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifndef CONFIG_EXAMPLES_PTYTEST_SERIALDEV
#  define CONFIG_EXAMPLES_PTYTEST_SERIALDEV "/dev/ttyS1"
#endif

#ifndef CONFIG_EXAMPLES_PTYTEST_DAEMONPRIO
#  define CONFIG_EXAMPLES_PTYTEST_DAEMONPRIO SCHED_PRIORITY_DEFAULT
#endif

#ifndef CONFIG_EXAMPLES_PTYTEST_STACKSIZE
#  define CONFIG_EXAMPLES_PTYTEST_STACKSIZE 2048
#endif

#define POLL_TIMEOUT  200

/****************************************************************************
 * Private Data
 ****************************************************************************/

struct term_pair_s
{
  int fd_uart;
  int fd_pty;
};

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: serial_in
 *
 * Description:
 *   Read data from serial and write to pts
 *
 ****************************************************************************/

static void serial_in(struct term_pair_s *tp)
{
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
  struct pollfd fdp;
#endif
  char buffer[16];
  int ret;

  if (!tp)
    {
      fprintf(stderr, "ERROR: terminal pair struct is NULL!\n");
      return;
    }

#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
  fdp.fd     = tp->fd_uart;
  fdp.events = POLLIN;
#endif

  /* Run forever */

  for (;;)
    {
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
      ret = poll((struct pollfd *)&fdp, 1, POLL_TIMEOUT);
      if (ret > 0)
        {
          if ((fdp.revents & POLLIN) != 0)
#endif
            {
              ssize_t len;

              len = read(tp->fd_uart, buffer, 16);
              if (len < 0)
                {
                  fprintf(stderr,
                          "ERROR Failed to read from serial: %d\n",
                          errno);
                }
              else if (len > 0)
                {
                  ret = write(tp->fd_pty, buffer, len);
                  if (ret < 0)
                    {
                      fprintf(stderr,
                              "Failed to write to serial: %d\n",
                              errno);
                    }
                }
            }
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
        }

      /* Timeout */

      else if (ret == 0)
        {
          continue;
        }

      /* Poll error */

      else if (ret < 0)
        {
          fprintf(stderr, "ERROR: poll failed: %d\n", errno);
          continue;
        }
#endif
    }
}

/****************************************************************************
 * Name: serial_out
 *
 * Description:
 *   Read data from pts and write to serial
 *
 ****************************************************************************/

static void serial_out(struct term_pair_s *tp)
{
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
  struct pollfd fdp;
#endif
  char buffer[16];
  int ret;

  if (!tp)
    {
      fprintf(stderr, "Error: terminal pair struct is NULL!\n");
      return;
    }

#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
  fdp.fd = tp->fd_pty;
  fdp.events = POLLIN;
#endif

  /* Run forever */

  for (;;)
    {
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
      ret = poll((struct pollfd *)&fdp, 1, POLL_TIMEOUT);
      if (ret > 0)
        {
          if ((fdp.revents & POLLIN) != 0)
#endif
            {
              ssize_t len;

              len = read(tp->fd_pty, buffer, 16);
              if (len < 0)
                {
                  fprintf(stderr,
                          "ERROR: Failed to read from pseudo-terminal: %d\n",
                          errno);
                }
              else if (len > 0)
                {
                  ret = write(tp->fd_uart, buffer, len);
                  if (ret < 0)
                    {
                      fprintf(stderr,
                              "ERROR: Failed to write to serial: %d\n",
                              errno);
                    }
                }
            }
#ifdef CONFIG_EXAMPLES_PTYTEST_POLL
        }

      /* Timeout */

      else if (ret == 0)
        {
          continue;
        }

      /* poll error */

      else if (ret < 0)
        {
          fprintf(stderr, "ERROR: poll failed: %d\n", errno);
          continue;
        }
#endif
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: pty_test_main
 ****************************************************************************/

#ifdef CONFIG_BUILD_KERNEL
int main(int argc, FAR char *argv[])
#else
int pty_test_main(int argc, char *argv[])
#endif
{
  struct term_pair_s termpair;
  struct termios tio;
  pthread_t si_thread;
  pthread_t so_thread;
  char buffer[16];
  pid_t pid;
  int fd_pts;
  int ret;

  printf("Create pseudo-terminal\n");

  /* Open pseudo-terminal master (pts factory) */

  termpair.fd_pty = open("/dev/ptmx", O_RDWR | O_NOCTTY);
  if (termpair.fd_pty < 0)
    {
      fprintf(stderr, "ERROR: Failed to opening /dev/ptmx: %d\n",
              errno);
      return EXIT_FAILURE;
    }

  /* Grant access and unlock the slave PTY */

  ret = grantpt(termpair.fd_pty);
  if (ret < 0)
    {
      fprintf(stderr, "ERROR: grantpt() failed: %d\n", errno);
      goto error_pts;
    }

  ret = unlockpt(termpair.fd_pty);
  if (ret < 0)
    {
      fprintf(stderr, "ERROR: unlockpt() failed: %d\n", errno);
      goto error_pts;
    }

  /* Get the create pts file-name */

  ret = ptsname_r(termpair.fd_pty, buffer, 16);
  if (ret < 0)
    {
      fprintf(stderr, "ERROR: ptsname_r() failed: %d\n", errno);
      goto error_pts;
    }

  /* Open the created pts */

  fd_pts = open(buffer, O_RDWR | O_NONBLOCK);
  if (fd_pts < 0)
    {
      fprintf(stderr, "ERROR: Failed to open %s: %d\n", buffer, errno);
      goto error_pts;
    }

  /* Open the second serial port to create a new console there */

  termpair.fd_uart = open(CONFIG_EXAMPLES_PTYTEST_SERIALDEV,
                          O_RDWR | O_NONBLOCK);
  if (termpair.fd_uart < 0)
    {
      fprintf(stderr, "Failed to open %s: %\n",
             CONFIG_EXAMPLES_PTYTEST_SERIALDEV, errno);
      goto error_serial;
    }

#ifdef CONFIG_SERIAL_TERMIOS
  /* Enable \n -> \r\n conversion during write */

  ret = tcgetattr(termpair.fd_uart, &tio);
  if (ret)
    {
      fprintf(stderr, "ERROR: tcgetattr() failed: %d\n", errno);
      goto error_serial;
    }

  tio.c_oflag = OPOST | ONLCR;
  ret = tcsetattr(termpair.fd_uart, TCSANOW, &tio);
  if (ret)
    {
      fprintf(stderr, "ERROR: tcsetattr() failed: %d\n", errno);
      goto error_serial;
    }
#endif

  printf("Starting a new NSH Session using %s\n", buffer);

  /* Close default stdin, stdout and stderr */

  close(0);
  close(1);
  close(2);

  /* Use this pts file as stdin, stdout, and stderr */

  (void)dup2(fd_pts, 0);
  (void)dup2(fd_pts, 1);
  (void)dup2(fd_pts, 2);

  /* Create a new console using this /dev/pts/N */

  pid = task_create("NSH Console", CONFIG_EXAMPLES_PTYTEST_DAEMONPRIO,
                    CONFIG_EXAMPLES_PTYTEST_STACKSIZE, nsh_consolemain, NULL);
  if (pid < 0)
    {
      /* Can't do output because stdout and stderr are redirected */

      goto error_console;
    }

  /* Start a thread to read from serial */

  ret = pthread_create(&si_thread, NULL,
                       (pthread_startroutine_t)serial_in,
                       (pthread_addr_t)&termpair);
  if (ret != 0)
    {
      /* Can't do output because stdout and stderr are redirected */

      goto error_thread1;
    }

  /* Start a thread to write to serial */

  ret = pthread_create(&so_thread, NULL,
                       (pthread_startroutine_t)serial_out,
                       (pthread_addr_t)&termpair);
  if (ret != 0)
    {
      /* Can't do output because stdout and stderr are redirected */

      goto error_thread2;
    }

  /* Stay here to keep the threads running */

  for (;;)
    {
      /* Nothing to do, then sleep to avoid eating all cpu time */

      usleep(10000);
    }

  return EXIT_SUCCESS;

error_thread2:
error_thread1:
error_console:
  close(termpair.fd_uart);
error_serial:
  close(fd_pts);
error_pts:
  close(termpair.fd_pty);

  return EXIT_FAILURE;
}
