#include "luagraph.h"
#include <stack>
#include <queue>
#include <android/log.h>

#define TAG "LuaGraph"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)

extern "C" {
#include "lauxlib.h"
#include "lualib.h"
}

void recoverPath(std::stack<int> &stack, std::vector<int> &prev, int src,
                 int to) { // NOLINT(misc-no-recursion) DFS
  if (to == src) return;
  if (to >= prev.size() || prev[to] == INT32_MAX) {
    LOGD("No route to point");
    return;
  }
  stack.push(to);
  recoverPath(stack, prev, src, prev[to]);
}

void
dijkstra(int V, std::vector<std::vector<Node>> &graph, int src, int dst, std::stack<int> &route) {
  std::vector<float> dist(V, FLT_MAX);
  std::vector<int> path(V, INT32_MAX);

  dist[src] = 0;

  std::priority_queue<Node, std::vector<Node>, std::greater<>> nodes;
  nodes.push(Node(src, 0));

  while (!nodes.empty()) {
    int u = nodes.top().mVertex;
    nodes.pop();

    for (Node node: graph[u]) {

      // If there is shorted path to v through u.
      if (dist[node.mVertex] > dist[u] + node.mWeight) {
        // Updating distance of v
        dist[node.mVertex] = dist[u] + node.mWeight;
        nodes.push(Node(node.mVertex, dist[node.mVertex]));
        path[node.mVertex] = u;
      }
    }
  }

  recoverPath(route, path, src, dst);
}

int createGraph(lua_State *L) {
  void *userdata = lua_newuserdata(L, sizeof(Graph));
  new(userdata) Graph();

  return 1;
}

int expandTo(lua_State *L) {
  auto *ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  ptr->vector.resize(static_cast<size_t>(luaL_checkinteger(L, 2)));

  return 0;
}

int deleteGraph(lua_State *L) {
  auto *ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  delete ptr;
  return 0;
}

int buildRoute(lua_State *L) {
  auto *ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  int src = static_cast<int>(luaL_checkinteger(L, 2));
  int dst = static_cast<int>(luaL_checkinteger(L, 3));

  std::stack<int> stack;

  dijkstra((int) ptr->vector.size(), ptr->vector, src, dst, stack);

  lua_createtable(L, (int) stack.size(), 0);

  int i = 0;
  while (!stack.empty()) {
    lua_pushinteger(L, stack.top());
    stack.pop();
    lua_rawseti(L, -2, ++i); /* In lua indices start at 1 */
  }
  return 1;
}

int connect(lua_State *L) {
  auto ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  if (ptr == nullptr) {
    luaL_error(L, "graph is nil");
  }
  long long from = luaL_checkinteger(L, 2);
  long long to = luaL_checkinteger(L, 3);
  auto w = (float) luaL_checknumber(L, 4);

  if (from >= ptr->vector.size()) {
    luaL_error(L, "from is out of bounds");
    return 0;
  }
  if (to >= ptr->vector.size()) {
    luaL_error(L, "to is out of bounds");
    return 0;
  }

  ptr->vector[from].push_back(Node(to, w));
  ptr->vector[to].push_back(Node(from, w));

  return 0;
}

int setAdjWeight(lua_State *L) {
  auto ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  long long from = luaL_checkinteger(L, 2);
  long long to = luaL_checkinteger(L, 3);
  auto w = (float) luaL_checknumber(L, 4);
  if (ptr->vector[from].size() <= from) {
    luaL_error(L, "from is out of bounds");
  }

  ptr->vector[from][to].mWeight = w;
  return 0;
}

int getAdjCount(lua_State *L) {
  auto ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  int idx = (int) luaL_checkinteger(L, 2);
  lua_pushinteger(L, (int) ptr->vector[idx].size());
  return 1;
}

int getAdj(lua_State *L) {
  auto ptr = static_cast<Graph *>(lua_touserdata(L, 1));
  int x = (int) luaL_checkinteger(L, 2);
  int y = (int) luaL_checkinteger(L, 3);

  lua_pushinteger(L, ptr->vector[x][y].mVertex);
  return 1;
}
