#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// #include <tuple.h>

/*
#include "vendor/vector.h"
#include <assert.h>
#include <libgen.h>
#include <stdbool.h>
#include <sys/stat.h>
*/

/*
static void print_usage(FILE *stream, char *given_name) {
  char *name = "remembering";
  if (given_name && given_name[0]) {
    name = given_name;
  }
  fprintf(stream, "Usage:  %s -p PROFILE -c 'COMMAND'\n", name);
}

static void print_help(FILE *stream) {
  const char *help_text =
      "\n"
      "Options:\n"
      " -p PROFILE      profile to be used for gathering and storing data\n"
      " -c 'COMMAND'    command to be run, reading from STDIN, writing to "
      "STDOUT\n"
      " -h              show this help\n"
      " -V              print program version\n"
      "See manpages for more information.\n";
  fprintf(stream, "%s", help_text);
}

static void print_missing(FILE *stream, char *text) {
  fprintf(stream, "Missing option: %s\n", text);
}

static struct Tuple *get_options(int argc, char *argv[]) {
  for (int i = 0; i < argc; i++) {
    if (!strcmp(argv[i], "--help")) {
      print_usage(stdout, argv[0]);
      print_help(stdout);
      exit(0);
    }
  }

  char *profilearg = NULL;
  char *commandarg = NULL;
  int option;
  while ((option = getopt(argc, argv, "c:p:hV")) != -1) {
    switch (option) {
    case 'c':
      commandarg = optarg;
      break;
    case 'p':
      profilearg = optarg;
      break;
    case 'h':
      print_usage(stdout, argv[0]);
      print_help(stdout);
      exit(0);
    case 'V':
      fprintf(stdout, "remembering-%s %s\n", VERSION, DATE);
      exit(0);
    }
  }

  if (commandarg == NULL) {
    print_missing(stderr, "-c 'COMMAND'");
    print_usage(stderr, argv[0]);
    exit(2);
  }

  if (profilearg == NULL) {
    print_missing(stderr, "-p 'PROFILE'");
    print_usage(stderr, argv[0]);
    exit(2);
  }

  struct Tuple *options = tuple_new();
  if (!options) {
    return NULL;
  }

  options->first = malloc(strlen(commandarg) + 1);
  options->second = malloc(strlen(profilearg) + 1);
  if (!options->first || !options->second) {
    tuple_free(options);
    return NULL;
  }
  strcpy(options->first, commandarg);
  strcpy(options->second, profilearg);

  return options;
}
*/

