//
// Created by martin on 11.02.22.
//

#include "AnimGraphEditor.h"

#include "AnimGraphResource.h"
#include "imgui.h"
#include "imnodes.h"
#include "misc/cpp/imgui_stdlib.h"

static AnimGraphResource sGraphGresource = AnimGraphResource();

ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
  switch (socket_type) {
    case SocketType::SocketTypeAnimation:
      return ImNodesPinShape_QuadFilled;
    case SocketType::SocketTypeFloat:
      return ImNodesPinShape_CircleFilled;
    case SocketType::SocketTypeVec3:
      return ImNodesPinShape_TriangleFilled;
    case SocketType::SocketTypeQuat:
      return ImNodesPinShape_Triangle;
    case SocketType::SocketTypeBool:
      return ImNodesPinShape_Circle;
    default:
      break;
  }

  return ImNodesPinShape_Quad;
}

void NodeSocketEditor(Socket& socket) {
  int mode_current = static_cast<int>(socket.m_type);
  ImGui::InputText("Name", &socket.m_name);
  if (ImGui::Combo(
          "Type",
          &mode_current,
          SocketTypeNames,
          sizeof(SocketTypeNames) / sizeof(char*))) {
    socket.m_type = static_cast<SocketType>(mode_current);
  }
}

void RemoveConnectionsForSocket(
    AnimGraphResource& graph_resource,
    AnimNodeResource& node_resource,
    Socket& socket) {
  std::vector<AnimGraphConnectionResource>::iterator iter =
      graph_resource.m_connections.begin();

  while (iter != graph_resource.m_connections.end()) {
    // TODO adjust for refactor
    assert(false);

    //    AnimGraphConnectionResource& connection = *iter;
    //    if (connection.m_source_node == &node_resource
    //        && connection.m_source_socket == &socket) {
    //      iter = sGraphGresource.m_connections.erase(iter);
    //    } else {
    //      iter++;
    //    }
  }
}

void AnimGraphEditorRenderSidebar(
    AnimGraphResource& graph_resource,
    AnimNodeResource& node_resource) {
  ImGui::Text("[%s]", node_resource.m_type_name.c_str());

  char node_name_buffer[256];
  memset(node_name_buffer, 0, sizeof(node_name_buffer));
  strncpy(
      node_name_buffer,
      node_resource.m_name.c_str(),
      std::min(node_resource.m_name.size(), sizeof(node_name_buffer)));

  if (ImGui::InputText("Name", node_name_buffer, sizeof(node_name_buffer))) {
    node_resource.m_name = node_name_buffer;
  }

  int num_properties = node_resource.m_socket_accessor->m_properties.size();
  for (int i = 0; i < num_properties; i++) {
    Socket& property = node_resource.m_socket_accessor->m_properties[i];
    if (property.m_type == SocketType::SocketTypeFloat) {
      ImGui::SliderFloat(
          property.m_name.c_str(),
          reinterpret_cast<float*>(property.m_reference.ptr),
          -100.f,
          100.f);
    } else if (property.m_type == SocketType::SocketTypeBool) {
      ImGui::Checkbox(
          property.m_name.c_str(),
          reinterpret_cast<bool*>(property.m_reference.ptr));
    } else if (property.m_type == SocketType::SocketTypeString) {
      char string_buf[1024];
      memcpy (string_buf, property.m_value.string_ptr->c_str(), property.m_value.string_ptr->size() + 1);
      if (ImGui::InputText(
              property.m_name.c_str(),
              string_buf,
              sizeof(string_buf))) {
        *property.m_value.string_ptr = string_buf;
      }
    }
  }

  if (&node_resource == &graph_resource.getGraphOutputNode()) {
    ImGui::Text("Outputs");

    // Graph outputs are the inputs of the output node!
    std::vector<Socket>& outputs = node_resource.m_socket_accessor->m_inputs;

    std::vector<Socket>::iterator iter = outputs.begin();
    while (iter != outputs.end()) {
      Socket& output = *iter;
      ImGui::PushID(&output);
      NodeSocketEditor(output);
      if (ImGui::Button("X")) {
        RemoveConnectionsForSocket(graph_resource, node_resource, output);
        iter = outputs.erase(iter);
      } else {
        iter++;
      }
      ImGui::PopID();
    }
  }

  if (&node_resource == &graph_resource.getGraphInputNode()) {
    ImGui::Text("Inputs");

    // Graph inputs are the outputs of the input node!
    std::vector<Socket>& inputs = node_resource.m_socket_accessor->m_outputs;

    std::vector<Socket>::iterator iter = inputs.begin();
    while (iter != inputs.end()) {
      Socket& input = *iter;
      ImGui::PushID(&input);
      NodeSocketEditor(input);
      if (ImGui::Button("X")) {
        RemoveConnectionsForSocket(graph_resource, node_resource, input);
        iter = inputs.erase(iter);
      } else {
        iter++;
      }
      ImGui::PopID();
    }
  }
}

