//***************************************************************************
//* Copyright (c) 2015 Saint Petersburg State University
//* Copyright (c) 2011-2014 Saint Petersburg Academic University
//* All Rights Reserved
//* See file LICENSE for details.
//***************************************************************************

/*
 * loop_traverser.hpp
 *
 *  Created on: Jan 28, 2013
 *      Author: ira
 */

#ifndef LOOP_TRAVERSER_H_
#define LOOP_TRAVERSER_H_

#include "path_extender.hpp"
#include "pe_resolver.hpp"
#include "path_visualizer.hpp"

namespace path_extend {

class LoopTraverser {

    const Graph& g_;
    GraphCoverageMap& covMap_;
    shared_ptr<ContigsMaker> extender_;
private:
    EdgeId FindStart(const set<VertexId>& component_set) const{
        EdgeId result;
        for (auto it = component_set.begin(); it != component_set.end(); ++it) {
            for (auto eit = g_.in_begin(*it); eit != g_.in_end(*it); ++eit) {
                if (component_set.count(g_.EdgeStart(*eit)) == 0) {
                    if (result != EdgeId()) {
                        return EdgeId();
                    }
                    result = *eit;
                }
            }
        }
        return result;
    }

    EdgeId FindFinish(const set<VertexId>& component_set) {
        EdgeId result;
        for (auto it = component_set.begin(); it != component_set.end(); ++it) {
            for (auto I = g_.out_begin(*it), E = g_.out_end(*it);
                    I != E; ++I) {
                if (component_set.count(g_.EdgeEnd(*I)) == 0) {
                    if (result != EdgeId()) {
                        return EdgeId();
                    }
                    result = *I;
                }
            }
        }
        return result;
    }

    void TryToGrow(BidirectionalPath* path, EdgeId component_entrance) {
        BidirectionalPath clone = *path;
        extender_->GrowPathSimple(*path);
        if (!path->Contains(component_entrance)) {
            DEBUG("Grown paths do not contain initial edges, rolling back");
            path->Clear();
            path->PushBack(clone);
        }
    }

    bool IsEndInsideComponent(const BidirectionalPath &path,
                              const set <VertexId> &component_set) {
        if (component_set.count(g_.EdgeStart(path.Front())) == 0) {
            return false;
        }
        for (size_t i = 0; i < path.Size(); ++i) {
            if (component_set.count(g_.EdgeEnd(path.At(i))) == 0)
                return false;
        }
        return true;
    }


    bool IsEndInsideComponent(const BidirectionalPath &path, EdgeId component_entrance,
                              const set <VertexId> &component_set,
                              bool conjugate = false) {
        int i = path.FindLast(component_entrance);
        VERIFY_MSG(i != -1, "Component edge is not found in the path")

        if ((size_t) i == path.Size() - 1) {
            if (conjugate)
                return component_set.count(g_.conjugate(g_.EdgeEnd(path.Back()))) > 0;
            else
                return component_set.count(g_.EdgeEnd(path.Back())) > 0;
        }

        if (conjugate)
            return IsEndInsideComponent(path.SubPath((size_t) i + 1).Conjugate(), component_set);
        else
            return IsEndInsideComponent(path.SubPath((size_t) i + 1), component_set);
    }

