/* todo: make ; splitting work better
 *
 * argvs - argv - shell
 *
 * a small method/dispatcher for C functions following the int main (int argc,
 * char **argv) pattern for receiving it's arguments.
 *
 * It is written in such a way that it should be easily repurposable for
 * various purposes. And possibly easy to extend if one wants to add more
 * advacned behavior.
 */

#include "config.h"

#include <stdio.h>

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "argvs.h"

#if HAVE_MRG
#define MRG_PRINTF 1
#endif


#ifdef MRG_PRINTF
#include <mrg.h>
#include <mrg-string.h>
extern Mrg *mrg;
extern MrgList *scrollback;
extern int use_ui;


#define printf(foo...) \
   do{ MrgString *str = mrg_string_new_printf (foo);\
       if (use_ui) {\
       MrgString *line = mrg_string_new (scrollback?scrollback->data:"");\
       for (char *p= str->str; *p; p++) { \
         if (*p == '\n') { \
           char *old = scrollback ? scrollback->data : NULL;\
           if (old)\
           {\
             mrg_list_remove (&scrollback, old);\
           }\
           mrg_list_prepend (&scrollback, strdup (line->str));\
           mrg_list_prepend (&scrollback, strdup (""));\
           mrg_string_set (line, "");\
         } else { \
           char *old = scrollback ? scrollback->data : NULL;\
           mrg_string_append_byte (line, *p);\
           if (old)\
           {\
             mrg_list_remove (&scrollback, old);\
           }\
           mrg_list_prepend (&scrollback, strdup (line->str));\
         } \
       } \
       mrg_string_free (line, 1);\
       }\
       else \
       { fprintf (stdout, "%s", str->str);\
       }\
       mrg_string_free (str, 1);\
   }while(0)
#endif



typedef struct _CmdIterator CmdIterator;
typedef struct _CmdEntry CmdEntry;

struct _CmdEntry {
  int (*fun)(COMMAND_ARGS);
  char *name;
  int         req_args;
  char *args;
  char *help;
};

static CmdEntry commands[] = {{0,}};
static CmdEntry dynamic_commands[512] = {{0,}};
static int dynamic_count = 0;

void
argvs_add (int (*fun)(COMMAND_ARGS),
           const char *name,
           int         required_arguments,
           const char *argument_help,
           const char *help)
{
  CmdEntry *entry = &dynamic_commands[dynamic_count++];
  entry->fun = fun;
  if (name)
    entry->name = strdup (name);
  entry->req_args = required_arguments;
  if (argument_help)
    entry->args = strdup (argument_help);
  if (help)
    entry->help = strdup (help);
}


static CmdIterator   *cmd_iterator_new (void *foo);
static CmdEntry      *cmd_iterator_next (CmdIterator *i);
static void           cmd_iterator_stop (CmdIterator *i);

static void
argvs_free (CmdEntry *e)
{
  free (e->help);
  free (e->args);
  free (e->name);
}

static void
argvs_cleanup (void)
{
  CmdIterator *i = cmd_iterator_new (NULL);
  CmdEntry *e;
  while ((e = cmd_iterator_next (i)))
    argvs_free (e);
}

#if HAVE_MRG
#include "argvs-commands.inc"
#else
  int cmd_source (COMMAND_ARGS);
  int cmd_argvs (COMMAND_ARGS);
  int cmd_aa_help (COMMAND_ARGS);

/* dummy to keep things working even without argvs when no ui is built */
void argvs_register(void) {
}
#endif
/* cmds.inc is supposed to be created by your build system by using the
 * following shell-script to run with your .c files as argument for
 * collecting command definitions.
 */

#if 0
#!/bin/sh

echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"
echo "/* this file is autogenerated by $0 */"
echo "/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */"

# create function declarations for each item
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
  sed -e 's/^int */int /' -e 's#(COMMAND_ARGS).*#(COMMAND_ARGS);#'

# create a function that registers all items found
echo "void argvs_register(void) {" 
cat $* | grep COMMAND_ARGS | grep ^int | sort | \
  sed -e 's/^int */ argvs_add (/ ' -e 's#(COMMAND_ARGS). */\*#, #' -e 's#\*/$#);#'
echo "};"

#endif

struct _CmdIterator {
  int       i;
  int       j;
  CmdEntry  internal;
  char     *name;
  char     *req_args;
  char     *help;
};

static CmdIterator *cmd_iterator_new (void *foo)
{
  CmdIterator *i = malloc (sizeof (CmdIterator));
  static int first_run = 1;
  if (first_run)
    {
      /* register the build-system harvested items */
      argvs_register ();
      first_run = 0;

      /* free up allocations, so valgrind will not be angry */
      atexit (argvs_cleanup);
    }

  /* at the moment, creating the iterator fills it up with possible sources,
   * this could be done half-lazily,.. adding references to unused sources
   * and expanding each as it is consumed, this allows stopping earlier with
   * less resource use.
   */
  i->name = NULL;

  i->j = -1;
  i->i = -1;
  return i;
}

