// DirectedGraph.hpp
//
//     Created on: 30. 01. 2018
//         Author: Jan Uhlik
//    Modified by:
//
// Copyright (c) 2017 Czech Technical University in Prague | Faculty of Information Technology. All rights reserved.
// Git repository: https://gitlab.fit.cvut.cz/algorithms-library-toolkit/automata-library

#ifndef ALIB2_DIRECTEDGRAPH_HPP
#define ALIB2_DIRECTEDGRAPH_HPP

#include <alib/map>
#include <alib/set>
#include <alib/tuple>
#include <alib/vector>
#include <functional>
#include <alib/pair>
#include <object/Object.h>
#include <core/normalize.hpp>

#include <common/Normalize.hpp>
#include <graph/GraphInterface.hpp>
#include <graph/GraphFeatures.hpp>

namespace graph {

template<typename TNode, typename TEdge>
class DirectedGraph : public GraphInterface<TNode, TEdge> {
// ---------------------------------------------------------------------------------------------------------------------

 public:
  using node_type = TNode;
  using edge_type = TEdge;

// ---------------------------------------------------------------------------------------------------------------------

 protected:
  ext::map<TNode, ext::map<TNode, TEdge>> m_succ_list;
  ext::map<TNode, ext::map<TNode, TEdge>> m_pred_list;

// =====================================================================================================================
// Constructor, Destructor, Operators
 public:

// ---------------------------------------------------------------------------------------------------------------------

  const ext::map<TNode, ext::map<TNode, TEdge>> &getSuccessorList() const &;
  ext::map<TNode, ext::map<TNode, TEdge>> &&getSuccessorList() &&;

  const ext::map<TNode, ext::map<TNode, TEdge>> &getPredecessorList() const &;
  ext::map<TNode, ext::map<TNode, TEdge>> &&getPredecessorList() &&;

// ---------------------------------------------------------------------------------------------------------------------

// =====================================================================================================================
// GraphBase interface
 public:
  int compare(const GraphBase &other) const override;

  virtual int compare(const DirectedGraph &other) const;

  void operator>>(std::ostream &ostream) const override;

  explicit operator std::string() const override;

// =====================================================================================================================
// Graph interface

 public:
  void addNode(const TNode &n);
  void addNode(TNode &&n);
  template<typename ... Params>
  void addNode(Params &&... params);

// ---------------------------------------------------------------------------------------------------------------------

  bool addEdge(const TEdge &e);
  bool addEdge(TEdge &&e);
  template<typename ... Params>
  bool addEdge(Params &&... params);

// ---------------------------------------------------------------------------------------------------------------------

// =====================================================================================================================
// GraphInterface interface

  size_t nodeCount() const override;
  size_t edgeCount() const override;

// ---------------------------------------------------------------------------------------------------------------------

  ext::set<TNode> getNodes() const override;
  ext::vector<TEdge> getEdges() const override;

// ---------------------------------------------------------------------------------------------------------------------

  ext::set<TNode> successors(const TNode &n) const override;
  ext::vector<TEdge> successorEdges(const TNode &n) const override;
  ext::set<TNode> predecessors(const TNode &n) const override;
  ext::vector<TEdge> predecessorEdges(const TNode &n) const override;

// ---------------------------------------------------------------------------------------------------------------------

