#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "args.h"

static char **expand_argv(int argc, char *argv[], int new_argc)
{
  char **new_argv;

  if ((argv != NULL) && (new_argc <= argc))
    return argv;

  new_argv = malloc(sizeof(char *) * (new_argc + 1));
  if (new_argv == NULL) {
    if (argv != NULL)
      free_argv(argv);
    return NULL;
  }

  if (argv != NULL) {
    memcpy(new_argv, argv, sizeof(char *) * argc);
    free(argv);
  }
  new_argv[argc] = NULL;

  return new_argv;
}

int get_argc(char *argv[])
{
  int argc = 0;
  while (argv[argc])
    argc++;
  return argc;
}

static int _noseparator(int c)
{
  return 0;
}

static int _isspace(int c)
{
  return isspace(c);
}

static int _ispunc(int c)
{
  return strchr(",:;", c) != NULL ? 1 : 0;
}

static int _isequal(int c)
{
  return strchr("=", c) != NULL ? 1 : 0;
}

static char **_make_argv(char **argv, char *line, int (*isseparator)(int c))
{
  unsigned char *p, *arg, *word_start, *word_end;
  int argc, len;

  if (line == NULL)
    return NULL;

  p = (unsigned char *)line;

  if (argv == NULL)
    argv = expand_argv(0, NULL, 0);
  if (argv == NULL)
    return NULL;

  argc = get_argc(argv);

  if (isseparator == NULL)
    isseparator = _noseparator;

  while (1) {
    while (*p && isseparator(*p))
      p++;
    word_start = p;
    while (*p && !isseparator(*p))
      p++;
    word_end = p;

    if (word_start == word_end)
      break;

    argv = expand_argv(argc, argv, argc + 1);
    if (argv == NULL)
      break;

    len = word_end - word_start;
    arg = malloc(len + 1);
    if (arg == NULL)
      break;
    strncpy((char *)arg, (char *)word_start, len);
    arg[len] = '\0';

    argv[argc++] = (char *)arg;
  }

  if (argv != NULL)
    argv[argc] = NULL;

  return argv;
}

char **make_argv(char *line)
{
  return _make_argv(NULL, line, _isspace);
}

char **make_argv_punc(char *line)
{
  return _make_argv(NULL, line, _ispunc);
}

char **make_argv_equal(char *line)
{
  return _make_argv(NULL, line, _isequal);
}

char **add_argv(char *argv[], char *arg)
{
  int argc;

  if (argv == NULL)
    argv = expand_argv(0, NULL, 0);
  if (argv == NULL)
    return NULL;

  argc = get_argc(argv);

  argv = expand_argv(argc, argv, argc + 1);
  if (argv == NULL)
    return NULL;

  arg = strdup(arg);
  if (arg != NULL) {
    argv[argc++] = (char *)arg;
    argv[argc] = NULL;
  }

  return argv;
}

char *free_argv(char *argv[])
{
  int i;

  if (argv) {
    for (i = 0; argv[i]; i++) {
      free(argv[i]);
    }
    free(argv);
  }

  return NULL;
}

int search_argv(char *argv[], char *arg)
{
  int argc, i;

  argc = get_argc(argv);

  for (i = 0; i < argc; i++) {
    if (!strcmp(argv[i], arg))
      return i;
  }

  return -1;
}

int rsearch_argv(char *argv[], char *arg)
{
  int argc, i;

  argc = get_argc(argv);

  for (i = argc - 1; i >= 0; i--) {
    if (!strcmp(argv[i], arg))
      return i;
  }

  return -1;
}

char **insert_argv(char *argv[], int pos, char *arg)
{
  int argc, i;

  argc = get_argc(argv);

  if (pos > argc)
    pos = argc;
  else if (pos < 0)
    pos = argc + pos;

  argv = add_argv(argv, arg);
  if (argv == NULL)
    return NULL;

  argc = get_argc(argv);

  arg = argv[argc - 1];
  
  for (i = argc - 1; i > pos; i--) {
    argv[i] = argv[i - 1];
  }
  argv[pos] = arg;

  return argv;
}

char **extract_argv(char *argv[], int pos, int num)
{
  int argc, i;
  char **extract;

  argc = get_argc(argv);

  if (pos > argc)
    pos = argc;
  else if (pos < 0)
    pos = argc + pos;

  if ((num < 0) || (pos + num > argc))
    num = argc - pos;

  extract = malloc(sizeof(char *) * (argc - pos + 1));
  if (extract == NULL)
    return NULL;

  for (i = 0; i < num; i++) {
    extract[i] = argv[pos + i];
  }
  extract[i] = NULL;

  argc -= num;

  for (i = 0; i < argc - pos; i++) {
    argv[pos + i] = argv[pos + num + i];
  }
  argv[pos + i] = NULL;

  return extract;
}

int delete_argv(char *argv[], int pos, int num)
{
  char **extract;

  extract = extract_argv(argv, pos, num);
  free_argv(extract);

  return get_argc(argv);
}
