// Copyright (C) 2012  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_MIN_CuT_Hh_
#define DLIB_MIN_CuT_Hh_

#include "min_cut_abstract.h"
#include "../matrix.h"
#include "general_flow_graph.h"
#include "../is_kind.h"

#include <iostream>
#include <fstream>
#include <deque>


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


namespace dlib
{

    typedef unsigned char node_label;

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

    const node_label SOURCE_CUT = 0;
    const node_label SINK_CUT = 254;
    const node_label FREE_NODE = 255;

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

    template <typename flow_graph>
    typename disable_if<is_directed_graph<flow_graph>,typename flow_graph::edge_type>::type 
    graph_cut_score (
        const flow_graph& g
    )
    {
        typedef typename flow_graph::edge_type edge_weight_type;
        edge_weight_type score = 0;
        typedef typename flow_graph::out_edge_iterator out_edge_iterator;
        for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
        {
            if (g.get_label(i) != SOURCE_CUT)
                continue;

            for (out_edge_iterator n = g.out_begin(i); n != g.out_end(i); ++n)
            {
                if (g.get_label(g.node_id(n)) != SOURCE_CUT)
                {
                    score += g.get_flow(n);
                }
            }
        }

        return score;
    }

    template <typename directed_graph>
    typename enable_if<is_directed_graph<directed_graph>,typename directed_graph::edge_type>::type 
    graph_cut_score (
        const directed_graph& g
    )
    {
        return graph_cut_score(dlib::impl::general_flow_graph<const directed_graph>(g));
    }

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

    class min_cut
    {

    public:

        min_cut()
        {
        }

        min_cut( const min_cut& )
        {
            // Intentionally left empty since all the member variables
            // don't logically contribute to the state of this object.
            // This copy constructor is here to explicitly avoid the overhead
            // of copying these transient variables.  
        }

        template <
            typename directed_graph
            >
        typename enable_if<is_directed_graph<directed_graph> >::type operator() (
            directed_graph& g,
            const unsigned long source_node,
            const unsigned long sink_node
        ) const
        {
            DLIB_ASSERT(graph_contains_length_one_cycle(g) == false,
                        "\t void min_cut::operator()"
                        << "\n\t Invalid arguments were given to this function."
                        );
            DLIB_ASSERT(graph_has_symmetric_edges(g) == true,
                        "\t void min_cut::operator()"
                        << "\n\t Invalid arguments were given to this function."
                        );

            dlib::impl::general_flow_graph<directed_graph> temp(g);
            (*this)(temp, source_node, sink_node);
        }

        template <
            typename flow_graph
            >
        typename disable_if<is_directed_graph<flow_graph> >::type operator() (
            flow_graph& g,
            const unsigned long source_node,
            const unsigned long sink_node
        ) const
        {
#ifdef ENABLE_ASSERTS
            DLIB_ASSERT(source_node != sink_node &&
                        source_node < g.number_of_nodes() &&
                        sink_node < g.number_of_nodes(),
                    "\t void min_cut::operator()"
                    << "\n\t Invalid arguments were given to this function."
                    << "\n\t g.number_of_nodes(): " << g.number_of_nodes() 
                    << "\n\t source_node: " << source_node 
                    << "\n\t sink_node:   " << sink_node 
                    << "\n\t this:   " << this
            );

            for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
            {
                typename flow_graph::out_edge_iterator j, end = g.out_end(i);
                for (j = g.out_begin(i); j != end; ++j)
                {
                    const unsigned long jj = g.node_id(j);
                    DLIB_ASSERT(g.get_flow(i,jj) >= 0,
                                "\t void min_cut::operator()"
                                << "\n\t Invalid inputs were given to this function." 
                                << "\n\t i: "<< i 
                                << "\n\t jj: "<< jj
                                << "\n\t g.get_flow(i,jj): "<< g.get_flow(i,jj)
                                << "\n\t this: "<< this
                                );

                }
            }
#endif
            parent.clear();
            active.clear();
            orphans.clear();

            typedef typename flow_graph::edge_type edge_type;
            COMPILE_TIME_ASSERT(is_signed_type<edge_type>::value);

            typedef typename flow_graph::out_edge_iterator out_edge_iterator;
            typedef typename flow_graph::in_edge_iterator in_edge_iterator;

            // initialize labels
            for (unsigned long i = 0; i < g.number_of_nodes(); ++i)
                g.set_label(i, FREE_NODE);

            g.set_label(source_node, SOURCE_CUT);
            g.set_label(sink_node, SINK_CUT);

            // used to indicate "no parent"
            const unsigned long no_parent = g.number_of_nodes();

            parent.assign(g.number_of_nodes(), no_parent);

            time = 1;
            dist.assign(g.number_of_nodes(), 0);
            ts.assign(g.number_of_nodes(), time);

            active.push_back(source_node);
            active.push_back(sink_node);


            in_edge_iterator in_begin = g.in_begin(active[0]);
            out_edge_iterator out_begin = g.out_begin(active[0]);

            unsigned long source_side, sink_side;
            while (grow(g,source_side,sink_side, in_begin, out_begin))
            {
                ++time;
                ts[source_node] = time;
                ts[sink_node] = time;

                augment(g, source_node, sink_node, source_side, sink_side);
                adopt(g, source_node, sink_node);
            }

        }