  std::string name() const override;

// ---------------------------------------------------------------------------------------------------------------------

};

// =====================================================================================================================

template<typename TNode, typename TEdge>
const ext::map<TNode, ext::map<TNode, TEdge>> &DirectedGraph<TNode, TEdge>::getSuccessorList() const &{
  return m_succ_list;
}

template<typename TNode, typename TEdge>
ext::map<TNode, ext::map<TNode, TEdge>> &&DirectedGraph<TNode, TEdge>::getSuccessorList() &&{
  return std::move(m_succ_list);
}

template<typename TNode, typename TEdge>
const ext::map<TNode, ext::map<TNode, TEdge>> &DirectedGraph<TNode, TEdge>::getPredecessorList() const &{
  return m_pred_list;
}

template<typename TNode, typename TEdge>
ext::map<TNode, ext::map<TNode, TEdge>> &&DirectedGraph<TNode, TEdge>::getPredecessorList() &&{
  return std::move(m_pred_list);
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
void DirectedGraph<TNode, TEdge>::addNode(const TNode &n) {
  m_succ_list[n];
  m_pred_list[n];
}

template<typename TNode, typename TEdge>
void DirectedGraph<TNode, TEdge>::addNode(TNode &&n) {
  m_succ_list[n];
  m_pred_list[std::forward<TNode>(n)];
}

template<typename TNode, typename TEdge>
template<typename... Params>
void DirectedGraph<TNode, TEdge>::addNode(Params &&... params) {
  addNode(TNode(std::forward<Params>(params) ...));
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
bool DirectedGraph<TNode, TEdge>::addEdge(const TEdge &e) {
  if (e.first == e.second) {
    return false;
  }

  auto search_succ = m_succ_list.find(e.first);
  if (search_succ != m_succ_list.end() && search_succ->second.find(e.second) != search_succ->second.end()) {
    return false;
  }

  addNode(e.first);
  addNode(e.second);
  m_succ_list[e.first].insert(ext::make_pair(e.second, e));
  m_pred_list[e.second].insert(ext::make_pair(e.first, e));

  return true;
}

template<typename TNode, typename TEdge>
bool DirectedGraph<TNode, TEdge>::addEdge(TEdge &&e) {
  if (e.first == e.second) {
    return false;
  }

  auto search_succ = m_succ_list.find(e.first);
  if (search_succ != m_succ_list.end() && search_succ->second.find(e.second) != search_succ->second.end()) {
    return false;
  }

  addNode(e.first);
  addNode(e.second);
  m_succ_list[e.first].insert(ext::make_pair(e.second, e));
  m_pred_list[e.second].insert(ext::make_pair(e.first, std::forward<TEdge>(e)));

  return true;
}

template<typename TNode, typename TEdge>
template<typename... Params>
bool DirectedGraph<TNode, TEdge>::addEdge(Params &&... params) {
  return addEdge(TEdge(std::forward<Params>(params) ...));
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
size_t DirectedGraph<TNode, TEdge>::nodeCount() const {
  return m_succ_list.size();
}
template<typename TNode, typename TEdge>
size_t DirectedGraph<TNode, TEdge>::edgeCount() const {
  size_t cnt = 0;

  for (const auto &i: m_succ_list) {
    cnt += i.second.size();
  }

  return cnt;
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
ext::set<TNode> DirectedGraph<TNode, TEdge>::getNodes() const {
  ext::set<TNode> set;

  for (const auto &i: m_succ_list) {
    set.insert(i.first);
  }

  return set;
}

template<typename TNode, typename TEdge>
ext::vector<TEdge> DirectedGraph<TNode, TEdge>::getEdges() const {
  ext::vector<TEdge> vec;

  for (const auto &i: m_succ_list) {
    for (const auto &j : i.second) {
      vec.push_back(j.second);
    }
  }

  return vec;
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
ext::set<TNode> DirectedGraph<TNode, TEdge>::successors(const TNode &n) const {
  ext::set<TNode> set;

  if (m_succ_list.count(n) == 0) {
    return set;
  }

  for (const auto &i: m_succ_list.at(n)) {
    set.insert(i.first);
  }

  return set;
}

template<typename TNode, typename TEdge>
ext::vector<TEdge> DirectedGraph<TNode, TEdge>::successorEdges(const TNode &n) const {
  ext::vector<TEdge> vec;

  if (m_succ_list.count(n) == 0) {
    return vec;
  }

  for (const auto &i: m_succ_list.at(n)) {
    vec.push_back(i.second);
  }

  return vec;
}

template<typename TNode, typename TEdge>
ext::set<TNode> DirectedGraph<TNode, TEdge>::predecessors(const TNode &n) const {
  ext::set<TNode> set;

  if (m_pred_list.count(n) == 0) {
    return set;
  }

  for (const auto &i: m_pred_list.at(n)) {
    set.insert(i.first);
  }

  return set;
}

template<typename TNode, typename TEdge>
ext::vector<TEdge> DirectedGraph<TNode, TEdge>::predecessorEdges(const TNode &n) const {
  ext::vector<TEdge> vec;

  if (m_pred_list.count(n) == 0) {
    return vec;
  }

  for (const auto &i: m_pred_list.at(n)) {
    vec.push_back(i.second);
  }

  return vec;
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
std::string DirectedGraph<TNode, TEdge>::name() const {
  return "DirectedGraph";
}

// ---------------------------------------------------------------------------------------------------------------------

template<typename TNode, typename TEdge>
int DirectedGraph<TNode, TEdge>::compare(const GraphBase &other) const {
  if (ext::type_index(typeid(*this)) == ext::type_index(typeid(other))) return this->compare((decltype(*this)) other);
  return ext::type_index(typeid(*this)) - ext::type_index(typeid(other));
}

template<typename TNode, typename TEdge>
int DirectedGraph<TNode, TEdge>::compare(const DirectedGraph &other) const {
  auto first = ext::tie(m_succ_list, m_pred_list);
  auto second = ext::tie(other.getSuccessorList(), other.getPredecessorList());

  static ext::compare<decltype(first)> comp;

  return comp(first, second);
}

template<typename TNode, typename TEdge>
void DirectedGraph<TNode, TEdge>::operator>>(std::ostream &ostream) const {
  ostream << "(" << name() << " ";
  for (const auto &i : m_succ_list) {
    ostream << i.first << "  -->" << std::endl;

    for (const auto &j : i.second) {
      ostream << "\t\t" << j.first << " " << j.second << std::endl;
    }
  }
  ostream << ")";
}

template<typename TNode, typename TEdge>
DirectedGraph<TNode, TEdge>::operator std::string() const {
  std::stringstream ss;
  ss << "<";
  for (const auto &i : m_succ_list) {
    ss << i.first << "  -->" << std::endl;

    for (const auto &j : i.second) {
      ss << "\t\t" << j.first << " " << j.second << std::endl;
    }
  }
  ss << ">";
  return std::move(ss).str();
}

// ---------------------------------------------------------------------------------------------------------------------

} // namespace graph

// =====================================================================================================================

namespace core {

/**
 * Helper for normalisation of types specified by templates used as internal datatypes of symbols and states.
 *
 * \returns new instance of the graph with default template parameters or unmodified instance if the template parameters were already the default ones
 */

template<typename TNode, typename TEdge>
struct normalize < graph::DirectedGraph < TNode, TEdge > > {
  static graph::DirectedGraph<> eval(graph::DirectedGraph<TNode, TEdge> &&value) {
    graph::DirectedGraph<> graph;

    // Create successor
    for (auto &&i: ext::make_mover(std::move(value).getSuccessorList())) {
      DefaultNodeType first = common::Normalize::normalizeNode(std::move(i.first));
      for (auto &&j: i.second) {
        DefaultNodeType second = common::Normalize::normalizeNode(std::move(j.first));
        DefaultEdgeType edge = common::Normalize::normalizeEdge(std::move(j.second));

        graph.addNode(first);
        graph.addNode(second);
        graph.addEdge(edge);
      }
    }

    // Create predecessors
    for (auto &&i: ext::make_mover(std::move(value).getPredecessorList())) {
      DefaultNodeType first = common::Normalize::normalizeNode(std::move(i.first));
      for (auto &&j: i.second) {
        DefaultNodeType second = common::Normalize::normalizeNode(std::move(j.first));
        DefaultEdgeType edge = common::Normalize::normalizeEdge(std::move(j.second));

        graph.addNode(first);
        graph.addNode(second);
        graph.addEdge(edge);
      }
    }

    return graph;
  }
};

}

// =====================================================================================================================
#endif //ALIB2_DIRECTEDGRAPH_HPP
