// FordFulkerson.cpp
//
//     Created on: 29. 03. 2016
//         Author: Jan Broz
//    Modified by: Jan Uhlik
//
// 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

#include "FordFulkerson.hpp"

#include <unordered_map>  // return value
#include <queue>          // BFS

#include <maximum_flow/FordFulkerson.hpp>
#include <registration/AlgoRegistration.hpp>

namespace graph {

namespace minimum_cut {

static maximum_flow::Capacity getCapacity(const DirectedGraph &graph) {
  maximum_flow::Capacity capacity;

  for (const auto &edge : graph.getEdges()) {
    capacity[edge.first][edge.second] = edge.capacity();
  }

  return capacity;
}

static maximum_flow::Capacity getCapacity(const UndirectedGraph &graph) {
  maximum_flow::Capacity capacity;

  for (const auto &edge : graph.getEdges()) {
    capacity[edge.first][edge.second] = edge.capacity();
    capacity[edge.second][edge.first] = edge.capacity();
  }

  return capacity;
}

static Cut fordfulkerson_impl_dir(const DirectedGraph &graph,
                                  const node::Node &source,
                                  const node::Node &sink) {
  Cut cut;

  maximum_flow::Capacity capacity = getCapacity(graph);

  maximum_flow::Flow flow = maximum_flow::FordFulkerson::findMaximumFlow(graph, source, sink);

  // mark nodes, which are reachable from source
  ext::unordered_map<node::Node, int> state;
  std::queue<node::Node> open;
  ext::vector<std::pair<node::Node, node::Node>> candidates;
  for (const node::Node &node : graph.getNodes())
    state[node] = 0;
  state[source] = 1;
  open.push(source);
  while (!open.empty()) {
    node::Node actual = open.front();
    open.pop();
    for (const node::Node &succ : graph.successors(actual)) {
      if (state[succ] == 0 && flow[actual][succ] < capacity[actual][succ]) {
        state[succ] = 1;
        open.push(succ);
      } else if (flow[actual][succ] == capacity[actual][succ]) {
        candidates.push_back({actual, succ});
      }
    }
    for (const node::Node &pred : graph.predecessors(actual)) {
      if (state[pred] == 0 && flow[pred][actual] > 0) {
        state[pred] = 1;
        open.push(pred);
      } else if (flow[pred][actual] == 0) {
        candidates.push_back({pred, actual});
      }
    }
  }

  // cut are those edges, which lead from nodes reachable from source to nodes unreachable from source
  for (const std::pair<node::Node, node::Node> & edge : candidates) {
    if ((state[edge.first] == 1 && state[edge.second] == 0)
        || (state[edge.first] == 0 && state[edge.second] == 1))
      cut.insert(edge);
  }

  return cut;
}

static Cut fordfulkerson_impl_undir(const UndirectedGraph &ugraph,
                                    const node::Node &source,
                                    const node::Node &sink) {
  Cut cut;

  maximum_flow::Capacity capacity = getCapacity(ugraph);

  maximum_flow::Flow flow = maximum_flow::FordFulkerson::findMaximumFlow(ugraph, source, sink);

  // mark nodes, which are reachable from source
  ext::unordered_map<node::Node, int> state;
  std::queue<node::Node> open;
  ext::vector<std::pair<node::Node, node::Node>> candidates;
  for (const node::Node &node : ugraph.getNodes())
    state[node] = 0;
  state[source] = 1;
  open.push(source);
  while (!open.empty()) {
    node::Node actual = open.front();
    open.pop();
    for (const node::Node &neigh : ugraph.successors(actual)) {
      if (state[neigh] == 0 && flow[actual][neigh] < capacity[actual][neigh]) {
        state[neigh] = 1;
        open.push(neigh);
      } else if (flow[actual][neigh] == capacity[actual][neigh]) {
        candidates.push_back({actual, neigh});
      }
    }
  }

  // cut are those edges, which lead from nodes reachable from source to nodes unreachable from source
  for (const std::pair<node::Node, node::Node> & edge : candidates) {
    if ((state[edge.first] == 1 && state[edge.second] == 0)
        || (state[edge.first] == 0 && state[edge.second] == 1))
      cut.insert(edge);
  }

  return cut;
}

Cut FordFulkerson::findMinimumCut(const DirectedGraph &graph,
                                  const node::Node &source,
                                  const node::Node &sink) {
  return fordfulkerson_impl_dir(graph, source, sink);
}

Cut FordFulkerson::findMinimumCut(const UndirectedGraph &graph,
                                  const node::Node &source,
                                  const node::Node &sink) {
  return fordfulkerson_impl_undir(graph, source, sink);
}

//auto BasicAlgorithmUndirectedGraph = registration::OverloadRegister < FordFulkerson, UndirectedGraph, UndirectedGraph > ( FordFulkerson::findMinimumCut );

} // namespace minimumcut

} // namespace graph

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

namespace {
auto FordFulkersonUndirected = registration::AbstractRegister<graph::minimum_cut::FordFulkerson,
                                                              graph::minimum_cut::Cut,
                                                              const graph::minimum_cut::UndirectedGraph &,
                                                              const node::Node &,
                                                              const node::Node &>(
    graph::minimum_cut::FordFulkerson::findMinimumCut);

auto FordFulkersonDirected = registration::AbstractRegister<graph::minimum_cut::FordFulkerson,
                                                            graph::minimum_cut::Cut,
                                                            const graph::minimum_cut::DirectedGraph &,
                                                            const node::Node &,
                                                            const node::Node &>(
    graph::minimum_cut::FordFulkerson::findMinimumCut);
}
