/*
 * cfgparser.c : GeeXboX uShare config file parser.
 * Originally developped for the GeeXboX project.
 * Copyright (C) 2005-2007 Alexis Saettler <asbin@asbin.org>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <stdbool.h>
#include <limits.h>

#include "config.h"
#include "gettext.h"
#include "cfgparser.h"
#include "ushare.h"
#include "trace.h"
#include "osdep.h"

#define USHARE_DIR_DELIM ","

static bool
ignore_line (const char *line)
{
  int i;
  size_t len;

  /* commented line */
  if (line[0] == '#' )
    return true;

  len = strlen (line);

  for (i = 0 ; i < (int) len ; i++ )
    if (line[i] != ' ' && line[i] != '\t' && line[i] != '\n')
      return false;

  return true;
}

static char *
strdup_trim (const char *s)
{
  size_t begin, end;
  char *r = NULL;

  if (!s)
    return NULL;

  end = strlen (s) - 1;

  for (begin = 0 ; begin < end ; begin++)
    if (s[begin] != ' ' && s[begin] != '\t' && s[begin] != '"')
      break;

  for (; begin < end ; end--)
    if (s[end] != ' ' && s[end] != '\t' && s[end] != '"' && s[end] != '\n')
      break;

  r = strndup (s + begin, end - begin + 1);

  return r;
}

static void
ushare_set_name (struct ushare_t *ut, const char *name)
{
  if (!ut || !name)
    return;

  if (ut->name)
  {
    free (ut->name);
    ut->name = NULL;
  }

  ut->name = strdup_trim (name);
}

static void
ushare_set_interface (struct ushare_t *ut, const char *iface)
{
  if (!ut || !iface)
    return;

  if (ut->interface)
  {
    free (ut->interface);
    ut->interface = NULL;
  }

  ut->interface = strdup_trim (iface);
}

static void
ushare_add_contentdir (struct ushare_t *ut, const char *dir)
{
  if (!ut || !dir)
    return;

  ut->contentlist = content_add (ut->contentlist, dir);
}

static void
ushare_set_cfg_file (struct ushare_t *ut, const char *file)
{
  if (!ut || !file)
    return;

  ut->cfg_file = strdup (file);
}

static void
ushare_set_dir (struct ushare_t *ut, const char *dirlist)
{
  char *x = NULL, *token = NULL;
  char *m_buffer = NULL, *buffer;

  if (!ut || !dirlist)
    return;

  x = strdup_trim (dirlist);
  if (x)
  {
    m_buffer = (char*) malloc (strlen (x) * sizeof (char));
    if (m_buffer)
    {
      buffer = m_buffer;
      token = strtok_r (x, USHARE_DIR_DELIM, &buffer);
      while (token)
      {
        ushare_add_contentdir (ut, token);
        token = strtok_r (NULL, USHARE_DIR_DELIM, &buffer);
      }
      free (m_buffer);
    }
    free (x);
  }
}

static void
ushare_set_port (struct ushare_t *ut, const char *port)
{
  if (!ut || !port)
    return;

  ut->port = atoi (port);
  if (ut->port < 49152)
  {
    fprintf (stderr,
             _("Warning: port doesn't fit IANA port assignements.\n"));

    fprintf (stderr, _("Warning: Only Dynamic or Private Ports can be used "
                       "(from 49152 through 65535)\n"));
    ut->port = 0;
  }
}

static void
ushare_set_telnet_port (struct ushare_t *ut, const char *port)
{
  if (!ut || !port)
    return;

  ut->telnet_port = atoi (port);
}

static void
ushare_use_web (struct ushare_t *ut, const char *val)
{
  if (!ut || !val)
    return;

  ut->use_presentation = (!strcmp (val, "yes")) ? true : false;
}

static void
ushare_use_telnet (struct ushare_t *ut, const char *val)
{
  if (!ut || !val)
    return;

  ut->use_telnet = (!strcmp (val, "yes")) ? true : false;
}

static void
ushare_use_xbox (struct ushare_t *ut, const char *val)
{
  if (!ut || !val)
    return;

  ut->xbox360 = (!strcmp (val, "yes")) ? true : false;
}

static void
ushare_use_dlna (struct ushare_t *ut, const char *val)
{
  if (!ut || !val)
    return;

#ifdef HAVE_DLNA
  ut->dlna_enabled = (!strcmp (val, "yes")) ? true : false;
#endif /* HAVE_DLNA */
}

static void
ushare_set_override_iconv_err (struct ushare_t *ut, const char *arg)
{
  if (!ut)
    return;

  ut->override_iconv_err = false;

  if (!strcasecmp (arg, "yes")
      || !strcasecmp (arg, "true")
      || !strcmp (arg, "1"))
    ut->override_iconv_err = true;
}

static u_configline_t configline[] = {
  { USHARE_NAME,                 ushare_set_name                },
  { USHARE_IFACE,                ushare_set_interface           },
  { USHARE_PORT,                 ushare_set_port                },
  { USHARE_TELNET_PORT,          ushare_set_telnet_port         },
  { USHARE_DIR,                  ushare_set_dir                 },
  { USHARE_OVERRIDE_ICONV_ERR,   ushare_set_override_iconv_err  },
  { USHARE_ENABLE_WEB,           ushare_use_web                 },
  { USHARE_ENABLE_TELNET,        ushare_use_telnet              },
  { USHARE_ENABLE_XBOX,          ushare_use_xbox                },
  { USHARE_ENABLE_DLNA,          ushare_use_dlna                },
  { NULL,                        NULL                           },
};

