//
// Created by martin on 25.03.22.
//

#ifndef ANIMTESTBED_ANIMGRAPH_H
#define ANIMTESTBED_ANIMGRAPH_H

#include "AnimGraphData.h"
#include "AnimGraphNodes.h"

//
// AnimGraph (Runtime)
//
struct AnimGraph {
  AnimData m_local_transforms;

  std::vector<AnimNode*> m_nodes;
  std::vector<AnimNode*> m_eval_ordered_nodes;
  std::vector<std::vector<AnimGraphConnection> > m_node_input_connections;
  std::vector<std::vector<AnimGraphConnection> > m_node_output_connections;
  NodeSocketAccessorBase* m_socket_accessor;
  char* m_input_buffer = nullptr;
  char* m_output_buffer = nullptr;

  std::vector<Socket>& getGraphOutputs() { return m_socket_accessor->m_inputs; }
  std::vector<Socket>& getGraphInputs() { return m_socket_accessor->m_outputs; }

  AnimDataAllocator m_anim_data_allocator;

  ~AnimGraph() { dealloc(); }

  bool init(AnimGraphContext& context);
  void dealloc() {
    if (m_node_input_connections.size() > 0) {
      std::vector<AnimGraphConnection>& graph_outputs =
          m_node_input_connections[0];

      for (size_t i = 0, n = graph_outputs.size(); i < n; i++) {
        AnimGraphConnection& connection = graph_outputs[i];
        if (connection.m_target_socket.m_type
            == SocketType::SocketTypeAnimation) {
          AnimData* graph_anim_output = static_cast<AnimData*>(
              connection.m_target_socket.m_reference.ptr);
          assert(graph_anim_output != nullptr);

          // we have to explicitly call the destructor as the AnimData* was
          // initialized using a placement new operator.
          graph_anim_output->m_local_matrices.vector::~vector();
        }
      }
    }

    delete[] m_input_buffer;
    delete[] m_output_buffer;

    for (int i = 0; i < m_nodes.size(); i++) {
      delete m_nodes[i];
    }

    delete m_socket_accessor;
  }

  void updateOrderedNodes();
  void updateOrderedNodesRecursive(int node_index);
  void markActiveNodes();
  bool checkIsNodeActive(AnimNode* node) {
    return node->m_state != AnimNodeEvalState::Deactivated;
  }

  void evalInputNode();
  void prepareNodeEval(AnimGraphContext& graph_context, size_t node_index);
  void finishNodeEval(size_t node_index);
  void evalOutputNode();

  void evalSyncTracks();
  void updateTime(float dt);
  void evaluate(AnimGraphContext& context);
  void resetNodeStates() {
    for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
      m_nodes[i]->m_time_now = 0.f;
      m_nodes[i]->m_time_last = 0.f;
      m_nodes[i]->m_state = AnimNodeEvalState::Undefined;
    }
  }

  Socket* getInputSocket(const std::string& name);
  Socket* getOutputSocket(const std::string& name);

  const Socket* getInputSocket(const std::string& name) const;
  const Socket* getOutputSocket(const std::string& name) const;

  void* getInputPtr(const std::string& name) const {
    const Socket* input_socket = getInputSocket(name);
    if (input_socket != nullptr) {
      return input_socket->m_reference.ptr;
    }

    return nullptr;
  }

  void* getOutputPtr(const std::string& name) const {
    const Socket* input_socket = getOutputSocket(name);
    if (input_socket != nullptr) {
      return input_socket->m_reference.ptr;
    }

    return nullptr;
  }

  int getNodeEvalOrderIndex(const AnimNode* node) {
    for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) {
      if (m_eval_ordered_nodes[i] == node) {
        return i;
      }
    }

    return -1;
  }
  const AnimNode* getAnimNodeForInput(
      size_t node_index,
      const std::string& input_name) const {
    assert(node_index < m_nodes.size());

    const std::vector<AnimGraphConnection>& input_connection =
        m_node_input_connections[node_index];
    for (size_t i = 0, n = input_connection.size(); i < n; i++) {
      if (input_connection[i].m_target_socket.m_name == input_name) {
        return input_connection[i].m_source_node;
      }
    }

    return nullptr;
  }

  AnimNode* getAnimNode(const char* name) {
    for (size_t i = 0; i < m_nodes.size(); i++) {
      if (m_nodes[i]->m_name == name) {
        return m_nodes[i];
      }
    }

    return nullptr;
  }

  size_t getAnimNodeIndex(AnimNode* node) {
    for (size_t i = 0; i < m_nodes.size(); i++) {
      if (m_nodes[i] == node) {
        return i;
      }
    }

    return -1;
  }
};

#endif  //ANIMTESTBED_ANIMGRAPH_H
