// DFS.hpp
//
//     Created on: 27. 02. 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_DFS_HPP
#define ALIB2_DFS_HPP

#include <functional>
#include <alib/vector>
#include <queue>
#include <alib/set>
#include <alib/map>

#include <common/ReconstructPath.hpp>

namespace graph {

namespace traverse {

class DFS {
// ---------------------------------------------------------------------------------------------------------------------

 public:

  /// Run DFS algorithm from the \p start node in the \p graph.
  ///
  /// Whenever node is opened, \p f_user is called with two parameters (the opened node and distance to this node).
  /// If return of \p f_user is true, then the algorithm is stopped.
  ///
  /// \param graph to explore.
  /// \param start initial node.
  /// \param f_user function which is called for every opened node with value of currently shortest path.
  ///
  template<
      typename TGraph,
      typename TNode,
      typename F = std::function<bool(const TNode &, const size_t &)>>
  static
  void
  run(const TGraph &graph,
      const TNode &start,
      F f_user = [](const TNode &, const size_t &) -> bool { return false; });

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

  /// Find path using DFS algorithm from the \p start node to the \p goal node in the \p graph.
  /// However, it is not quarantee that the return path gonna be the shortest one.
  ///
  /// Whenever node is opened, \p f_user is called with two parameters (the opened node and distance to this node).
  ///
  /// \param graph to explore.
  /// \param start initial node.
  /// \param goal final node.
  /// \param f_user function which is called for every opened node with value of currently shortest path.
  ///
  /// \returns nodes in path, if there is no such path vector is empty.
  ///
  template<
      typename TGraph,
      typename TNode,
      typename F = std::function<void(const TNode &, const size_t &)>>
  static
  ext::vector<TNode>
  findPath(const TGraph &graph,
           const TNode &start,
           const TNode &goal,
           F f_user = [](const TNode &, const size_t &) {});

  template<typename TGraph, typename TNode>
  static
  ext::vector<TNode>
  findPathRegistration(const TGraph &graph,
                       const TNode &start,
                       const TNode &goal) {
    return findPath(graph, start, goal);

  }

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

 private:

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

  template<typename TNode>
  struct Data {
    ext::map<TNode, size_t> d; // distance
    ext::set<TNode> v; // visited
    ext::map<TNode, TNode> p; // parents
  };

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

  template<typename TGraph, typename TNode, typename F1, typename F2>
  static
  ext::vector<TNode>
  impl(const TGraph &graph,
       const TNode &start,
       const TNode &goal,
       F1 f_user,
       F2 f_stop);

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

  template<typename TGraph, typename TNode, typename F1, typename F2>
  static
  bool
  expansion(const TGraph &graph,
            DFS::Data<TNode> &data,
            const TNode &n,
            const TNode &p,
            F1 f_user,
            F2 f_stop);

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

};

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

template<typename TGraph, typename TNode, typename F>
void DFS::run(const TGraph &graph, const TNode &start, F f_user) {
  impl(graph, start, start, f_user, [](const TNode &) -> bool { return false; });
}

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

template<typename TGraph, typename TNode, typename F>
ext::vector<TNode> DFS::findPath(const TGraph &graph, const TNode &start, const TNode &goal, F f_user) {
  return impl(graph, start, goal,
              [&](const TNode &n, const auto &d) -> bool {
                f_user(n, d);
                return false;
              },
              [&](const TNode &n) -> bool { return goal == n; });
}

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

template<typename TGraph, typename TNode, typename F1, typename F2>
ext::vector<TNode> DFS::impl(const TGraph &graph,
                             const TNode &start,
                             const TNode &goal,
                             F1 f_user,
                             F2 f_stop) {
  Data<TNode> data;

  // Init search
  data.d[start] = -1;

  // Start DFS
  expansion(graph, data, start, start, f_user, f_stop);

  return common::ReconstructPath::reconstructPath(data.p, start, goal);
}

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

template<typename TGraph, typename TNode, typename F1, typename F2>
bool
DFS::expansion(const TGraph &graph,
               DFS::Data<TNode> &data,
               const TNode &n,
               const TNode &p,
               F1 f_user,
               F2 f_stop) {
  if (data.v.find(n) != data.v.end()) return false;

  data.v.insert(n);
  data.p.insert_or_assign(n, p);
  data.d[n] = data.d[p] + 1;

  // Run user's function
  if (f_user(n, data.d[n])) {
    return true; // Stop algorithm
  }

  // Stop if reach the goal
  if (f_stop(n)) {
    return true; // Stop algorithm
  }

  for (const auto &i : graph.successors(n)) {
    bool stop = expansion(graph, data, i, n, f_user, f_stop);

    if (stop) {
      return true;
    }
  }

  return false;
}


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


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

} // namespace traverse

} // namespace graph

#endif //ALIB2_DFS_HPP
