/****************************************************************************
 * apps/examples/chat/chat_main.c
 *
 *   Copyright (C) 2016 Vladimir Komendantskiy. All rights reserved.
 *   Author: Vladimir Komendantskiy <vladimir@moixaenergy.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 <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "netutils/chat.h"

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

#define CHAT_TTYNAME_SIZE  32

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Chat app private state. A supertype of 'chat_ctl'. */

struct chat_app
{
  struct chat_ctl ctl;             /* Embedded 'chat_ctl' type. */

  /* Private fields */

  int argc;                        /* number of command-line arguments */
  FAR char** argv;                 /* command-line arguments */
  char tty[CHAT_TTYNAME_SIZE];     /* modem TTY device node */
  FAR char* script;                /* raw chat script - input to the parser */
  bool script_dynalloc;            /* true iff the script should be freed */
};

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

/* Preset chat scripts */

FAR char g_chat_script0[] = CONFIG_EXAMPLES_CHAT_PRESET0;
FAR char g_chat_script1[] = CONFIG_EXAMPLES_CHAT_PRESET1;
FAR char g_chat_script2[] = CONFIG_EXAMPLES_CHAT_PRESET2;
FAR char g_chat_script3[] = CONFIG_EXAMPLES_CHAT_PRESET3;

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

static void chat_show_usage(void)
{
  printf("Usage: chat [options]\n"
         "  where [options] is a possibly empty list of\n"
         "-d<file>   : modem TTY device node\n"
         "-e         : echo modem output to stderr\n"
         "-f<file>   : chat script file\n"
         "-p<number> : preprogrammed script\n"
         "-t<number> : default modem response timeout\n"
         "-v         : verbose mode\n");
}

static int chat_chardev(FAR struct chat_app* priv)
{
  int flags;

  flags = fcntl(priv->ctl.fd, F_GETFL, 0);
  if (flags < 0)
    {
      return flags;
    }

  flags = fcntl(priv->ctl.fd, F_SETFL, flags | O_NONBLOCK);
  if (flags < 0)
    {
      return flags;
    }

  return 0;
}

static int chat_script_preset(FAR struct chat_app* priv, int script_number)
{
  int ret = 0;

  _info("preset script %d\n", script_number);

  switch (script_number)
    {
    case 0:
      priv->script = g_chat_script0;
      break;

    case 1:
      priv->script = g_chat_script1;
      break;

    case 2:
      priv->script = g_chat_script2;
      break;

    case 3:
      priv->script = g_chat_script3;
      break;

    default:
      ret = -ERANGE;
      break;
    }

  return ret;
}

static int chat_script_read(FAR struct chat_app* priv,
                            FAR const char* filepath)
{
  FAR char* scriptp;
  size_t spare_size = CONFIG_EXAMPLES_CHAT_SIZE-1;
  ssize_t read_size;
  bool eof = false;
  int ret = 0;
  int fd;

  priv->script = malloc(CONFIG_EXAMPLES_CHAT_SIZE);
  if (!priv->script)
    {
      return -ENOMEM;
    }

  priv->script_dynalloc = true;
  scriptp = priv->script;
  memset(scriptp, 0, CONFIG_EXAMPLES_CHAT_SIZE);

  fd = open(filepath, O_RDONLY);
  if (fd < 0)
    {
      fprintf(stderr, "Cannot open %s, error %d\n", filepath, errno);
      ret = -ENOENT;
    }

  while (!ret && !eof && spare_size > 0)
    {
      read_size = read(fd, scriptp, spare_size);
      if (read_size < 0)
        {
          /* EINTR is not a read error.  It simply means that a signal was
           * received while waiting for the read to complete.
           */

          if (errno != EINTR)
            {
              fprintf(stderr, "Cannot read %s, error %d\n",
                      filepath, errno);
              ret = -EIO;
            }
        }
      else if (read_size == 0)
        {
          eof = true;
        }
      else
        {
          /* read_size > 0 */

          DEBUGASSERT(read_size <= spare_size);
          scriptp += read_size;
          spare_size -= read_size;
        }
    }

  close(fd);
  return ret;
}

