#include "code_generator.h"

#include <compiler/parser/parser.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utils/memory/memory.h>
#include <utils/types.h>

#include "compiler/tree_parser/tree_parser.h"

const char *COMMAND_STRINGS[] = {
    "COMMAND_NONE",
    "COMMAND_CALL_FUNCTION",
    "COMMAND_PUSH_STRING",
};

void printInstruction(Instruction instruction) {
  printf("%s", COMMAND_STRINGS[instruction.command]);
  switch (instruction.command) {
    case COMMAND_NONE:
      printf("\n");
      return;
    case COMMAND_CALL_FUNCTION:
    case COMMAND_PUSH_STRING:
    case COMMAND_POP_IDENTIFIER:
    case COMMAND_PUSH_IDENTIFIER:
      SizedString *sizedString = instruction.operand;
      printf(" '%.*s'\n", (int)sizedString->size, sizedString->str);
      return;
  }
  fprintf(stderr, "bad instruction %d\n", instruction.command);
}

void printInstructions(Instructions instructions) {
  for (size_t i = 0; i < instructions.size; ++i) {
    printInstruction(instructions.instructions[i]);
  }
}

void deleteInstruction(Instruction instruction) {
  switch (instruction.command) {
    case COMMAND_NONE:
      return;
    case COMMAND_PUSH_STRING:
    case COMMAND_CALL_FUNCTION:
    case COMMAND_PUSH_IDENTIFIER:
    case COMMAND_POP_IDENTIFIER:
      SizedString *sizedString = instruction.operand;
      free(sizedString->str);
      free(sizedString);
      return;
  }
  fprintf(stderr, "bad instruction %d\n", instruction.command);
}

void deleteInstructions(Instructions instructions) {
  for (size_t i = 0; i < instructions.size; ++i) {
    deleteInstruction(instructions.instructions[i]);
  }
  free(instructions.instructions);
}

Instructions codeGenerator(SourceCode *code) {
  ParsedTree *root = treeParser(code);
  if (root != NULL) {
    Instructions instructions = _codeGenerator(root, code);

    deleteParsedTree(root);
    return instructions;
  }
  const Instructions error = {
      .instructions = NULL,
      .size = ERROR_SIZE,
  };
  return error;
}

Instructions codeGeneratorWithPrint(SourceCode *code) {
  ParsedTree *root = treeParserWithPrint(code);
  if (root != NULL) {
    printf("----tree parsed:\n");
    printParsedTreeNode(root);
    Instructions instructions = _codeGenerator(root, code);

    deleteParsedTree(root);
    return instructions;
  }
  fprintf(stderr, "error in tree parser\n");
  const Instructions error = {
      .instructions = NULL,
      .size = ERROR_SIZE,
  };
  return error;
}

Instructions _codeGenerator(ParsedTree *root, SourceCode *code) {
  const TreeScopeMetadata *metadata = root->metadata;

  size_t instructions_size = 10;
  Instruction *instructions =
      a404m_malloc(instructions_size * sizeof(*instructions));
  size_t instructions_inserted = 0;

  for (size_t i = 0; i < metadata->lines_size; ++i) {
    ParsedTree *node = metadata->lines[i];
    if (!nodeToInstruction(node, &instructions, &instructions_size,
                           &instructions_inserted, code)) {
      goto RETURN_ERROR;
    }
  }

  Instructions result = {
      .instructions = a404m_realloc(
          instructions, instructions_inserted * sizeof(*instructions)),
      .size = instructions_inserted,
  };
  return result;

RETURN_ERROR:
  free(instructions);

  const Instructions error = {
      .instructions = NULL,
      .size = ERROR_SIZE,
  };
  return error;
}

