// Written by retoor@molodetz.nl

// This program provides functionality to highlight the keywords in source code
// using ANSI color formatting and convert Markdown syntax into ANSI-colored
// text output.

// Uses standard C libraries: <stdio.h>, <string.h>. Also utilizes ANSI escape
// codes for text formatting.

// MIT License:
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

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

#define RESET "\033[0m"
#define BOLD "\033[1m"
#define ITALIC "\033[3m"
#define FG_YELLOW "\033[33m"
#define FG_BLUE "\033[34m"
#define FG_CYAN "\033[36m"

int is_keyword(const char *word) {

  const char *keywords[] = {
      "int", "float", "double", "char", "void", "if", "else", "while", "for",
      "return", "struct", "printf",
      // Rust keywords
      "let", "fn", "impl", "match", "enum", "trait", "use", "mod", "pub",
      "const", "static",
      // Python keywords
      "def", "class", "import", "from", "as", "with", "try", "except",
      "finally", "lambda", "async", "await",
      // Java keywords
      "public", "private", "protected", "class", "interface", "extends",
      "implements", "new", "static", "final", "synchronized",
      // JavaScript keywords
      "var", "let", "const", "function", "async", "await", "if", "else",
      "switch", "case", "break", "continue", "return",
      // C++ keywords
      "namespace", "template", "typename", "class", "public", "private",
      "protected", "virtual", "override", "friend", "new",
      // Go keywords
      "package", "import", "func", "var", "const", "type", "interface",
      "struct", "go", "defer", "select",
      // Bash keywords
      "if", "then", "else", "elif", "fi", "case", "esac", "for", "while",
      "until", "do", "done", "function",
      // C# keywords
      "namespace", "using", "class", "interface", "public", "private",
      "protected", "static", "void", "new", "override"};

  for (size_t i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) {
    if (strcmp(word, keywords[i]) == 0) {
      return 1;
    }
  }
  return 0;
}

void highlight_code(const char *code) {
  const char *ptr = code;
  char buffer[4096];
  size_t index = 0;

  while (*ptr) {
    if ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') ||
        (*ptr == '_')) {
      while ((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z') ||
             (*ptr >= '0' && *ptr <= '9') || (*ptr == '_')) {
        buffer[index++] = *ptr++;
      }
      buffer[index] = 0;

      if (is_keyword(buffer)) {
        printf(FG_BLUE "%s" RESET, buffer);
      } else {
        printf("%s", buffer);
      }
      index = 0;
    } else if (*ptr >= '0' && *ptr <= '9') {
      while (*ptr >= '0' && *ptr <= '9') {
        buffer[index++] = *ptr++;
      }
      buffer[index] = 0;
      printf(FG_CYAN "%s" RESET, buffer);
      index = 0;
    } else {
      putchar(*ptr);
      ptr++;
    }
  }
}

void parse_markdown_to_ansi(const char *markdown) {
  const char *ptr = markdown;
  bool inside_code = false;

  while (*ptr) {
    if (*ptr == '`' && *(ptr + 1) != '`') {
      inside_code = !inside_code;
      if (inside_code) {
        printf(FG_YELLOW);
      } else {
        printf(RESET);
      }
      ptr++;
      continue;
    } else if (!strncmp(ptr, "```", 3)) {
      inside_code = !inside_code;
      if (inside_code) {
        ptr = strstr(ptr, "\n") + 1;
      } else {
        ptr += 3;
      }
      if (*ptr == '\0') {
        break;
      }
    }

    if (inside_code) {
      char code_buffer[1024 * 1024] = {0};
      ;
      size_t index = 0;

      while (*ptr && *ptr != '`') {
        code_buffer[index++] = *ptr++;
        if (*ptr == '\n' || *ptr == ' ' || *ptr == '\t' || *ptr == '.') {
          code_buffer[index++] = 0;
          highlight_code(code_buffer);
          index = 0;
        }
      }
      code_buffer[index] = 0;
      if (index) {
        highlight_code(code_buffer);
      }
    } else {
      if (strncmp(ptr, "**", 2) == 0) {
        printf(BOLD);
        ptr += 2;
        while (*ptr && strncmp(ptr, "**", 2) != 0) {
          putchar(*ptr++);
        }
        if (*ptr == '*' && *(ptr + 1) == '*')
          ptr += 2;
        printf(RESET);
      } else if (*ptr == '*' && (ptr == markdown || *(ptr - 1) != '*')) {
        printf(ITALIC);
        ptr++;
        while (*ptr && *ptr != '*') {
          putchar(*ptr++);
        }
        if (*ptr == '*')
          ptr++;
        printf(RESET);
      } else if (strncmp(ptr, "### ", 4) == 0) {
        printf(BOLD FG_YELLOW);
        ptr += 4;
        while (*ptr && *ptr != '\n') {
          putchar(*ptr++);
        }
        printf(RESET "\n");
      } else if (strncmp(ptr, "## ", 3) == 0) {
        printf(BOLD FG_YELLOW);
        ptr += 3;
        while (*ptr && *ptr != '\n') {
          putchar(*ptr++);
        }
        printf(RESET "\n");
      } else if (strncmp(ptr, "# ", 2) == 0) {
        printf(BOLD FG_YELLOW);
        ptr += 2;
        while (*ptr && *ptr != '\n') {
          putchar(*ptr++);
        }
        printf(RESET "\n");
      } else {
        putchar(*ptr);
        ptr++;
      }
    }
  }
}