    private:

        unsigned long distance_to_origin (
            const unsigned long no_parent,
            unsigned long p,
            unsigned long 
        ) const
        {
            unsigned long start = p;
            unsigned long count = 0;
            while (p != no_parent)
            {
                if (ts[p] == time)
                {
                    count += dist[p];

                    unsigned long count_down = count;
                    // adjust the dist and ts for the nodes on this path.
                    while (start != p)
                    {
                        ts[start] = time;
                        dist[start] = count_down;
                        --count_down;
                        start = parent[start];
                    }

                    return count;
                }
                p = parent[p];
                ++count;
            }

            return std::numeric_limits<unsigned long>::max();
        }

        template <typename flow_graph>
        void adopt (
            flow_graph& g,
            const unsigned long source,
            const unsigned long sink
        ) const
        {
            typedef typename flow_graph::out_edge_iterator out_edge_iterator;
            typedef typename flow_graph::in_edge_iterator in_edge_iterator;

            // used to indicate "no parent"
            const unsigned long no_parent = g.number_of_nodes();

            while (orphans.size() > 0)
            {
                const unsigned long p = orphans.back();
                orphans.pop_back();

                const unsigned char label_p = g.get_label(p);

                // Try to find a valid parent for p.
                if (label_p == SOURCE_CUT)
                {
                    const in_edge_iterator begin(g.in_begin(p));
                    const in_edge_iterator end(g.in_end(p));
                    unsigned long best_dist = std::numeric_limits<unsigned long>::max();
                    unsigned long best_node = 0;
                    for(in_edge_iterator q = begin; q != end; ++q)
                    {
                        const unsigned long id = g.node_id(q);

                        if (g.get_label(id) != label_p || g.get_flow(q) <= 0 )
                            continue;

                        unsigned long temp = distance_to_origin(no_parent, id,source);
                        if (temp < best_dist)
                        {
                            best_dist = temp;
                            best_node = id;
                        }

                    }
                    if (best_dist != std::numeric_limits<unsigned long>::max())
                    {
                        parent[p] = best_node;
                        dist[p] = dist[best_node] + 1;
                        ts[p] = time;
                    }

                    // if we didn't find a parent for p
                    if (parent[p] == no_parent)
                    {
                        for(in_edge_iterator q = begin; q != end; ++q)
                        {
                            const unsigned long id = g.node_id(q);

                            if (g.get_label(id) != SOURCE_CUT)
                                continue;

                            if (g.get_flow(q) > 0)
                                active.push_back(id);

                            if (parent[id] == p)
                            {
                                parent[id] = no_parent;
                                orphans.push_back(id);
                            }
                        }
                        g.set_label(p, FREE_NODE);
                    }
                }
                else
                {
                    unsigned long best_node = 0;
                    unsigned long best_dist = std::numeric_limits<unsigned long>::max();
                    const out_edge_iterator begin(g.out_begin(p));
                    const out_edge_iterator end(g.out_end(p));
                    for(out_edge_iterator q = begin; q != end; ++q)
                    {
                        const unsigned long id = g.node_id(q);
                        if (g.get_label(id) != label_p || g.get_flow(q) <= 0)
                            continue;

                        unsigned long temp = distance_to_origin(no_parent, id,sink);

                        if (temp < best_dist)
                        {
                            best_dist = temp;
                            best_node = id;
                        }
                    }

                    if (best_dist != std::numeric_limits<unsigned long>::max())
                    {
                        parent[p] = best_node;
                        dist[p] = dist[best_node] + 1;
                        ts[p] = time;
                    }

                    // if we didn't find a parent for p
                    if (parent[p] == no_parent)
                    {
                        for(out_edge_iterator q = begin; q != end; ++q)
                        {
                            const unsigned long id = g.node_id(q);

                            if (g.get_label(id) != SINK_CUT)
                                continue;

                            if (g.get_flow(q) > 0)
                                active.push_back(id);

                            if (parent[id] == p)
                            {
                                parent[id] = no_parent;
                                orphans.push_back(id);
                            }
                        }

                        g.set_label(p, FREE_NODE);
                    }
                }

                
            }

        }