bool nodeToInstruction(ParsedTree *tree, Instruction **instructions,
                       size_t *instructions_size, size_t *instructions_inserted,
                       SourceCode *code) {
  /*printf("Parsing token = %s\n", TREE_TOKEN_STRINGS[tree->token]);*/
  switch (tree->token) {
    case TREE_TOKEN_FUNCTION_CALL: {
      const TreeFunctionCallMetadata *tree_metadata = tree->metadata;
      for (size_t i = 0; i < tree_metadata->values_size; ++i) {
        if (!nodeToInstruction(tree_metadata->values[i], instructions,
                               instructions_size, instructions_inserted,
                               code)) {
          return false;
        }
      }

      CommandCallFunctionOperand *operand = a404m_malloc(sizeof(*operand));
      operand->size =
          tree_metadata->function->nameEnd - tree_metadata->function->nameBegin;
      operand->str = a404m_malloc((operand->size + 1) * sizeof(char));
      strncpy(operand->str, tree_metadata->function->nameBegin, operand->size);
      const Instruction instruction = {
          .command = COMMAND_CALL_FUNCTION,
          .operand = operand,
      };
      insertInstruction(instruction, instructions, instructions_size,
                        instructions_inserted);
      return true;
    }
    case TREE_TOKEN_IDENTIFIER: {
      const TreeIdentifierMetadata *tree_metadata = tree->metadata;
      CommandPushIdentifierOperand *operand = a404m_malloc(sizeof(*operand));
      operand->size =
          tree_metadata->variable->nameEnd - tree_metadata->variable->nameBegin;
      operand->str = a404m_malloc((operand->size + 1) * sizeof(char));
      strncpy(operand->str, tree_metadata->variable->nameBegin,
              operand->size * sizeof(char));

      const Instruction instruction = {
          .command = COMMAND_PUSH_IDENTIFIER,
          .operand = operand,
      };
      insertInstruction(instruction, instructions, instructions_size,
                        instructions_inserted);
      return true;
    }
    case TREE_TOKEN_VALUE_STRING: {
      const TreeStringValueMetadata *tree_metadata = tree->metadata;
      CommandPushStringOperand *operand = a404m_malloc(sizeof(*operand));
      operand->size = tree_metadata->size;
      operand->str = a404m_malloc((operand->size + 1) * sizeof(char));
      memcpy(operand->str, tree_metadata->str,
             (operand->size + 1) * sizeof(char));

      const Instruction instruction = {
          .command = COMMAND_PUSH_STRING,
          .operand = operand,
      };
      insertInstruction(instruction, instructions, instructions_size,
                        instructions_inserted);
      return true;
    }
    case TREE_TOKEN_DEFINE_VARIABLE: {
      const TreeDefineVariableMetadata *tree_metadata = tree->metadata;
      if (tree_metadata->value == NULL) {
        return true;
      } else if (!nodeToInstruction(tree_metadata->value, instructions,
                                    instructions_size, instructions_inserted,
                                    code)) {
        return false;
      }
      CommandPopIdentifierOperand *operand = a404m_malloc(sizeof(*operand));

      operand->size = tree_metadata->nameEnd - tree_metadata->nameBegin;
      operand->str = a404m_malloc((operand->size + 1) * sizeof(char));
      strncpy(operand->str, tree_metadata->nameBegin, operand->size);

      const Instruction instruction = {
          .command = COMMAND_POP_IDENTIFIER,
          .operand = operand,
      };
      insertInstruction(instruction, instructions, instructions_size,
                        instructions_inserted);
      return true;
    }
    case TREE_TOKEN_DEFINE_CONSTANT:
      return true;
    case TREE_TOKEN_GLOBAL_SCOPE:
    case TREE_TOKEN_LOCAL_SCOPE:
    case TREE_TOKEN_STRUCT:
    case TREE_TOKEN_FUNCTION:
    case TREE_TOKEN_NONE:
    case TREE_TOKEN_ROOT:
  }
  printError("Unhandled token %s\n", code, tree->strBegin, tree->strEnd,
             TREE_TOKEN_STRINGS[tree->token]);
  return false;
}

void insertInstruction(const Instruction instruction,
                       Instruction **restrict instructions,
                       size_t *restrict instructions_size,
                       size_t *restrict instructions_inserted) {
  if (*instructions_inserted == *instructions_size) {
    *instructions_size += *instructions_size / 2 + 1;
    *instructions =
        a404m_realloc(*instructions, *instructions_size * sizeof(Instruction));
  }
  (*instructions)[*instructions_inserted] = instruction;
  ++*instructions_inserted;
}