void AnimGraphEditorUpdate() {
  ImGui::BeginMenuBar();
  if (ImGui::Button("Save")) {
    sGraphGresource.saveToFile("editor_graph.json");
  }
  if (ImGui::Button("Load")) {
    sGraphGresource.loadFromFile("editor_graph.json");

    for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) {
      const AnimNodeResource& node_resource = sGraphGresource.m_nodes[i];
      ImNodes::SetNodeGridSpacePos(
          i,
          ImVec2(node_resource.m_position[0], node_resource.m_position[1]));
    }
  }
  if (ImGui::Button("Clear")) {
    sGraphGresource.clear();
  }
  char graph_name_buffer[256];
  memset(graph_name_buffer, 0, sizeof(graph_name_buffer));
  strncpy(
      graph_name_buffer,
      sGraphGresource.m_name.c_str(),
      sizeof(graph_name_buffer));
  if (ImGui::InputText("Name", graph_name_buffer, sizeof(graph_name_buffer))) {
    sGraphGresource.m_name = graph_name_buffer;
  }

  ImGui::EndMenuBar();

  ImGui::Columns(2);

  //
  // Node editor canvas
  //
  ImNodes::BeginNodeEditor();

  // Popup menu
  {
    const bool open_popup =
        ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)
        && ImNodes::IsEditorHovered()
        && ImGui::IsMouseReleased(ImGuiMouseButton_Right);

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
    if (!ImGui::IsAnyItemHovered() && open_popup) {
      ImGui::OpenPopup("add node");
    }

    if (ImGui::BeginPopup("add node")) {
      const ImVec2 click_pos = ImGui::GetMousePosOnOpeningCurrentPopup();
      std::string node_type_name = "";
      if (ImGui::MenuItem("AnimSampler")) {
        node_type_name = "AnimSampler";
      }

      if (ImGui::MenuItem("Blend2")) {
        node_type_name = "Blend2";
      }

      if (ImGui::MenuItem("SpeedScale")) {
        node_type_name = "SpeedScale";
      }

      if (ImGui::MenuItem("MathAddNode")) {
        node_type_name = "MathAddNode";
      }

      if (ImGui::MenuItem("MathFloatToVec3Node")) {
        node_type_name = "MathFloatToVec3Node";
      }

      if (node_type_name != "") {
        AnimNodeResource node_resource =
            AnimNodeResourceFactory(node_type_name);
        size_t node_id = sGraphGresource.m_nodes.size();
        ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos());
        sGraphGresource.m_nodes.push_back(node_resource);
      }

      ImGui::EndPopup();
    }

    ImGui::PopStyleVar(ImGuiStyleVar_WindowPadding);
  }

  for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) {
    AnimNodeResource& node_resource = sGraphGresource.m_nodes[i];

    ImNodes::BeginNode(i);
    ImGui::PushItemWidth(110.0f);

    // Header
    ImNodes::BeginNodeTitleBar();
    if (&node_resource == &sGraphGresource.getGraphOutputNode()) {
      ImGui::TextUnformatted("Graph Outputs");
    } else if (&node_resource == &sGraphGresource.getGraphInputNode()) {
      ImGui::TextUnformatted("Graph Inputs");
    } else {
      ImGui::TextUnformatted(node_resource.m_type_name.c_str());
    }
    ImNodes::EndNodeTitleBar();

    // Inputs
    const std::vector<Socket>& node_inputs =
        node_resource.m_socket_accessor->m_inputs;
    for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
      const Socket& socket = node_inputs[j];

      ImColor socket_color = ImColor(255, 255, 255, 255);
      if (socket.m_flags & SocketFlagAffectsTime) {
        socket_color = ImColor(255, 128, 128, 255);
      }

      ImNodes::BeginInputAttribute(
          GenerateInputAttributeId(i, j),
          sGetSocketShapeFromSocketType(socket.m_type),
          socket_color);
      ImGui::TextUnformatted(socket.m_name.c_str());

      bool socket_connected =
          sGraphGresource.isSocketConnected(node_resource, socket.m_name);
      if (!socket_connected &&
          (socket.m_type == SocketType::SocketTypeFloat)) {
        ImGui::SameLine();
        float socket_value = 0.f;
        ImGui::PushItemWidth(100.0f - ImGui::CalcTextSize(socket.m_name.c_str()).x);
        ImGui::DragFloat("##hidelabel", &socket_value, 0.01f);
        ImGui::PopItemWidth();
      }

      ImNodes::PushAttributeFlag(
          ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
      ImNodes::EndInputAttribute();
    }

    // Outputs
    const std::vector<Socket>& node_outputs =
        node_resource.m_socket_accessor->m_outputs;
    for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
      const Socket& socket = node_outputs[j];
      ImNodes::BeginOutputAttribute(
          GenerateOutputAttributeId(i, j),
          sGetSocketShapeFromSocketType(socket.m_type),
          ImColor(255, 255, 255, 255));
      ImGui::TextUnformatted(socket.m_name.c_str());
      ImNodes::PushAttributeFlag(
          ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
      ImNodes::EndInputAttribute();
    }

    // Graph output node
    if (i == 0) {
      if (ImGui::Button("+Output")) {
        AnimNodeResource& graph_output_node =
            sGraphGresource.getGraphOutputNode();

        static float bla = 0.f;
        std::string socket_name = "Output";
        socket_name += std::to_string(
            graph_output_node.m_socket_accessor->m_inputs.size());
        graph_output_node.m_socket_accessor->RegisterInput<float>(
            socket_name,
            nullptr);
      }
    } else if (i == 1) {
      if (ImGui::Button("+Input")) {
        AnimNodeResource& graph_input_node =
            sGraphGresource.getGraphInputNode();

        static float bla = 0.f;
        std::string socket_name = "Input";
        socket_name += std::to_string(
            graph_input_node.m_socket_accessor->m_outputs.size());
        graph_input_node.m_socket_accessor->RegisterOutput<float>(
            socket_name,
            nullptr);
      }
    }

    // Save state in node resource
    ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(i);
    node_resource.m_position[0] = node_pos[0];
    node_resource.m_position[1] = node_pos[1];
    ImGui::PopItemWidth();

    ImNodes::EndNode();

    // Ensure flags such as SocketFlagAffectsTime are properly set.
    node_resource.m_socket_accessor->UpdateFlags();
  }

  for (size_t i = 0, n = sGraphGresource.m_connections.size(); i < n; i++) {
    const AnimGraphConnectionResource& connection =
        sGraphGresource.m_connections[i];
    int start_attr, end_attr;

    const AnimNodeResource& source_node =
        sGraphGresource.m_nodes[connection.source_node_index];
    int source_socket_index = source_node.m_socket_accessor->GetOutputIndex(
        connection.source_socket_name);

    const AnimNodeResource& target_node =
        sGraphGresource.m_nodes[connection.target_node_index];
    int target_socket_index = target_node.m_socket_accessor->GetInputIndex(
        connection.target_socket_name);

    start_attr = GenerateOutputAttributeId(
        connection.source_node_index,
        source_socket_index);
    end_attr = GenerateInputAttributeId(
        connection.target_node_index,
        target_socket_index);

    ImNodes::Link(i, start_attr, end_attr);
  }

  ImNodes::EndNodeEditor();

  // Handle newly created links.
  int start_attr, end_attr;
  if (ImNodes::IsLinkCreated(&start_attr, &end_attr)) {
    int node_start_id;
    int node_start_output_index;
    SplitOutputAttributeId(
        start_attr,
        &node_start_id,
        &node_start_output_index);

    int node_end_id;
    int node_end_input_index;
    SplitInputAttributeId(end_attr, &node_end_id, &node_end_input_index);

    AnimGraphConnectionResource connection;
    connection.source_node_index = node_start_id;
    const AnimNodeResource& source_node =
        sGraphGresource.m_nodes[node_start_id];
    connection.source_socket_name =
        source_node.m_socket_accessor->m_outputs[node_start_output_index]
            .m_name;

    connection.target_node_index = node_end_id;
    const AnimNodeResource& target_node = sGraphGresource.m_nodes[node_end_id];
    connection.target_socket_name =
        target_node.m_socket_accessor->m_inputs[node_end_input_index].m_name;

    sGraphGresource.m_connections.push_back(connection);
  }

  if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) {
    std::cerr << "Delete key!" << std::endl;
  }

  // Handle link detachements.
  int link_id = 0;
  if (ImNodes::IsLinkDestroyed(&link_id)) {
    sGraphGresource.m_connections.erase(
        sGraphGresource.m_connections.begin() + link_id);
  }

  int selected_nodes[ImNodes::NumSelectedNodes()];
  ImNodes::GetSelectedNodes(selected_nodes);

  //
  // Sidebar
  //
  ImGui::NextColumn();

  if (ImNodes::NumSelectedNodes() == 1) {
    if (selected_nodes[0] < sGraphGresource.m_nodes.size()) {
      AnimNodeResource& selected_node =
          sGraphGresource.m_nodes[selected_nodes[0]];
      AnimGraphEditorRenderSidebar(sGraphGresource, selected_node);
    }
  }

  ImGui::Columns(1);
}

void AnimGraphEditorGetRuntimeGraph(AnimGraph& anim_graph) {
  sGraphGresource.createInstance(anim_graph);
}