#include "gameplay/ai.h"
#include "gameplay/gameactor.h"

#include "utility/raycaster.h"
#include "utility/script.h"

AI::AI(GameActor *actor, std::unique_ptr<Raycaster> raycaster)
    : state(AIState::Idle), raycaster(std::move(raycaster)), actor(actor),
      lastGCTime(std::chrono::high_resolution_clock::now()), GCTimeout(3) {}

/*
 * The behavior script works off of three different states, idle, patrol and
 * alert. When the script is idle, this is */
void AI::attachBehaviourScript(std::unique_ptr<AIScript> behaviour) {
  // passing out instance of raycaster and this AI into our scripting api
  // pay special attention each ai script has control of only their own instance
  // of ai!
  this->behaviour = std::move(behaviour);
  this->behaviour->lua["raycaster"] =
      sol::make_reference(this->behaviour->lua, raycaster.get());
  this->behaviour->lua["ai"] = sol::make_reference(this->behaviour->lua, this);

  idleFunc = this->behaviour->lua["idle"];
  patrolFunc = this->behaviour->lua["patrol"];
  alertFunc = this->behaviour->lua["alert"];
}

void AI::update() {
  try {
    // If our ai doesn't have a reference to the actor it's controlling it will
    // do nothing. If our ai script does have an actor but no target, it will
    // default to its idle state.
    if (actor && target) {
      switch (state) {
      case AIState::Idle:
        if (idleFunc.valid()) {
          auto result = idleFunc(actor, target);
          if (!result.valid()) {
            sol::error err = result;
            LOG(ERROR, "Lua error: {}", err.what(), NULL);
          }
        }
        break;
      case AIState::Patrol:
        if (patrolFunc.valid()) {
          auto result = patrolFunc(actor, target);
          if (!result.valid()) {
            sol::error err = result;
            LOG(ERROR, "Lua error: {}", err.what(), NULL);
          }
        }
        break;
      case AIState::Alert:
        if (alertFunc.valid()) {
          auto result = alertFunc(actor, target);
          if (!result.valid()) {
            sol::error err = result;
            LOG(ERROR, "Lua error: {}", err.what(), NULL);
          }
        }
        break;
      }
    } else if (actor) {
      if (idleFunc.valid()) {
        auto result = idleFunc(actor, nullptr);
        if (!result.valid()) {
          sol::error err = result;
          LOG(ERROR, "Lua Error: {}", err.what());
        }
      }
    }
    // Collect garbage just to be sure.
    std::chrono::high_resolution_clock::time_point curTime =
        std::chrono::high_resolution_clock::now();
    if (curTime - lastGCTime >= GCTimeout) {
      behaviour->lua.collect_gc();
      lastGCTime = curTime;
    }
  } catch (const std::exception &e) {
    LOG(ERROR, "Problem occured during AI update: {}", e.what());
    state = AIState::Idle;
  }
}

AI::~AI() {
  // Just set all of the references to null, given they are smart pointers, they
  // are freed when no longer used.
  behaviour->lua["raycaster"] = sol::nil;
  behaviour->lua["ai"] = sol::nil;
  behaviour->lua["idle"] = sol::nil;
  behaviour->lua["patrol"] = sol::nil;
  behaviour->lua["alert"] = sol::nil;
  behaviour->lua.collect_gc();
}
