/*
    open source routing machine
    Copyright (C) Dennis Luxen, others 2010

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU AFFERO General Public License as published by
the Free Software Foundation; either version 3 of the License, or
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
or see http://www.gnu.org/licenses/agpl.txt.
 */

#ifndef STATICGRAPH_H_INCLUDED
#define STATICGRAPH_H_INCLUDED

#include "../DataStructures/Percent.h"
#include "../Util/SimpleLogger.h"
#include "../typedefs.h"

#include <algorithm>
#include <vector>

template< typename EdgeDataT>
class StaticGraph {
public:
    typedef NodeID NodeIterator;
    typedef NodeID EdgeIterator;
    typedef EdgeDataT EdgeData;
    class InputEdge {
    public:
        EdgeDataT data;
        NodeIterator source;
        NodeIterator target;
        bool operator<( const InputEdge& right ) const {
            if ( source != right.source )
                return source < right.source;
            return target < right.target;
        }
    };

    struct _StrNode {
        //index of the first edge
        EdgeIterator firstEdge;
    };

    struct _StrEdge {
        NodeID target;
        EdgeDataT data;
    };

    StaticGraph( const int nodes, std::vector< InputEdge > &graph ) {
        std::sort( graph.begin(), graph.end() );
        _numNodes = nodes;
        _numEdges = ( EdgeIterator ) graph.size();
        _nodes.resize( _numNodes + 1);
        EdgeIterator edge = 0;
        EdgeIterator position = 0;
        for ( NodeIterator node = 0; node <= _numNodes; ++node ) {
            EdgeIterator lastEdge = edge;
            while ( edge < _numEdges && graph[edge].source == node )
                ++edge;
            _nodes[node].firstEdge = position; //=edge
            position += edge - lastEdge; //remove
        }
        _edges.resize( position ); //(edge)
        edge = 0;
        for ( NodeIterator node = 0; node < _numNodes; ++node ) {
            for ( EdgeIterator i = _nodes[node].firstEdge, e = _nodes[node+1].firstEdge; i != e; ++i ) {
                _edges[i].target = graph[edge].target;
                _edges[i].data = graph[edge].data;
                assert(_edges[i].data.distance > 0);
                edge++;
            }
        }
    }

    StaticGraph( std::vector<_StrNode> & nodes, std::vector<_StrEdge> & edges) {
        _numNodes = nodes.size();
        _numEdges = edges.size();

        _nodes.swap(nodes);
        _edges.swap(edges);

        //Add dummy node to end of _nodes array;
        _nodes.push_back(_nodes.back());

#ifndef NDEBUG
        Percent p(GetNumberOfNodes());
        for(unsigned u = 0; u < GetNumberOfNodes(); ++u) {
            for(unsigned eid = BeginEdges(u); eid < EndEdges(u); ++eid) {
                unsigned v = GetTarget(eid);
                EdgeData & data = GetEdgeData(eid);
                if(data.shortcut) {
                    unsigned eid2 = FindEdgeInEitherDirection(u, data.id);
                    if(eid2 == UINT_MAX) {
                        SimpleLogger().Write(logWARNING) <<
                            "cannot find first segment of edge (" <<
                                u << "," << data.id << "," << v << ")";

                        data.shortcut = false;
                    }
                    eid2 = FindEdgeInEitherDirection(data.id, v);
                    if(eid2 == UINT_MAX) {
                        SimpleLogger().Write(logWARNING) <<
                            "cannot find second segment of edge (" <<
                                u << "," << data.id << "," << v << ")";
                        data.shortcut = false;
                    }
                }
            }
            p.printIncrement();
        }
#endif
    }

    unsigned GetNumberOfNodes() const {
        return _numNodes;
    }

    unsigned GetNumberOfEdges() const {
        return _numEdges;
    }

    unsigned GetOutDegree( const NodeIterator &n ) const {
        return BeginEdges(n)-EndEdges(n) - 1;
    }

    inline NodeIterator GetTarget( const EdgeIterator &e ) const {
        return NodeIterator( _edges[e].target );
    }

    inline EdgeDataT &GetEdgeData( const EdgeIterator &e ) {
        return _edges[e].data;
    }

    const EdgeDataT &GetEdgeData( const EdgeIterator &e ) const {
        return _edges[e].data;
    }

    EdgeIterator BeginEdges( const NodeIterator &n ) const {
        return EdgeIterator( _nodes[n].firstEdge );
    }

    EdgeIterator EndEdges( const NodeIterator &n ) const {
        return EdgeIterator( _nodes[n+1].firstEdge );
    }

    //searches for a specific edge
    EdgeIterator FindEdge( const NodeIterator &from, const NodeIterator &to ) const {
        EdgeIterator smallestEdge = SPECIAL_EDGEID;
        EdgeWeight smallestWeight = UINT_MAX;
        for ( EdgeIterator edge = BeginEdges( from ); edge < EndEdges(from); edge++ ) {
            const NodeID target = GetTarget(edge);
            const EdgeWeight weight = GetEdgeData(edge).distance;
            if(target == to && weight < smallestWeight) {
                smallestEdge = edge; smallestWeight = weight;
            }
        }
        return smallestEdge;
    }

    EdgeIterator FindEdgeInEitherDirection( const NodeIterator &from, const NodeIterator &to ) const {
        EdgeIterator tmp =  FindEdge( from, to );
        return (UINT_MAX != tmp ? tmp : FindEdge( to, from ));
    }

    EdgeIterator FindEdgeIndicateIfReverse( const NodeIterator &from, const NodeIterator &to, bool & result ) const {
        EdgeIterator tmp =  FindEdge( from, to );
        if(UINT_MAX == tmp) {
            tmp =  FindEdge( to, from );
            if(UINT_MAX != tmp)
                result = true;
        }
        return tmp;
    }

private:

    NodeIterator _numNodes;
    EdgeIterator _numEdges;

    std::vector< _StrNode > _nodes;
    std::vector< _StrEdge > _edges;
};

#endif // STATICGRAPH_H_INCLUDED