static void cmd_iterator_stop (CmdIterator *i)
{
  if (i->name)
    free (i->name);
  i->name = NULL;
  free (i);
}

static CmdEntry *cmd_iterator_next (CmdIterator *i)
{
  if (i->name)
    free (i->name);
  i->name = NULL;

  /* first iterate commands from db, then fixed list - this
   * order permits overriding internal commands like forth does */
  i->j++;
  if (i->j < dynamic_count)
    return &dynamic_commands[i->j];

  /* first iterate commands from db, then fixed list - this
   * order permits overriding internal commands like forth does */
  i->i++;
  if ((unsigned)i->i < sizeof (commands) / sizeof (commands[0]))
    if (commands[i->i].fun)
      return &commands[i->i];
  cmd_iterator_stop (i);
  return NULL;
}

/******* the rest should be using the iteration API for access
 * to commands and not poke around in internals   ************/

static char *string_chop_head (char *orig) /* return pointer to reset after arg */
{
  int j=0;
  int eat=0; /* number of chars to eat at start */
  
  if(orig)
    {
      int got_more;
      char *o = orig;
      while(o[j] == ' ')
        {j++;eat++;}
      
      if (o[j]=='"')
        {
          eat++;j++;
          while(o[j] != '"' &&
                o[j] != 0)
            j++;
          o[j]='\0';
          j++;
        }
      else if (o[j]=='\'')
        {
          eat++;j++;
          while(o[j] != '\'' &&
                o[j] != 0)
            j++;
          o[j]='\0';
          j++;
        }
      else
        {
          while(o[j] != ' ' &&
                o[j] != 0 &&
                o[j] != ';')
            j++;
        }

      if (o[j] == 0 ||
          o[j] == ';')
        got_more = 0;
      else
        got_more = 1;
      o[j]=0; /* XXX: this is where foo;bar won't work but foo ;bar works*/

      if(eat)
       {
         int k;
         for (k=0; k<j-eat; k++)
           orig[k] = orig[k+eat];
       }
      if (got_more)
        return &orig[j+1];
    }
  return NULL;
}


int argvs_eval_argv (char **cargv, int cargc);
int argvs_eval_argv (char **cargv, int cargc)
{
  CmdIterator *ci = cmd_iterator_new (NULL);
  CmdEntry *command;
  int ret = -1;

  if (cargc <= 0)
  {
    for (char **arg = cargv; *arg; arg++, cargc++);
  }

  while ((command = cmd_iterator_next (ci)))
    if (!strcmp (cargv[0], command->name))
      {
        if (command->req_args > cargc - 1)
          {
            printf ("command '%s' needs %i args, %i given\n",
                command->name, command->req_args, cargc - 1);
          }
        else
          ret = command->fun (cargc, cargv, NULL);
        if (ret)
          printf ("%s returned: %i\n", command->name, ret);
        cmd_iterator_stop (ci);
        break;
      }
  return ret;
}

int argvs_eval (const char *cmdline)
{
  char *cargv[32];
  int   cargc;
  char *rest, *copy;
  int ret = 0;

  copy = calloc (strlen (cmdline)+2, 1);
  strcpy (copy, cmdline);
  rest = copy;

  /* XXX: detect ; and pause ... we could have a label that we go back
   * to and evaluate again when one is encountered... t c*/
  do {
    cargc = 0;
    while (rest && cargc < 30 && rest[0] != ';')
      {
        cargv[cargc++] = rest;
        rest = string_chop_head (rest);
      }
    cargv[cargc] = NULL;


    if (cargv[0])
      {
        CmdIterator *ci = cmd_iterator_new (NULL);
        CmdEntry *command;
        int found = 0;
        while ((command = cmd_iterator_next (ci)))
          if (!strcmp (cargv[0], command->name))
            {
              if (command->req_args > cargc - 1)
                {
                  printf ("command '%s' needs %i args, %i given\n",
                      command->name, command->req_args, cargc - 1);
                  ret = -1;
                }
              else
                ret = command->fun (cargc, cargv, NULL);
              if (ret)
                printf ("%s returned: %i\n", command->name, ret);
              cmd_iterator_stop (ci);
              found = 1;
              break;
              goto b;
            }
b:
        if (cargv[0][0] && !found)
          {
            printf ("unknown command '%s' use ? for a list of registered commands\n", cargv[0]);
          }
      }
    if (rest && rest[0]==';')
      {
        rest++;
        while (rest[0] == ' ')rest++;
      }
  } while (rest && rest[0]);

  free (copy);
  return ret;
}