/*

static char *expand_profile_name(char *profile_name) {
  char *prefix = NULL;
  char *env_prefix = getenv("XDG_DATA_HOME");
  if (env_prefix) {
    prefix = strdup(env_prefix);
    if (!prefix) {
      return NULL;
    }
  } else {
    char *home = getenv("HOME");
    char *path = "/.local/share/remembering";
    prefix = malloc(strlen(home) + strlen(path) + 1);
    if (!prefix) {
      return NULL;
    }
    strcpy(prefix, home);
    strcat(prefix, path);
  }
  char *separator = "/";
  char *expanded_profile =
      malloc(strlen(prefix) + strlen(separator) + strlen(profile_name) + 1);
  if (!expanded_profile) {
    free(prefix);
    return NULL;
  }
  strcpy(expanded_profile, prefix);
  strcat(expanded_profile, separator);
  strcat(expanded_profile, profile_name);

  free(prefix);

  return expanded_profile;
}

#ifdef TEST
static char *rand_str(size_t size) {
  static const char CHARSET[] = "0123456789";
  "abcdefghijklmnopqrstuvwxyz"
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  static const int CEILING = sizeof CHARSET - 1;
  static const int DEFAULT_LENGTH = 24;

  size = size == 0 ? DEFAULT_LENGTH : size;

  char *str = malloc(size + 1);
  assert(str);

  size_t cur = size;
  while (cur--) {
    str[cur] = CHARSET[rand() % CEILING];
  }
  str[size] = '\0';
  return str;
}

const char *set_test_home(const char *new_home) {
  size_t size = _PC_PATH_MAX == -1 ? 4096 : _PC_PATH_MAX;
  char *buf = malloc(size + 1);
  assert(buf);
  char *cwd = getcwd(buf, size);
  assert(buf);
  const char *suffix = "/tests/test-profiles/";
  char *home = malloc(strlen(cwd) + strlen(suffix) + 1);
  assert(home);
  strcpy(home, cwd);
  strcat(home, suffix);
  // free(buf);
  // free(cwd);
}

static void test_expand_profile_name() {
  {
    testing("expanding profile name without setting $XDG_DATA_HOME");
    char *r1 = rand_str(0);
    printf("\n\nrand_str() is: %s----\n", r1);
    char *r2 = rand_str(10);
    printf("\n\nrand_str() is: %s----\n", r2);
    free(r1);
    free(r2);
    // exit(1);
    // const char *new_home = random_string("test-expand-profile-name-");
    // const char *previous_home = set_test_home(new_home);

    // int ret = setenv("HOME", previous_home, 1);
    // assert(ret == 0);
    test_ok();
  }
}
#endif

static void mkdir_p(char *dir, mode_t mode) { mkdir(dir, mode); }

#ifdef TEST
static void test_mkdir_p() {
  {
    testing("xablau");
    test_ok();
  }
}
#endif

static void ensure_profile(char *profile) {
  char *prefix = dirname(profile);
  struct stat sb;
  if (!(stat(prefix, &sb) == 0 && S_ISDIR(sb.st_mode))) {
    mkdir_p(prefix, 0755);
  }
  // FILE *f = fopen(profile, "w+");
  // fclose(f);
}

#ifdef TEST
static void test_ensure_profile() {
  {
    testing("xablau");
    test_ok();
  }
}
#endif

static const char RANKING_DELIMITER = ':';

struct Rankings {
  struct Vector *values;
  struct Vector *ranks;
};

static struct Rankings *rankings_new() {
  struct Rankings *r = malloc(sizeof(struct Rankings));
  if (!r) {
    return NULL;
  }
  r->values = vector_new();
  if (!r->values) {
    free(r);
    return NULL;
  }
  r->ranks = vector_new();
  if (!r->ranks) {
    vector_free(r->values);
    free(r);
    return NULL;
  }
  return r;
}

static void rankings_free(struct Rankings *r) {
  vector_free(r->values);
  vector_free(r->ranks);
  free(r);
}

static bool rankings_insert(struct Rankings *r, char *value, int *rank) {
  return vector_push_back(r->values, value) && vector_push_back(r->ranks, rank);
}

#ifdef TEST
static void test_rankings() {
  {
    testing("struct Rankings grows values and ranks at the same rate");
    struct Rankings *r = rankings_new();
    int some_limit = 123;
    for (int i = 0; i < some_limit; i++) {
      char *s = strdup("some text");

      int *p = malloc(sizeof(int));
      assert(p);
      *p = i;

      rankings_insert(r, s, p);
    }

    assert(r->values->count == r->ranks->count);
    assert(r->values->size == r->ranks->size);

    rankings_free(r);
    test_ok();
  }

  {
    testing("an empty Rankings doesn't leak");
    struct Rankings *r = rankings_new();
    rankings_free(r);
    test_ok();
  }
}
#endif

/ *

struct GetlineParams {
  char *line;
  size_t len;
  ssize_t read;
  FILE *stream;
};

int parse_ranked_line(char *entry, struct Tuple *t) {
  char *value_substr = strchr(entry, RANKING_DELIMITER);
  if (value_substr == NULL) {
    fprintf(stderr, "Missing delimiter ('%c') in line:\n%s\n",
            RANKING_DELIMITER, entry);
    return 1;
  }

  int ranking_chars_count = value_substr - entry;
  char *ranking_chars = malloc(ranking_chars_count + 1);
  strncpy(ranking_chars, entry, ranking_chars_count);
  int *ranking = malloc(sizeof(int)); // how to handle fail to parse?
  *ranking = atoi(ranking_chars);
  free(ranking_chars);

  value_substr++; // drop included delimiter
  char *value_copy = malloc(strlen(value_substr) + 1);
  strcpy(value_copy, entry);

  t->first = value_copy;
  t->second = ranking;

  return 0;
}

int get_stdin(struct GetlineParams *s_params, struct GetlineParams *p_params,
              struct Rankings *p_rankings) {
  s_params->read = getline(&s_params->line, &s_params->len, s_params->stream);
  if (s_params->read == -1) {
    while ((p_params->read = getline(&p_params->line, &p_params->len,
                                     p_params->stream)) != -1) {
      struct Tuple t;
      parse_ranked_line(p_params->line, &t);
      rankings_insert(p_rankings, t.first, t.second);
    }
    return 1;
  }
  return 0;
}

int get_profile(struct GetlineParams *s_params, struct GetlineParams *p_params,
                struct Rankings *s_rankings, struct Rankings *p_rankings) {
  p_params->read = getline(&p_params->line, &p_params->len, p_params->stream);
  if (p_params->read == -1) {
    while ((s_params->read = getline(&s_params->line, &s_params->len,
                                     s_params->stream)) != -1) {
      char *value_copy = malloc(strlen(s_params->line) + 1);
      strcpy(value_copy, s_params->line);

      int *rank = malloc(sizeof(int));
      *rank = 0;
      rankings_insert(s_rankings, value_copy, rank);
      rankings_insert(p_rankings, value_copy, rank);
    }
    return 1;
  }
  return 0;
}

int merge_stdin_with_profile(char *profile_name, struct Rankings *s_rankings,
                             struct Rankings *p_rankings) {
  FILE *profile = fopen(profile_name, "r");
  struct GetlineParams s_params = {
      .line = NULL, .len = 0, .read = 0, .stream = stdin};
  struct GetlineParams p_params = {
      .line = NULL, .len = 0, .read = 0, .stream = profile};

  int stop = get_stdin(&s_params, &p_params, p_rankings) ||
             get_profile(&s_params, &p_params, s_rankings, p_rankings);
  while (!stop) {
    struct Tuple t;
    parse_ranked_line(p_params.line, &t);
    int cmp = strcmp(s_params.line, t.first);

    if (cmp == 0) {
      rankings_insert(s_rankings, t.first, t.second);
      rankings_insert(p_rankings, t.first, t.second);
      stop = get_stdin(&s_params, &p_params, p_rankings) ||
             get_profile(&s_params, &p_params, s_rankings, p_rankings);
      tuple_free(&t);
      continue;
    }

    if (cmp < 0) {
      char *value_copy = malloc(strlen(s_params.line) + 1);
      strcpy(value_copy, s_params.line);
      int *rank = malloc(sizeof(int));
      *rank = 0;
      rankings_insert(s_rankings, value_copy, rank);
      rankings_insert(p_rankings, value_copy, rank);
      stop = get_stdin(&s_params, &p_params, p_rankings);
      tuple_free(&t);
      continue;
    }

    if (cmp > 0) {
      rankings_insert(p_rankings, t.first, t.second);
      stop = get_profile(&s_params, &p_params, s_rankings, p_rankings);
      tuple_free(&t);
      continue;
    }
  }

  free(s_params.line);
  free(p_params.line);
  fclose(profile);
  return 0;
}
*/

