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

#pragma once

namespace omnigraph {

template<class Graph>
struct vertex_neighbour {
protected:
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
public:
    VertexId     vertex;
    EdgeId         edge;

    vertex_neighbour(VertexId new_vertex, EdgeId new_edge) :
        vertex(new_vertex),
        edge(new_edge) { }
};

template<class Graph>
class NeighbourIterator {
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
protected:
    const Graph &graph_;
    VertexId vertex_;
public:
    NeighbourIterator(const Graph &graph, VertexId vertex) :
        graph_(graph),
        vertex_(vertex) { }

    virtual bool HasNext() = 0;
    virtual vertex_neighbour<Graph> Next() = 0;
    virtual  ~NeighbourIterator() { }
};

template<class Graph>
class ForwardNeighbourIterator : public NeighbourIterator<Graph>{
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    typedef typename VertexId::type::edge_const_iterator edge_const_iterator;

    pair<edge_const_iterator, edge_const_iterator> out_edges_;
public:
    ForwardNeighbourIterator(const Graph &graph, VertexId vertex) :
        NeighbourIterator<Graph>(graph, vertex),
        out_edges_(make_pair(graph.OutgoingEdges(vertex).begin(),
                graph.OutgoingEdges(vertex).end())) { }

    bool HasNext(){
        return out_edges_.first != out_edges_.second;
    }

    vertex_neighbour<Graph> Next() {
        vertex_neighbour<Graph> res(this->graph_.EdgeEnd(*out_edges_.first), *out_edges_.first);
        out_edges_.first++;
        return res;
    }
};

template<class Graph>
class BackwardNeighbourIterator : public NeighbourIterator<Graph>{
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    typedef typename VertexId::type::edge_const_iterator edge_const_iterator;

    pair<edge_const_iterator, edge_const_iterator>  in_edges_;
public:
    BackwardNeighbourIterator(const Graph &graph, VertexId vertex) :
        NeighbourIterator<Graph>(graph, vertex),
        in_edges_(make_pair(graph.IncomingEdges(vertex).begin(),
                graph.IncomingEdges(vertex).end())) { }

    bool HasNext(){
        return in_edges_.first != in_edges_.second;
    }

    vertex_neighbour<Graph> Next() {
        vertex_neighbour<Graph> res(this->graph_.EdgeStart(*in_edges_.first), *in_edges_.first);
        in_edges_.first++;
        return res;
    }
};

template<class Graph>
class UnorientedNeighbourIterator : public NeighbourIterator<Graph>{
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    typedef typename VertexId::type::edge_const_iterator edge_const_iterator;

    pair<edge_const_iterator, edge_const_iterator>  in_edges_;
    pair<edge_const_iterator, edge_const_iterator>  out_edges_;
public:
    UnorientedNeighbourIterator(const Graph &graph, VertexId vertex) :
        NeighbourIterator<Graph>(graph, vertex),
        in_edges_(make_pair(graph.IncomingEdges(vertex).begin(),
                graph.IncomingEdges(vertex).end())),
        out_edges_(make_pair(graph.OutgoingEdges(vertex).begin(),
                graph.OutgoingEdges(vertex).end())) { }

    bool HasNext(){
        return in_edges_.first != in_edges_.second;
    }

    // first all outgoing edges are visited
    // then all incoming
    vertex_neighbour<Graph> Next() {
        if(out_edges_.first != out_edges_.second){
            vertex_neighbour<Graph> res(this->graph_.EdgeEnd(*out_edges_.first), *out_edges_.first);
            out_edges_.first++;
            return res;
        }
        vertex_neighbour<Graph> res(this->graph_.EdgeStart(*in_edges_.first), *in_edges_.first);
        in_edges_.first++;
        return res;
    }
};

template<class Graph>
class ForwardNeighbourIteratorFactory {
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    const Graph &graph_;
public:
    typedef ForwardNeighbourIterator<Graph> NeighbourIterator;
    ForwardNeighbourIteratorFactory(const Graph &graph) : graph_(graph) { }
    NeighbourIterator CreateIterator(VertexId vertex){
        return NeighbourIterator(graph_, vertex);
    }
};

template<class Graph>
class BackwardNeighbourIteratorFactory {
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    const Graph &graph_;
public:
    typedef BackwardNeighbourIterator<Graph> NeighbourIterator;
    BackwardNeighbourIteratorFactory(const Graph &graph) : graph_(graph) { }
    NeighbourIterator CreateIterator(VertexId vertex){
        return NeighbourIterator(graph_, vertex);
    }
};

template<class Graph>
class UnorientedNeighbourIteratorFactory {
    typedef typename Graph::VertexId VertexId;
    typedef typename Graph::EdgeId EdgeId;
    const Graph &graph_;
public:
    typedef UnorientedNeighbourIterator<Graph> NeighbourIterator;
    UnorientedNeighbourIteratorFactory(const Graph &graph) : graph_(graph) { }
    NeighbourIterator CreateIterator(VertexId vertex){
        return NeighbourIterator(graph_, vertex);
    }
};

}