//#include "common.h"

static int echo_on = 0;
static char  prompt[60] = "> ";

void update_prompt (void);
void update_prompt (void)
{
  {
    snprintf (prompt, 59, "> ");
  }
}

int argvs_source (const char *path)
{
  char commandline[1024]="";
  FILE *f = fopen(path, "r");
  if (!f)
    return -1;


  for (fgets(commandline,1023,f);
       fgets(commandline,1023,f);)
    {
      commandline[strlen(commandline)-1]=0; /* remove newline */
      if (echo_on && commandline[0])
        {
          update_prompt ();
          printf ("%s", prompt);
          printf ("%s\n", commandline);
        }
      argvs_eval (commandline);
    }
  fclose (f);
  return 0;
}

int cmd_source (COMMAND_ARGS) /* ".", 1, "<path>", "sources script at path"*/
{
  return argvs_source (argv[1]);
}

#ifdef USE_LINENOISE
static int str_has_prefix (const char *string, const char *buf)
{
  return !strncmp (string, buf, strlen (buf));
}
static void linenoise_completion (const char *buf, linenoiseCompletions *lc)
{
  CmdIterator *ci = cmd_iterator_new (NULL);
  CmdEntry *command;

  while ((command = cmd_iterator_next (ci)))
    {
      if (str_has_prefix (command->name, buf))
        linenoiseAddCompletion (lc, (void*)command->name);
    }

  /* add completions for some central commands,. like help.. */
}
#endif

int cmd_argvs (COMMAND_ARGS) /* "argvs", 0, "<script | -c <command> >", "the minimalistic shell with int argc, char **argv semantics you now use" */
{    

  if (argv[1] && !strcmp(argv[1], "-c"))
    {
      argvs_eval (argv[2]);
    }
  else if (argc > 1)
    {
      CmdIterator *ci = cmd_iterator_new (NULL);
      CmdEntry *command;

      while ((command = cmd_iterator_next (ci)))
        {
          if (!strcmp (argv[1], command->name))
            {
              if (command->req_args > argc - 2)
                {
                  printf ("command '%s' needs %i args, %i given\n",
                      command->name, command->req_args, argc - 2);
                  cmd_iterator_stop (ci);
                  return -1;
                }
              cmd_iterator_stop (ci);
              return command->fun (argc-1, argv+1, NULL);
            }
        }
      /* didn't match a command, gamble that it is a file instead */
      {
        cmd_source (argc, argv, NULL);
      }
    }
  else
    {
#ifdef USE_LINENOISE
      char *commandline;
      
      linenoiseSetCompletionCallback(linenoise_completion);
      update_prompt ();
      while ( (commandline = linenoise(prompt))!=NULL &&
               strcmp(commandline, "q") &&
               strcmp(commandline, "quit"))
      {
        linenoiseHistoryAdd (commandline);
        argvs_eval (commandline);
        free (commandline);
        update_prompt ();
      }
#else
      char commandline[1024];
      printf ("> ");
      fgets (commandline, 1023, stdin);
      for (;
           strcmp (commandline, "quit\n") && strcmp (commandline,"q\n"); 
           fgets (commandline, 1023, stdin))
        {
          commandline[strlen(commandline)-1]=0; /* remove newline */
          argvs_eval (commandline);
          printf ("> ");
        }
#endif
    }
  return 0;
}

int argvs_command_exist (const char *string)
{
  CmdIterator *ci = cmd_iterator_new (NULL);
  CmdEntry *command;
  int found = 0;

  while ((command = cmd_iterator_next (ci)))
    if (!strcmp (command->name, string))
      found = 1;
  return found;
}

int cmd_aa_help (COMMAND_ARGS) /* "?", 0, "[command]", "get's help about a coloritto command" */
{
  if (argv[1])
    {
      CmdIterator *ci = cmd_iterator_new (NULL);
      CmdEntry *command;

      while ((command = cmd_iterator_next (ci)))
        {
          if (!strcmp (argv[1], "?") ||
              !strcmp (command->name, argv[1]))
          {
            printf ("%s %s\n", command->name, command->args);
            printf ("  %s\n\n", command->help);
          }
        }
    }
  else
    {
      CmdIterator *ci = cmd_iterator_new (NULL);
      CmdEntry *command;
      printf ("Available commands:\n");


      while ((command = cmd_iterator_next (ci)))
        printf ("%s ", command->name);
      printf ("\n\n");
      printf ("Enter ? <commandname>    for details on a command\n");
      printf ("      ? ?                for details on all commands\n");
    }
  return 0;
}

#ifdef COMPILE_TEST
int main (int argc, char **argv)
{
  /* just transfer control to the shell */
  cmd_argvs (argc, argv, NULL);
  return 0;
}
#endif
