#include "runner.h"

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

const BuiltinFunction BUILTIN_FUNCTIONS[] = {
    print,
};
const char *BUILTIN_FUNCTION_NAMES[] = {
    "print",
};
const size_t BUILTIN_FUNCTIONS_SIZE =
    sizeof(BUILTIN_FUNCTIONS) / sizeof(BuiltinFunction);

bool runner(SourceCode *sourceCode) {
  Instructions instructions = codeGenerator(sourceCode);
  if (instructions.size != ERROR_SIZE) {
    bool ranSuccess = _runner(instructions);
    deleteInstructions(instructions);
    return ranSuccess;
  }
  return false;
}

bool _runner(Instructions instructions) {
  size_t stack_size = 0;
  void **stack = a404m_malloc(stack_size * sizeof(void *));
  size_t stack_inserted = 0;

  size_t variables_size = 0;
  RunnerVariable **variables =
      a404m_malloc(variables_size * sizeof(RunnerVariable *));
  size_t variables_inserted = 0;

  for (size_t i = 0; i < instructions.size; ++i) {
    if (!runInstruction(instructions.instructions[i], &stack, &stack_size,
                        &stack_inserted, &variables, &variables_size,
                        &variables_inserted)) {
      goto RETURN_ERROR;
    }
  }

  for (size_t i = 0; i < variables_inserted; ++i) {
    free(variables[i]);
  }

  free(variables);
  free(stack);
  return true;

RETURN_ERROR:
  free(stack);
  return false;
}

BuiltinFunction getBuiltinFunction(SizedString string) {
  for (size_t i = 0; i < BUILTIN_FUNCTIONS_SIZE; ++i) {
    const char *search = BUILTIN_FUNCTION_NAMES[i];
    // faster than strlen+strncmp
    for (size_t j = 0;; ++j) {
      const char searchChar = search[j];
      if (j == string.size) {
        if (searchChar == '\0') {
          return BUILTIN_FUNCTIONS[i];
        } else {
          break;
        }
      } else if (searchChar == '\0') {
        break;
      } else if (searchChar != string.str[j]) {
        break;
      }
    }
  }
  return NULL;
}

bool runInstruction(Instruction instruction, void ***restrict stack,
                    size_t *restrict stack_size,
                    size_t *restrict stack_inserted,
                    RunnerVariable ***restrict variables,
                    size_t *restrict variables_size,
                    size_t *restrict variables_inserted) {
  switch (instruction.command) {
    case COMMAND_PUSH_IDENTIFIER: {
      const CommandPushIdentifierOperand *operand = instruction.operand;
      pushToStack(
          getRunnerVariable(operand, variables, variables_inserted)->value,
          stack, stack_size, stack_inserted);
      return true;
    }
    case COMMAND_PUSH_STRING: {
      CommandPushStringOperand *operand = instruction.operand;
      pushToStack(operand, stack, stack_size, stack_inserted);
      return true;
    }
    case COMMAND_CALL_FUNCTION: {
      SizedString *functionName = instruction.operand;
      const BuiltinFunction function = getBuiltinFunction(*functionName);
      if (function == NULL) {
        fprintf(stderr, "function '%.*s' not found\n", (int)functionName->size,
                functionName->str);
        return false;
      }
      function(stack, stack_inserted);
      return true;
    }
    case COMMAND_POP_IDENTIFIER: {
      const CommandPopIdentifierOperand *operand = instruction.operand;
      setRunnerVariable(popFromStack(stack, stack_inserted), operand, variables,
                        variables_size, variables_inserted);
      return true;
    }
    case COMMAND_NONE:
  }
  fprintf(stderr, "bad command '%d'\n", instruction.command);
  return false;
}

RunnerVariable *getRunnerVariable(const SizedString *varName,
                                  RunnerVariable ***restrict variables,
                                  size_t *restrict variables_inserted) {
  for (size_t i = *variables_inserted - 1; i != (typeof(i))-1; --i) {
    RunnerVariable *variable = (*variables)[i];
    if (variable->name->size == varName->size &&
        strncmp(varName->str, variable->name->str, varName->size) == 0) {
      return variable;
    }
  }
  return NULL;
}

void setRunnerVariable(void *value, const SizedString *varName,
                       RunnerVariable ***restrict variables,
                       size_t *restrict variables_size,
                       size_t *restrict variables_inserted) {
  RunnerVariable *variable =
      getRunnerVariable(varName, variables, variables_inserted);
  if (variable != NULL) {
    variable->value = value;
    return;
  }

  variable = a404m_malloc(sizeof(*variable));
  variable->name = varName;
  variable->value = value;

  if (*variables_inserted == *variables_size) {
    *variables_size += *variables_size / 2 + 1;
    *variables =
        a404m_realloc(*variables, *variables_size * sizeof(RunnerVariable *));
  }
  (*variables)[*variables_inserted] = variable;
  *variables_inserted += 1;
}

void *popFromStack(void ***restrict stack, size_t *restrict stack_inserted) {
  if (*stack_inserted == 0) {
    fprintf(stderr, "stack underflow\n");
    exit(1);
  }
  return (*stack)[--*stack_inserted];
}

void pushToStack(void *value, void ***restrict stack,
                 size_t *restrict stack_size, size_t *restrict stack_inserted) {
  if (*stack_inserted == *stack_size) {
    *stack_size += *stack_size / 2 + 1;
    *stack = a404m_realloc(*stack, *stack_size * sizeof(void *));
  }
  (*stack)[*stack_inserted] = value;
  ++*stack_inserted;
}

void print(void ***restrict stack, size_t *restrict stack_inserted) {
  const SizedString *string = popFromStack(stack, stack_inserted);
  fwrite(string->str, string->size, 1, stdout);
}