#include "argparser.h"
#include "aliases.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define INITIAL_SIZE 30

struct argparser {
  arg_t *args;
  u64 size;
  u64 args_count;
  u64 positional_count;
  u64 named_count;
  u64 flag_count;
};

argparser_t *create_parser(void) {
  argparser_t *parser = (argparser_t *)malloc(sizeof(argparser_t));

  if (!parser) {
    return NULL;
  }

  parser->args = (arg_t *)malloc(INITIAL_SIZE * sizeof(arg_t));
  if (!(parser->args)) {
    free(parser);

    return NULL;
  }

  parser->size = INITIAL_SIZE;
  parser->args_count = parser->positional_count = parser->named_count =
      parser->flag_count = 0;

  add_argument(parser, (arg_t){ARG_TYPE_FLAG, "h", "help"});

  return parser;
}

void destroy_parser(argparser_t **parser) {
  if (*parser) {
    if ((*parser)->args) {
      free((*parser)->args);
    }

    free(*parser);

    *parser = NULL;
  }
}

void add_argument(argparser_t *parser, arg_t arg) {
  bool valid_arg_type = false;

  switch (arg.type) {
  case ARG_TYPE_POSITIONAL:
  case ARG_TYPE_NAMED:
  case ARG_TYPE_FLAG:
    valid_arg_type = true;
    break;
  default:
    valid_arg_type = false;
  }

  if (!valid_arg_type) {
    return;
  }

  if (parser->args_count + 1 == parser->size) {
    arg_t *new_args =
        (arg_t *)malloc((parser->size + INITIAL_SIZE) * sizeof(arg_t));

    if (!new_args) {
      return;
    }

    memcpy(new_args, parser->args, parser->args_count);

    free(parser->args);

    parser->args = new_args;
  }

  parser->args[(parser->args_count)++] = arg;

  switch (arg.type) {
  case ARG_TYPE_POSITIONAL:
    ++(parser->positional_count);
    break;
  case ARG_TYPE_NAMED:
    ++(parser->named_count);
    break;
  case ARG_TYPE_FLAG:
    ++(parser->flag_count);
    break;
  default:
    break;
  }
}

void print_usage(argparser_t *parser) {
  arg_t positional[parser->positional_count];
  memset(positional, 0, parser->positional_count);
  u64 positional_index = 0;

  arg_t named[parser->named_count];
  memset(named, 0, parser->named_count);
  u64 named_index = 0;

  arg_t flag[parser->flag_count];
  memset(flag, 0, parser->flag_count);
  u64 flag_index = 0;

  for (u64 i = 0; i < parser->args_count; ++i) {
    const arg_t *arg = &(parser->args[i]);

    switch (arg->type) {
    case ARG_TYPE_POSITIONAL:
      positional[positional_index++] = *arg;
      break;
    case ARG_TYPE_NAMED:
      named[named_index++] = *arg;
      break;
    case ARG_TYPE_FLAG:
      flag[flag_index++] = *arg;
      break;
    default:
      break;
    }
  }

  char usage_line[2048] = {0};
  char positional_args[2048] = {0};
  char named_args[2048] = {0};
  char flag_args[2048] = {0};

  sprintf(usage_line, "Usage: program ");

  if (positional_index > 0) {
    sprintf(positional_args, "Positional arguments:\n");
  }

  if (named_index > 0) {
    sprintf(named_args, "Named arguments:\n");
  }

  if (flag_index > 0) {
    sprintf(flag_args, "Flag arguments:\n");
  }

  for (u64 i = 0; i < flag_index; ++i) {
    const arg_t *arg = &(flag[i]);

    sprintf(&(usage_line[strlen(usage_line)]), "[");
    sprintf(&(flag_args[strlen(flag_args)]), "\t");

    if (strlen(arg->short_name) > 0) {
      sprintf(&(usage_line[strlen(usage_line)]), "-%s | ", arg->short_name);
      sprintf(&(flag_args[strlen(flag_args)]), "-%s, ", arg->short_name);
    }

    sprintf(&(usage_line[strlen(usage_line)]), "--%s] ", arg->long_name);
    sprintf(&(flag_args[strlen(flag_args)]), "--%s\n", arg->long_name);
  }

  for (u64 i = 0; i < named_index; ++i) {
    const arg_t *arg = &(named[i]);

    sprintf(&(usage_line[strlen(usage_line)]), "[");
    sprintf(&(named_args[strlen(named_args)]), "\t");

    if (strlen(arg->short_name) > 0) {
      sprintf(&(usage_line[strlen(usage_line)]), "-%s | ", arg->short_name);
      sprintf(&(named_args[strlen(named_args)]), "-%s, ", arg->short_name);
    }

    u64 name_length = strlen(arg->long_name);

    char capitalised[name_length];
    memset(capitalised, 0, name_length);

    strcpy(capitalised, arg->long_name);

    for (u64 i = 0; i < name_length; ++i) {
      capitalised[i] = toupper(capitalised[i]);
    }

    sprintf(&(usage_line[strlen(usage_line)]), "--%s %s] ", arg->long_name,
            capitalised);
    sprintf(&(named_args[strlen(named_args)]), "--%s\n", arg->long_name);
  }

  for (u64 i = 0; i < positional_index; ++i) {
    const arg_t *arg = &(named[i]);

    sprintf(&(positional_args[strlen(positional_args)]), "\t");

    u64 name_length = strlen(arg->long_name);

    char capitalised[name_length];
    memset(capitalised, 0, name_length);

    strcpy(capitalised, arg->long_name);

    for (u64 i = 0; i < name_length; ++i) {
      capitalised[i] = toupper(capitalised[i]);
    }

    sprintf(&(usage_line[strlen(usage_line)]), "%s ", capitalised);
    sprintf(&(positional_args[strlen(positional_args)]), "%s\n", capitalised);
  }

  printf("%s\n\n", usage_line);

  if (strlen(positional_args) > 0) {
    printf("%s\n", positional_args);
  }

  if (strlen(named_args) > 0) {
    printf("%s\n", named_args);
  }

  if (strlen(flag_args) > 0) {
    printf("%s\n", flag_args);
  }
}