#include "code_generator.h"

#include <compiler/parser/parser.h>
#include <stdio.h>
#include <stdlib.h>

#include "utils/memory/memory.h"
#include "utils/types.h"

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

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

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:
    case COMMAND_PRINT:
      break;
    case COMMAND_PUSH_STRING:
      SizedString *sizedString = instruction.operand;
      free(sizedString->str);
      free(sizedString);
      break;
    default:
      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(ParsedNode *root) {
  const ScopeMetadata *metadata = root->metadata;

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

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

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

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

bool nodeToInstruction(ParsedNode *node, Instruction **instructions,
                       size_t *instructions_size,
                       size_t *instructions_inserted) {
  switch (node->token) {
      // TODO: this is wrong when you want functions
    case PARSED_TOKEN_PARENTHESIS: {
      const ScopeMetadata *metadata = node->metadata;
      for (size_t i = 0; i < metadata->operands_size; ++i) {
        if (!nodeToInstruction(metadata->operands[i], instructions,
                               instructions_size, instructions_inserted)) {
          return false;
        }
      }
      return true;
    }
    case PARSED_TOKEN_EOL:
      return nodeToInstruction(node->metadata, instructions, instructions_size,
                               instructions_inserted);
    case PARSED_TOKEN_PRINT:
      if (nodeToInstruction(node->metadata, instructions, instructions_size,
                            instructions_inserted)) {
        const Instruction instruction = {
            .command = COMMAND_PRINT,
            .operand = NULL,
        };
        insertInstruction(instruction, instructions, instructions_size,
                          instructions_inserted);
        return true;
      } else {
        return false;
      }
    case PARSED_TOKEN_VALUE_STRING: {
      SizedString *string = nodeToString(node);
      if (string == NULL) {
        return false;
      }
      const Instruction instruction = {
          .command = COMMAND_PUSH_STRING,
          .operand = string,
      };
      insertInstruction(instruction, instructions, instructions_size,
                        instructions_inserted);
      return true;
    }
    case PARSED_TOKEN_NONE:
    case PARSED_TOKEN_ROOT:
  }
  fprintf(stderr, "unexpected token %s\n", PARSED_TOKEN_STRINGS[node->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;
}

SizedString *nodeToString(ParsedNode const *node) {
  const char *strBegin = node->strBegin + 1;
  const char *strEnd = node->strEnd - 1;

  char *str = a404m_malloc((strEnd - strBegin + 1) * sizeof(char));
  size_t inserted = 0;

  for (char const *iter = strBegin; iter < strEnd; ++iter) {
    char c = *iter;
    if (c == '\\') {
      if (++iter < strEnd) {
        switch (*iter) {
          case '\'':
            c = '\'';
            break;
          case '\"':
            c = '\"';
            break;
          case 'n':
            c = '\n';
            break;
          default:
            fprintf(stderr, "bad string, bad '\\'\n");
            goto RETURN_ERROR;
        }
      } else {
        fprintf(stderr, "bad string, bad '\\'\n");
        goto RETURN_ERROR;
      }
    }
    str[inserted] = c;
    ++inserted;
  }

  str[inserted] = '\0';
  SizedString *string = a404m_malloc(sizeof(SizedString));
  string->str = a404m_realloc(str, (inserted + 1) * sizeof(char));
  string->size = inserted;
  return string;
RETURN_ERROR:
  free(str);
  return NULL;
}