/*
#ifdef TEST
void test_libeuandreh() {
  time_t t;
  srand((unsigned)time(&t));
}
void unit_tests() {
  test_libeuandreh();

  test_vector();
  test_tuple();
  test_expand_profile_name();
  test_mkdir_p();
  test_ensure_profile();
  test_rankings();
}
#endif
*/

int main() { return 0; }
/*
int main(int argc, char *argv[]) {
#ifdef TEST
unit_tests();
return 0;
#endif

struct Tuple *options = get_options(argc, argv);
if (!options) {
  goto oom;
}

char *profile = expand_profile_name(options->second);
if (!profile) {
  tuple_free(options);
  goto oom;
}
free(options->second);
options->second = profile;
ensure_profile(options->second);

tuple_free(options);
return 0;
oom:
fprintf(stderr, "Unable to allocate memory, exitting.\n");
return 1;
// exit(1);

  struct Tuple options;
  struct Rankings s_rankings;
  struct Rankings p_rankings;
  rankings_init(&s_rankings);
  rankings_init(&p_rankings);

  int ret = 0;

  get_options(&options, argc, argv);
  if (process_options(&options)) {
    ret = 1;
    goto cleanup;
  }
  // char *command = options.first;
  char *profile = options.second;
  merge_stdin_with_profile(profile, &s_rankings, &p_rankings);

cleanup:
  options_free(&options);
  rankings_free(&s_rankings);
  rankings_free(&p_rankings);
  return ret;
}
*/

/*
  char *err_msg = "malloc failed: unable to allocate memory while processing "
                  "command-line options.\n";



*/