static int chat_parse_args(FAR struct chat_app* priv)
{
  /*
   * -d TTY device node (non-Linux feature)
   * -e echo to stderr
   * -f script file
   * -p preprogrammed script (non-Linux feature)
   * -t timeout
   * -v verbose mode
   */

  int numarg;
  int ret = 0;
  int i;

  DEBUGASSERT(priv != NULL);

  if (priv->argc < 2)
    {
      ret = -EINVAL;
    }

  /* Iterate through command-line arguments and parse those */

  for (i = 1; !ret && i < priv->argc && priv->argv[i]; i++)
    {
      if (priv->argv[i][0] != '-')
        {
          ret = -EINVAL;
        }
      else
        {
          switch (priv->argv[i][1])
            {
            case 'd':
              /* set the TTY device node */

              strncpy(priv->tty,
                      (FAR char*) priv->argv[i] + 2,
                      CHAT_TTYNAME_SIZE-1);
              break;

            case 'e':
              priv->ctl.echo = true;
              break;

            case 'f':
              ret = chat_script_read(priv,
                                     (FAR char*) priv->argv[i] + 2);
              break;

            case 'p':
              numarg = strtol((FAR char*) priv->argv[i] + 2,
                              NULL, 10);
              if (errno < 0)
                {
                  ret = -EINVAL;
                  break;
                }

              ret = chat_script_preset(priv, numarg);
              break;

            case 't':
              numarg = strtol((FAR char*) priv->argv[i] + 2,
                              NULL, 10);

              if (errno < 0 || numarg < 0)
                {
                  ret = -EINVAL;
                }
              else
                {
                  priv->ctl.timeout = numarg;
                }

              break;

            case 'v':
              priv->ctl.verbose = true;
              break;

            default:
              ret = -EINVAL;
              break;
            }
        }
    }

  return ret;
}

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

/****************************************************************************
 * Name: chat_main
 *
 * Description:
 *   Chat command entry point.
 *
 ****************************************************************************/

#ifdef CONFIG_BUILD_KERNEL
int main(int argc, FAR char** argv)
#else
int chat_main(int argc, FAR char** argv)
#endif
{
  struct chat_app priv;
  int ret;
  int exit_code = EXIT_SUCCESS;

  priv.argc = argc;
  priv.argv = argv;
  priv.ctl.echo = false;
  priv.ctl.verbose = false;
  priv.ctl.timeout = CONFIG_EXAMPLES_CHAT_TIMEOUT_SECONDS;
  priv.script = NULL;
  priv.script_dynalloc = false;
  strncpy(priv.tty, CONFIG_EXAMPLES_CHAT_TTY_DEVNODE, CHAT_TTYNAME_SIZE-1);

  _info("parsing the arguments\n");
  ret = chat_parse_args((FAR struct chat_app*) &priv);
  if (ret < 0)
    {
      _info("Command line parsing failed: code %d, errno %d\n", ret, errno);
      chat_show_usage();
      exit_code = EXIT_FAILURE;
      goto with_script;
    }

  if (priv.script == NULL)
    {
      fprintf(stderr, "No chat script given\n");
      exit_code = EXIT_FAILURE;
      goto no_script;
    }

  _info("opening %s\n", priv.tty);
  priv.ctl.fd = open(priv.tty, O_RDWR);
  if (priv.ctl.fd < 0)
    {
      _info("Failed to open %s: %d\n", priv.tty, errno);
      exit_code = EXIT_FAILURE;
      goto with_script;
    }

  _info("setting up character device\n");
  ret = chat_chardev(&priv);
  if (ret < 0)
    {
      _info("Failed to open %s: %d\n", priv.tty, errno);
      exit_code = EXIT_FAILURE;
      goto with_tty_dev;
    }

  ret = chat((FAR struct chat_ctl*) &priv.ctl, priv.script);

with_tty_dev:
  close(priv.ctl.fd);

with_script:
  if (priv.script_dynalloc)
    {
      free(priv.script);
    }

no_script:
  fflush(stderr);
  fflush(stdout);

  _info("Exit code %d\n", exit_code);
  return exit_code;
}
