/*
 * UPIWIN - Micro Pi Windowing Framework Kernel
 * Copyright (C) 2019 Amy Bowersox/Erbosoft Metaverse Design Solutions
 *
 * 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 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 <limits.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include "config.h"
#include "scode.h"
#include "log.h"

/* command line options for UPIWIN */
static const struct option long_options[] = {
  {"framebuffer", required_argument, 0, 'F'},
  {"help",        no_argument,       0, 'h'},
  {"touchscreen", required_argument, 0, 'T'},
  { NULL,         0,                 0,  0 }
};

static const char *short_options = "F:hT:";

/* printed to stdout when upiwin is executed with -h/--help option */
static const char *helptext =
  "UPIWIN - Micro Pi Windows server program\n\n"
  "Usage: upiwin [options] scriptname [scriptargs]\n\n"
  "Available options:\n"
  "  -F,--framebuffer [devname] - Specifies the framebuffer device name\n"
  "  -h,--help - Displays this help message.\n"
  "  -T,--touchscreen - Specifies the touchscreen device name\n"
  "";

#define EXITFUNCBLOCK_FUNCCOUNT 64  /* number of exit functions per function block */

/* exit functions are stored in these data blocks */
typedef struct tagEXITFUNCBLOCK
{
  struct tagEXITFUNCBLOCK *next;             /* chained in single linked list */
  int num_funcs;                             /* number of functions this block contains */
  PEXITFUNC funcs[EXITFUNCBLOCK_FUNCCOUNT];  /* pointers to functions */
} EXITFUNCBLOCK, *PEXITFUNCBLOCK;

/* The global configuration data */
GLOBAL_CONFIG Gconfig;

static PEXITFUNCBLOCK exitfuncs = NULL;   /* pointer to head of exit function chain */

static void run_exit_funcs(void)
{
  int i;
  PEXITFUNCBLOCK p;

  while (exitfuncs)
  {
    p = exitfuncs;
    exitfuncs = p->next;
    for (i = p->num_funcs - 1; i >= 0; i--)
    {
      /* execute functions in LIFO order */
      ASSERT(p->funcs[i]);
      (*(p->funcs[i]))();
    }
    free(p);
  }
}

static void init_defaults(void)
{
  memset(&Gconfig, 0, sizeof(GLOBAL_CONFIG));
  Gconfig.framebuffer_device = "/dev/fb1";
  Gconfig.touchscreen_device = "/dev/input/touchscreen";
  Gconfig.python_loc = "/usr/bin/python3";
  Gconfig.button_debounce = 100;
  Gconfig.sys_mq_length = 64;
  Gconfig.click_time = 500;
  Gconfig.click_radius = 2;
}

static HRESULT parse_cmdline(int argc, char *argv[], GLOBAL_CONFIG *parsed)
{
  int c;
  PSTR pstr;
  PPCSTR pargs;
  BOOL help = FALSE;

  memset(parsed, 0, sizeof(GLOBAL_CONFIG));
  for (;;)
  {
    c = getopt_long(argc, argv, short_options, long_options, NULL);
    if (c==-1)
      break;
    switch (c)
    {
      case 'F':   /* frame buffer device name */
        pstr = strdup(optarg);
        if (!pstr)
        {
          Log(LERROR, "Out of memory in parse_cmdline");
          return E_OUTOFMEMORY;
        }
        if (parsed->framebuffer_device)
          free((PVOID)(parsed->framebuffer_device));
        parsed->framebuffer_device = pstr;
        break;

      case 'h':   /* show help */
        help = TRUE;
        break;

      case 'T':   /* touchscreen device name */
        pstr = strdup(optarg);
        if (!pstr)
        {
          Log(LERROR, "Out of memory in parse_cmdline");
          return E_OUTOFMEMORY;
        }
        if (parsed->touchscreen_device)
          free((PVOID)(parsed->touchscreen_device));
        parsed->touchscreen_device = pstr;
        break;

      default:
        fprintf(stderr, "%s: unexpected option -%c\n", argv[0], c);
        return E_UNEXPECTED;
    }
  }
  if (help)
  {
    fputs(helptext, stdout);
    return S_FALSE;
  }

  if (optind < argc)
  {
    pstr = realpath(argv[optind], NULL); /* implicit strdup */
    if (!pstr)
    {
      Log(LERROR, "Out of memory in parse_cmdline");
      return E_OUTOFMEMORY;
    }
    if (access(pstr, R_OK))
    {
      fprintf(stderr, "%s: script %s not found\n", argv[0], pstr);
      return UPIWIN_E_INVALIDSCRIPT;
    }
    parsed->script_name = pstr;
    if (++optind < argc)
    {
      parsed->script_arg_count = argc - optind;
      pargs = (PPCSTR)malloc(sizeof(PCSTR) * parsed->script_arg_count);
      if (!pargs)
      {
        Log(LERROR, "Out of memory in parse_cmdline");
        return E_OUTOFMEMORY;
      }
      for (c = 0; c < parsed->script_arg_count; c++)
      {
        pargs[c] = strdup(argv[optind++]);
        if (!(pargs[c]))
        {
          Log(LERROR, "Out of memory in parse_cmdline");
          return E_OUTOFMEMORY;
        }
      }
      parsed->script_args = pargs;
    }
  }
  else
  {
    fprintf(stderr, "%s: no script specified\n", argv[0]);
    return UPIWIN_E_NOSCRIPT;
  }

  return S_OK;
}

static void overlay_config(GLOBAL_CONFIG *p)
{
  if (p->framebuffer_device)
    Gconfig.framebuffer_device = p->framebuffer_device;
  if (p->touchscreen_device)
    Gconfig.touchscreen_device = p->touchscreen_device;
  /* always overlay the script name and arguments */
  Gconfig.script_name = p->script_name;
  Gconfig.script_arg_count = p->script_arg_count;
  Gconfig.script_args = p->script_args;
}

HRESULT Config_setup(int argc, char *argv[])
{
  HRESULT hr;
  GLOBAL_CONFIG from_commandline;

  if (geteuid() != 0)
  {
    Log(LFATAL, "upiwin must be run with root privileges");
    return E_ACCESSDENIED;
  }

  if (atexit(run_exit_funcs))
  {
    Log(LFATAL, "Unable to set up exit function mechanism");
    return E_FAIL;
  }

  /* set defaults */
  init_defaults();

  /* evaluate command line */
  hr = parse_cmdline(argc, argv, &from_commandline);
  if (hr != S_OK)
    return hr;

  /* command line overrides everything */
  overlay_config(&from_commandline);
  return S_OK;
}

HRESULT Config_exitfunc(PEXITFUNC pfn)
{
  PEXITFUNCBLOCK p;

  if (!exitfuncs || (exitfuncs->num_funcs == EXITFUNCBLOCK_FUNCCOUNT))
  {
    p = (PEXITFUNCBLOCK)malloc(sizeof(EXITFUNCBLOCK));
    if (!p)
    {
      Log(LERROR, "unable to allocate another exit function block");
      return E_OUTOFMEMORY;
    }
    p->next = exitfuncs;
    p->num_funcs = 0;
    exitfuncs = p;
  }
  exitfuncs->funcs[exitfuncs->num_funcs++] = pfn;
  return S_OK;
}
