#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils/file.h>
#include <vm/runner/runner.h>

#include "compiler/source_code/source_code.h"
#include "fasm/code_generator/code_generator.h"
#include "fasm/lexer/lexer.h"
#include "fasm/linker/linker.h"
#include "fasm/runner/runner.h"
#include "utils/types.h"

typedef enum FelanMode {
  FELAN_MODE_NONE,
  FELAN_MODE_COMPILE_FASM,
  FELAN_MODE_COMPILE_FELAN,
  FELAN_MODE_RUN_FASM,
  FELAN_MODE_RUN_FELAN,
} FelanMode;

int runFelan(const char *const filePath) {
  SourceCode sourceCode = makeSourceCode();

  Code *code;
  code = read_whole_file("std/builtins.felan");
  if (code == NULL) {
    goto RETURN_ERROR;
  }
  pushToSourceCode(&sourceCode, code);

  code = read_whole_file(filePath);
  if (code == NULL) {
    goto RETURN_ERROR;
  }
  pushToSourceCode(&sourceCode, code);

  if (!runner(&sourceCode)) {
    goto RETURN_ERROR;
  }

  deleteSourceCodeInners(sourceCode);
  return 0;

RETURN_ERROR:
  deleteSourceCodeInners(sourceCode);
  return 1;
}

int compileFasm(const char *const filePath) {
  SourceCode sourceCode = makeSourceCode();

  Code *code = read_whole_file(filePath);
  if (code == NULL) {
    goto RETURN_ERROR;
  }
  pushToSourceCode(&sourceCode, code);

  // printf("----lexing:\n");
  FasmLines *lines;
  if ((lines = fasmLexer(&sourceCode)) == NULL) {
    goto RETURN_ERROR;
  }

  // for (size_t i = 0; i < sourceCode.size; ++i) {
  //  fasmLinesPrint(lines[i]);
  // }

  // printf("----linking:\n");

  FasmLinkedLines linkedLines = fasmLinker(lines, &sourceCode);

  if (linkedLines.lines_size == ERROR_SIZE) {
    goto RETURN_LINKED_ERROR;
  }

  // fasmLinkedLinesPrint(linkedLines);

  // printf("----bytecode:\n");

  ByteCode bytecode = fasmCodeGenerator(&linkedLines);

  // for (size_t i = 0; i < bytecode.code_size; ++i) {
  //  printf("0x%.2x ", bytecode.code[i]);
  //}
  // printf("\n");

  deleteByteCodeInners(bytecode);

  fasmLinkedLinesDeleteInner(linkedLines);

  for (size_t i = 0; i < sourceCode.size; ++i) {
    fasmLinesDeleteInner(lines[i]);
  }

  free(lines);
  deleteSourceCodeInners(sourceCode);
  return 0;

RETURN_LINKED_ERROR:
  for (size_t i = 0; i < sourceCode.size; ++i) {
    fasmLinesDeleteInner(lines[i]);
  }
RETURN_ERROR:
  deleteSourceCodeInners(sourceCode);
  return 1;
}

int runFasm(const char *const filePath) {
  SourceCode sourceCode = makeSourceCode();

  Code *code = read_whole_file(filePath);
  if (code == NULL) {
    goto RETURN_ERROR;
  }
  pushToSourceCode(&sourceCode, code);

  FasmLines *lines;
  if ((lines = fasmLexer(&sourceCode)) == NULL) {
    goto RETURN_ERROR;
  }

  FasmLinkedLines linkedLines = fasmLinker(lines, &sourceCode);

  if (linkedLines.lines_size == ERROR_SIZE) {
    goto RETURN_LINKED_ERROR;
  }

  ByteCode bytecode = fasmCodeGenerator(&linkedLines);

  uint32_t result = fasmRunner(bytecode);

  deleteByteCodeInners(bytecode);

  fasmLinkedLinesDeleteInner(linkedLines);

  for (size_t i = 0; i < sourceCode.size; ++i) {
    fasmLinesDeleteInner(lines[i]);
  }

  free(lines);
  deleteSourceCodeInners(sourceCode);
  return result;

RETURN_LINKED_ERROR:
  for (size_t i = 0; i < sourceCode.size; ++i) {
    fasmLinesDeleteInner(lines[i]);
  }
RETURN_ERROR:
  deleteSourceCodeInners(sourceCode);
  return 1;
}

int main(int argc, char *argv[]) {
  FelanMode compileMode = FELAN_MODE_NONE;

  char const *filePath = NULL;

  for (int i = 1; i < argc; ++i) {
    const char *const arg = argv[i];
    if (strcmp(arg, "compile-fasm") == 0) {
      if (compileMode == FELAN_MODE_NONE) {
        compileMode = FELAN_MODE_COMPILE_FASM;
      } else {
        fprintf(stderr, "'%s' is not expected\n", arg);
        return 1;
      }
    } else if (strcmp(arg, "compile-felan") == 0) {
      if (compileMode == FELAN_MODE_NONE) {
        compileMode = FELAN_MODE_COMPILE_FELAN;
      } else {
        fprintf(stderr, "'%s' is not expected\n", arg);
        return 1;
      }
      fprintf(stderr, "'%s' is not yet supported\n", arg);
      return 1;
    } else if (strcmp(arg, "run-fasm") == 0) {
      if (compileMode == FELAN_MODE_NONE) {
        compileMode = FELAN_MODE_RUN_FASM;
      } else {
        fprintf(stderr, "'%s' is not expected\n", arg);
        return 1;
      }
    } else if (strcmp(arg, "run-felan") == 0) {
      if (compileMode == FELAN_MODE_NONE) {
        compileMode = FELAN_MODE_RUN_FELAN;
      } else {
        fprintf(stderr, "'%s' is not expected\n", arg);
        return 1;
      }
    } else if (filePath == NULL) {
      filePath = arg;
    } else {
      fprintf(stderr, "'%s' is not expected\n", arg);
      return 1;
    }
  }

  if (filePath == NULL) {
    fprintf(stderr, "Need a file path to operate\n");
  }

  switch (compileMode) {
    case FELAN_MODE_COMPILE_FASM:
      return compileFasm(filePath);
    case FELAN_MODE_COMPILE_FELAN:
      return 1;
    case FELAN_MODE_RUN_FASM:
      return runFasm(filePath);
    case FELAN_MODE_NONE:
    case FELAN_MODE_RUN_FELAN:
      return runFelan(filePath);
  }
}