#include <pcre2.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../logger.h"
#include "utils.h"

static void print_error(int code) {
  PCRE2_UCHAR message[256];
  if (pcre2_get_error_message(code, message,
                              sizeof(message) / sizeof(PCRE2_UCHAR)))
    LOG("PCRE2", "Error: %s\n", (const char *)message);
}

int regex_match(const char *subject, str_array_t patterns,
                str_array_t *results) {
  pcre2_code *re;

  int errornumber;
  int rc = PCRE2_ERROR_NOMATCH;

  PCRE2_SIZE subject_len = strlen(subject);
  PCRE2_SIZE offset = 0, erroroffset;
  PCRE2_SIZE *ovector;

  pcre2_match_data *match_data;
  for (unsigned short i = 0; i < patterns.n; i++) {
    DEBUG_PRINT("Gets pattern: %s\n", patterns.str[i]);
    re = pcre2_compile((PCRE2_SPTR)patterns.str[i], PCRE2_ZERO_TERMINATED, 0,
                       &errornumber, &erroroffset, NULL);

    if (re == NULL) {
      PCRE2_UCHAR buffer[256];
      pcre2_get_error_message(errornumber, buffer, sizeof(buffer));
      LOG("PCRE2", "compilation failed at offset %d: %s\n", (int)erroroffset,
          buffer);
      return 1;
    }

    match_data = pcre2_match_data_create_from_pattern(re, NULL);

    while (offset < subject_len &&
           (rc = pcre2_match(re, (PCRE2_SPTR)subject, (PCRE2_SIZE)subject_len,
                             offset, 0, match_data, NULL)) > 0) {

      // results->str = realloc(results->str, sizeof(char *) * (rc +
      // results->n));
      int valid_n = results->n;
      resize_str_array(results, rc - 1 + results->n);

      ovector = pcre2_get_ovector_pointer(match_data);
      DEBUG_PRINT("Get %d captures.\n", rc - 1);
      DEBUG_PRINT("Match succeeded at offset %d.\n", (int)ovector[0]);

      for (unsigned short j = 1; j < rc; j++) {
        PCRE2_SIZE substring_length = ovector[2 * j + 1] - ovector[2 * j];
        PCRE2_SPTR substring = (PCRE2_SPTR)subject + ovector[2 * j];
        /* Here we need to manually control the str array,
         * as PCRE2_SPTR == const unsigned char
         * (which cannot be directly casted) */
        results->str[valid_n + j - 1] = malloc(substring_length + 1);
        sprintf(results->str[valid_n + j - 1], "%.*s", (int)substring_length,
                substring);
        DEBUG_PRINT("index: %2d, substring_length: %d\n", j,
                    (int)substring_length);
      }
      offset = ovector[1];
      DEBUG_PRINT("offset: %zu, subject_len: %zu\n", offset, subject_len);
    }
    pcre2_match_data_free(match_data);
    pcre2_code_free(re);
    if (rc <= 0) {

      switch (rc) {
      case PCRE2_ERROR_NOMATCH:
        DEBUG_PRINT("No match found.\n");
        break;
      case 0:
        LOG("PCRE2",
            "ovector was not big enough for all the captured substrings\n");
        break;
      default:
        print_error(rc);
        return 1;
      }
    }
  }
  return 0;
}

int substitute_str(const char *subject, const char *pattern,
                   const char *replacement, char **presult) {
  pcre2_code *re;
  pcre2_match_context *match_context;
  int rc, error;
  PCRE2_SIZE erroffset, outlength;
  PCRE2_UCHAR *outbuf;

  re = pcre2_compile((PCRE2_SPTR)pattern, PCRE2_ZERO_TERMINATED,
                     PCRE2_MULTILINE, &error, &erroffset, 0);
  if (!re) {
    print_error(error);
    return 1;
  }

  match_context = pcre2_match_context_create(0);

  outlength = 0;
  rc = pcre2_substitute(re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0,
                            PCRE2_SUBSTITUTE_GLOBAL |
                                PCRE2_SUBSTITUTE_OVERFLOW_LENGTH |
                                PCRE2_SUBSTITUTE_EXTENDED,
                            0, match_context, (PCRE2_SPTR)replacement,
                            PCRE2_ZERO_TERMINATED, 0, &outlength);

  if (rc != PCRE2_ERROR_NOMEMORY) {
    print_error(rc);
    return 1;
  }

  outbuf = malloc(outlength * sizeof(PCRE2_UCHAR));

  rc = pcre2_substitute(re, (PCRE2_SPTR)subject, PCRE2_ZERO_TERMINATED, 0,
                            PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_EXTENDED,
                            0, match_context, (PCRE2_SPTR)replacement,
                            PCRE2_ZERO_TERMINATED, outbuf, &outlength);

  if (rc < 0) {
    print_error(rc);
    return 1;
  }

  *presult = (char *)outbuf;

  pcre2_match_context_free(match_context);
  pcre2_code_free(re);

  return 0;
}

const char *mimeType2ext(const char *mimeType) {
  static char mimeType_l[CHAR_MAX];
  strcpy(mimeType_l, mimeType);
  const char *exts[2];
  size_t extsCount = 0;

  char *token = strtok(mimeType_l, "/");
  while (token != NULL && extsCount < 2) {
    exts[extsCount++] = token;
    token = strtok(NULL, "/");
  }

  if (extsCount == 2) {
    return exts[1];
  }

  return "mp4"; // Cannot parse, use default
}

int repchr(char *str, char t, char r) {
  int c = 0;
  for (size_t i = 0; str[i] != '\0'; i++) {
    if (str[i] == t) {
      str[i] = r;
      c++;
    }
  }
  return c;
}

void free_and_nullify(void *p) {
  if (p) {
    free(p);
    p = NULL;
  }
}