        template <typename flow_graph>
        void augment (
            flow_graph& g,
            const unsigned long& source,
            const unsigned long& sink,
            const unsigned long& source_side, 
            const unsigned long& sink_side
        ) const
        {
            typedef typename flow_graph::edge_type edge_type;

            // used to indicate "no parent"
            const unsigned long no_parent = g.number_of_nodes();

            unsigned long s = source_side;
            unsigned long t = sink_side;
            edge_type min_cap = g.get_flow(s,t);

            // find the bottleneck capacity on the current path.

            // check from source_side back to the source for the min capacity link.
            t = s;
            while (t != source)
            {
                s = parent[t];
                const edge_type temp = g.get_flow(s, t);
                if (temp < min_cap)
                {
                    min_cap = temp;
                }
                t = s;
            }

            // check from sink_side back to the sink for the min capacity link
            s = sink_side;
            while (s != sink)
            {
                t = parent[s];
                const edge_type temp = g.get_flow(s, t);
                if (temp < min_cap)
                {
                    min_cap = temp;
                }
                s = t;
            }


            // now push the max possible amount of flow though the path
            s = source_side;
            t = sink_side;
            g.adjust_flow(t,s, min_cap);

            // trace back towards the source
            t = s;
            while (t != source)
            {
                s = parent[t];
                g.adjust_flow(t,s, min_cap);
                if (g.get_flow(s,t) <= 0)
                {
                    parent[t] = no_parent;
                    orphans.push_back(t);
                }

                t = s;
            }

            // trace back towards the sink 
            s = sink_side;
            while (s != sink)
            {
                t = parent[s];
                g.adjust_flow(t,s, min_cap);
                if (g.get_flow(s,t) <= 0)
                {
                    parent[s] = no_parent;
                    orphans.push_back(s);
                }
                s = t;
            }
        }

        template <typename flow_graph>
        bool grow (
            flow_graph& g,
            unsigned long& source_side, 
            unsigned long& sink_side,
            typename flow_graph::in_edge_iterator& in_begin,
            typename flow_graph::out_edge_iterator& out_begin
        ) const
        /*!
            ensures
                - if (an augmenting path was found) then 
                    - returns true
                    - (#source_side, #sink_side) == the point where the two trees meet.
                      #source_side is part of the source tree and #sink_side is part of
                      the sink tree.
                - else
                    - returns false
        !*/
        {
            typedef typename flow_graph::out_edge_iterator out_edge_iterator;
            typedef typename flow_graph::in_edge_iterator in_edge_iterator;


            while (active.size() != 0)
            {
                // pick an active node
                const unsigned long A = active[0];

                const unsigned char label_A = g.get_label(A);

                // process its neighbors
                if (label_A == SOURCE_CUT)
                {
                    const out_edge_iterator out_end = g.out_end(A);
                    for(out_edge_iterator& i = out_begin; i != out_end; ++i)
                    {
                        if (g.get_flow(i) > 0)
                        {
                            const unsigned long id = g.node_id(i);
                            const unsigned char label_i = g.get_label(id); 
                            if (label_i == FREE_NODE)
                            {
                                active.push_back(id);
                                g.set_label(id,SOURCE_CUT);
                                parent[id] = A;
                                ts[id] = ts[A];
                                dist[id] = dist[A] + 1;
                            }
                            else if (label_A != label_i)
                            {
                                source_side = A;
                                sink_side = id;
                                return true;
                            }
                            else if (is_closer(A, id))
                            {
                                parent[id] = A;
                                ts[id] = ts[A];
                                dist[id] = dist[A] + 1;
                            }
                        }
                    }
                }
                else if (label_A == SINK_CUT)
                {
                    const in_edge_iterator in_end = g.in_end(A);
                    for(in_edge_iterator& i = in_begin; i != in_end; ++i)
                    {
                        if (g.get_flow(i) > 0)
                        {
                            const unsigned long id = g.node_id(i);
                            const unsigned char label_i = g.get_label(id); 
                            if (label_i == FREE_NODE)
                            {
                                active.push_back(id);
                                g.set_label(id,SINK_CUT);
                                parent[id] = A;
                                ts[id] = ts[A];
                                dist[id] = dist[A] + 1;
                            }
                            else if (label_A != label_i)
                            {
                                sink_side = A;
                                source_side = id;
                                return true;
                            }
                            else if (is_closer(A, id))
                            {
                                parent[id] = A;
                                ts[id] = ts[A];
                                dist[id] = dist[A] + 1;
                            }
                        }
                    }
                }

                active.pop_front();
                if (active.size() != 0)
                {
                    in_begin = g.in_begin(active[0]);
                    out_begin = g.out_begin(active[0]);
                }
            }

            return false;
        }

        inline bool is_closer (
            unsigned long p,
            unsigned long q
        ) const
        {
            // return true if p is closer to a terminal than q
            return ts[q] <= ts[p] && dist[q] > dist[p];
        }

        mutable std::vector<uint32> dist;
        mutable std::vector<uint32> ts;
        mutable uint32 time;
        mutable std::vector<unsigned long> parent;

        mutable std::deque<unsigned long> active;
        mutable std::vector<unsigned long> orphans;
    };

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

}

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

#endif // DLIB_MIN_CuT_Hh_