static void
parse_config_line (struct ushare_t *ut, const char *line,
                   u_configline_t *configline)
{
  char *s = NULL;

  s = strchr (line, '=');
  if (s && s[1] != '\0')
  {
    int i;
    for (i=0 ; configline[i].name ; i++)
    {
      if (!strncmp (line, configline[i].name, strlen (configline[i].name)))
      {
        configline[i].set_var (ut, s + 1);
        break;
      }
    }
  }
}

int
parse_config_file (struct ushare_t *ut)
{
  char filename[PATH_MAX];
  FILE *conffile;
  char *line = NULL;
  size_t size = 0;
  ssize_t read;

  if (!ut)
    return -1;

  if (!ut->cfg_file)
    snprintf (filename, PATH_MAX, "%s/%s", SYSCONFDIR, USHARE_CONFIG_FILE); 
  else
    snprintf (filename, PATH_MAX, "%s", ut->cfg_file);

  conffile = fopen (filename, "r");
  if (!conffile) {
    /* fprintf here, because syslog not yet ready */
    fprintf (stderr, _("Warning: can't parse file \"%s\".\n"), filename);
    return -1;
  }

  while ((read = getline (&line, &size, conffile)) != -1)
  {
    if (ignore_line (line))
      continue;

    if (line[read-1] == '\n')
      line[read-1] = '\0';

    while (line[0] == ' ' || line[0] == '\t')
      line++;

    parse_config_line (ut, line, configline);
  }

  fclose (conffile);

  if (line)
    free (line);

  return 0;
}

inline static void
display_usage (void)
{
  display_headers ();
  printf ("\n");
  printf (_("Usage: ushare [-n name] [-i interface] [-p port] [-c directory] [[-c directory]...]\n"));
  printf (_("Options:\n"));
  printf (_(" -n, --name=NAME\tSet UPnP Friendly Name (default is '%s')\n"),
          DEFAULT_USHARE_NAME);
  printf (_(" -i, --interface=IFACE\tUse IFACE Network Interface (default is '%s')\n"),
          DEFAULT_USHARE_IFACE);
  printf (_(" -f, --cfg=FILE\t\tConfig file to be used\n"));
  printf (_(" -p, --port=PORT\tForces the HTTP server to run on PORT\n"));
  printf (_(" -q, --telnet-port=PORT\tForces the TELNET server to run on PORT\n"));
  printf (_(" -c, --content=DIR\tShare the content of DIR directory\n"));
  printf (_(" -w, --no-web\t\tDisable the control web page (enabled by default)\n"));
  printf (_(" -t, --no-telnet\tDisable the TELNET control (enabled by default)\n"));
  printf (_(" -o, --override-iconv-err\tIf iconv fails parsing name, still add to media contents (hoping the renderer can handle it)\n"));
  printf (_(" -v, --verbose\t\tSet verbose display\n"));
  printf (_(" -x, --xbox\t\tUse XboX 360 compliant profile\n"));
#ifdef HAVE_DLNA
  printf (_(" -d, --dlna\t\tUse DLNA compliant profile (PlayStation3 needs this)\n"));
#endif /* HAVE_DLNA */
  printf (_(" -D, --daemon\t\tRun as a daemon\n"));
  printf (_(" -V, --version\t\tDisplay the version of uShare and exit\n"));
  printf (_(" -h, --help\t\tDisplay this help\n"));
}

int
parse_command_line (struct ushare_t *ut)
{
  int c, index;
  char short_options[] = "VhvDowtxdn:i:p:q:c:f:";
  struct option long_options [] = {
    {"version", no_argument, 0, 'V' },
    {"help", no_argument, 0, 'h' },
    {"verbose", no_argument, 0, 'v' },
    {"daemon", no_argument, 0, 'D' },
    {"override-iconv-err", no_argument, 0, 'o' },
    {"name", required_argument, 0, 'n' },
    {"interface", required_argument, 0, 'i' },
    {"port", required_argument, 0, 'p' },
    {"telnet-port", required_argument, 0, 'q' },
    {"content", required_argument, 0, 'c' },
    {"no-web", no_argument, 0, 'w' },
    {"no-telnet", no_argument, 0, 't' },
    {"xbox", no_argument, 0, 'x' },
#ifdef HAVE_DLNA
    {"dlna", no_argument, 0, 'd' },
#endif /* HAVE_DLNA */
    {"cfg", required_argument, 0, 'f' },
    {0, 0, 0, 0 }
  };

  /* command line argument processing */
  while (true)
  {
    c = getopt_long (ut->argc, ut->argv, short_options, long_options, &index);

    if (c == EOF)
      break;

    switch (c)
    {
    case 0:
      /* opt = long_options[index].name; */
      break;

    case '?':
    case 'h':
      display_usage ();
      return -1;

    case 'V':
      display_headers ();
      return -1;

    case 'v':
      ut->verbose = true;
      break;

    case 'D':
      ut->daemon = true;
      break;

    case 'o':
      ut->override_iconv_err = true;
      break;

    case 'n':
      ushare_set_name (ut, optarg);
      break;

    case 'i':
      ushare_set_interface (ut, optarg);
      break;

    case 'p':
      ushare_set_port (ut, optarg);
      break;

    case 'q':
      ushare_set_telnet_port (ut, optarg);
      break;

    case 'c':
      ushare_add_contentdir (ut, optarg);
      break;

    case 'w':
      ut->use_presentation = false;
      break;

    case 't':
      ut->use_telnet = false;
      break;

    case 'x':
      ut->xbox360 = true;
      break;

#ifdef HAVE_DLNA
    case 'd':
      ut->dlna_enabled = true;
      break;
#endif /* HAVE_DLNA */
      
    case 'f':
      ushare_set_cfg_file (ut, optarg);
      break;

    default:
      break;
    }
  }

  return 0;
}