    void TraverseLoop(EdgeId start, EdgeId end, const set<VertexId>& component_set) {
        DEBUG("start " << g_.int_id(start) << " end " << g_.int_id(end));
        BidirectionalPathSet coveredStartPaths =
                covMap_.GetCoveringPaths(start);
        BidirectionalPathSet coveredEndPaths =
                covMap_.GetCoveringPaths(end);

        for (auto it_path = coveredStartPaths.begin();
                it_path != coveredStartPaths.end(); ++it_path) {
            if ((*it_path)->FindAll(end).size() > 0) {
                return;
            }
        }
        if (coveredStartPaths.size() < 1 or coveredEndPaths.size() < 1) {
            DEBUG("TraverseLoop STRANGE SITUATION: start " << coveredStartPaths.size() << " end " << coveredEndPaths.size());
            return;
        }

        if (coveredStartPaths.size() > 1 or coveredEndPaths.size() > 1) {
            DEBUG("Ambiguous situation in path joining, quitting");
            return;
        }

        BidirectionalPath* startPath = *coveredStartPaths.begin();
        BidirectionalPath* endPath = *coveredEndPaths.begin();
        if ((*startPath) == endPath->Conjugate()){
            return;
        }

        //TryToGrow(startPath, start);
        //TryToGrow(endPath->GetConjPath(), g_.conjugate(end));

        //Checking that paths ends are within component
        if (!IsEndInsideComponent(*startPath, start, component_set) ||
                !IsEndInsideComponent(*endPath->GetConjPath(), g_.conjugate(end), component_set, true)) {
            DEBUG("Some path goes outside of the component")
            return;
        }

        size_t commonSize = startPath->CommonEndSize(*endPath);
        size_t nLen = 0;
        DEBUG("Str " << startPath->Size() << ", end" << endPath->Size());
        if (commonSize == 0 && !startPath->Empty() > 0 && !endPath->Empty()) {
            DEBUG("Estimating gap size");
            VertexId lastVertex = g_.EdgeEnd(startPath->Back());
            VertexId firstVertex = g_.EdgeStart(endPath->Front());

            if (firstVertex == lastVertex) {
                nLen = 0;
            } else {
                DijkstraHelper<Graph>::BoundedDijkstra dijkstra(DijkstraHelper<Graph>::CreateBoundedDijkstra(g_, 1000, 3000));
                dijkstra.Run(lastVertex);
                vector<EdgeId> shortest_path = dijkstra.GetShortestPathTo(g_.EdgeStart(endPath->Front()));

                if (shortest_path.size() == 0) {
                    DEBUG("Failed to find closing path");
                    return;
                } else if (!IsEndInsideComponent(BidirectionalPath(g_, shortest_path), component_set)) {
                    DEBUG("Closing path is outside the component");
                    return;
                } else {
                    for (size_t i = 0; i < shortest_path.size(); ++i) {
                        nLen += g_.length(shortest_path[i]);
                    }
                    nLen += g_.k();
                }
            }
        }
        if (commonSize < endPath->Size()){
            startPath->PushBack(endPath->At(commonSize), (int) nLen);
        }
        for (size_t i = commonSize + 1; i < endPath->Size(); ++i) {
            startPath->PushBack(endPath->At(i), endPath->GapAt(i), endPath->TrashPreviousAt(i), endPath->TrashCurrentAt(i));
        }
        DEBUG("travers");
        startPath->Print();
        endPath->Print();
        DEBUG("conj");
        endPath->GetConjPath()->Print();
        endPath->Clear();
    }

public:
    LoopTraverser(const Graph& g, GraphCoverageMap& coverageMap, shared_ptr<ContigsMaker> extender) :
            g_(g), covMap_(coverageMap), extender_(extender) {
    }

    void TraverseAllLoops() {
        DEBUG("TraverseAllLoops");
        shared_ptr<GraphSplitter<Graph>> splitter = LongEdgesExclusiveSplitter<Graph>(g_, 1000);
        while (splitter->HasNext()) {
            GraphComponent<Graph> component = splitter->Next();
            if (component.v_size() > 10)
                continue;
            set<VertexId> component_set(component.v_begin(), component.v_end());
            EdgeId start = FindStart(component_set);
            EdgeId finish = FindFinish(component_set);
            if (start == EdgeId() || finish == EdgeId()) {
                continue;
            }
            TraverseLoop(start, finish, component_set);
        }

    }
protected:
    DECL_LOGGER("LoopTraverser");
};

}

#endif /* LOOP_TRAVERSER_H_ */
