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

#define LIB_STR_IMPL
#include <libstr.h>

typedef enum {
  CTL_ERR_OK,
  CTL_ERR_NO_ARGS,
  CTL_ERR_NO_FILE,
  CTL_ERR_UNCLOSED_CODE,
} ctl_error_t;

void ctl_error_str(ctl_error_t err) {
  switch (err) {
    case CTL_ERR_OK:
      break;
    case CTL_ERR_NO_ARGS:
      printf("Usage: ctl <filename>\n");
      break;
    case CTL_ERR_NO_FILE:
      printf("Error: No file found!\n");
      break;
    case CTL_ERR_UNCLOSED_CODE:
      printf("Error: Unclosed code block!\n");
      break;
  }
}

char *str_replace_all_len(char *buffer, char *str, char *replacement, int len) {
  char *ret = NULL;
  int str_len = strlen(str);
  for (int i = 0; i < len; i++) {
    char *ptr = &buffer[i];
    if (strncmp(ptr, str, str_len) == 0) {
      str_append(&ret, replacement);
      i += str_len - 1;
    }
    else {
      str_append_len(&ret, &buffer[i], 1);
    }
  }
  return ret;
}

char *str_hex_len(char *str, int len) {
  const int HEX = 4;
  char *ret = calloc(HEX * len + 1, sizeof(char));
  for (int i = 0; i < len; i++) {
    unsigned char hex = str[i] & 0xff;
    snprintf(ret + HEX * i, HEX * len, "\\x%02x", hex);
  }
  ret[HEX * len] = '\0';
  return ret;
}

ctl_error_t ctl_render(char **template, char *content) {
  int len = strlen(content);
  int set = 0;
  int is_code = 0;
  str_append(template, "{\n");
  for (int i = 0; i < len; i++) {
    if (content[i] == '%') {
      // ignore escaped %
      if (i + 1 < len && content[i + 1] == '%') {
        i += 1;
        continue;
      }
      if (is_code) {
        str_append_len(template, content + set, i - set);
        str_append(template, "\n");
      }
      else {
        const int STRING_LIMIT = 4095;
        int str_len = i - set;
        int rem_len = str_len;
        int hex_len = 0;
        int idx = 0;
        while (rem_len > 0) {
          if (rem_len > STRING_LIMIT) {
            hex_len = STRING_LIMIT;
          }
          else {
            hex_len = str_len - idx;
          }
          rem_len -= hex_len;
          char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len);
          char *hex = str_hex_len(str, strlen(str));
          str_append(template, "CTL_OUT(ctl_out, \"");
          str_append(template, hex);
          str_append(template, "\");\n");
          free(str);
          free(hex);
          idx += hex_len;
        }
      }
      is_code = !is_code;
      set = i + 1;
    }
  }
  if (is_code) {
    return CTL_ERR_UNCLOSED_CODE;
  }
  else {
    const int STRING_LIMIT = 4095;
    int str_len = len - set;
    int rem_len = str_len;
    int hex_len = 0;
    int idx = 0;
    while (rem_len > 0) {
      if (rem_len > STRING_LIMIT) {
        hex_len = STRING_LIMIT;
      }
      else {
        hex_len = str_len - idx;
      }
      rem_len -= hex_len;
      char *str = str_replace_all_len(content + set + idx, "%%", "%", hex_len);
      char *hex = str_hex_len(str, strlen(str));
      str_append(template, "CTL_OUT(ctl_out, \"");
      str_append(template, hex);
      str_append(template, "\");\n");
      free(str);
      free(hex);
      idx += hex_len;
    }
  }
  str_append(template, "}\n");
  *template = str_replace_all(*template, "%%", "%");
  return CTL_ERR_OK;
}

ctl_error_t ctl_file_read(char **content, const char *filename) {
  FILE *fp = fopen(filename, "r");
  if (fp == NULL) {
    return CTL_ERR_NO_FILE;
  }
  fseek(fp, 0, SEEK_END);
  int len = ftell(fp);
  fseek(fp, 0, SEEK_SET);
  *content = calloc(len + 1, sizeof(char));
  fread(*content, sizeof(char), len, fp);
  (*content)[len] = '\0';
  fclose(fp);
  return CTL_ERR_OK;
}

int main(int argc, char **argv) {
  ctl_error_t err;
  if (argc < 2) {
    err = CTL_ERR_NO_ARGS;
    ctl_error_str(err);
    return err;
  }
  char *filename = argv[1];
  char *content = NULL;
  err = ctl_file_read(&content, filename);
  if (err != CTL_ERR_OK) {
    ctl_error_str(err);
    return err;
  }
  char *template = NULL;
  err = ctl_render(&template, content);
  if (err != CTL_ERR_OK) {
    ctl_error_str(err);
    free(content);
    if (template != NULL) {
      free(template);
    }
    return err;
  }
  printf("%s\n", template);
  free(template);
  free(content);
  return CTL_ERR_OK;
}
