/*
    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 CONTRACTIONCLEANUP_H_INCLUDED
#define CONTRACTIONCLEANUP_H_INCLUDED

#ifdef _GLIBCXX_PARALLEL
#include <parallel/algorithm>
#else
#include <algorithm>
#endif
#ifdef _WIN32
#include <time.h>
#else
#include <sys/time.h>
#endif
#include "Contractor.h"

#ifdef _OPENMP
#include <omp.h>
#endif

class ContractionCleanup {
private:

    struct _HeapData {
        NodeID parent;
        _HeapData( NodeID p ) {
            parent = p;
        }
    };
#ifdef _MANYCORES
    typedef BinaryHeap< NodeID, NodeID, int, _HeapData, DenseStorage<NodeID, NodeID> > _Heap;
#else
    typedef BinaryHeap< NodeID, NodeID, int, _HeapData > _Heap;
#endif
    struct _ThreadData {
        _Heap* _heapForward;
        _Heap* _heapBackward;
        _ThreadData( NodeID nodes ) {
            _heapBackward = new _Heap(nodes);
            _heapForward = new _Heap(nodes);
        }
        ~_ThreadData()
        {
            delete _heapBackward;
            delete _heapForward;
        }
    };
    union _MiddleName {
        NodeID nameID;
        NodeID middle;
    };

public:

    struct Edge {
        NodeID source;
        NodeID target;
        struct EdgeData {
            int distance;
            bool shortcut;
            bool forward;
            bool backward;
            short type;
            _MiddleName middleName;
        } data;

        //sorts by source and other attributes
        static bool CompareBySource( const Edge& left, const Edge& right ) {
            if ( left.source != right.source )
                return left.source < right.source;
            int l = ( left.data.forward ? -1 : 0 ) + ( left.data.backward ? -1 : 0 );
            int r = ( right.data.forward ? -1 : 0 ) + ( right.data.backward ? -1 : 0 );
            if ( l != r )
                return l < r;
            if ( left.target != right.target )
                return left.target < right.target;
            return left.data.distance < right.data.distance;
        }

        bool operator== ( const Edge& right ) const {
            return ( source == right.source && target == right.target && data.distance == right.data.distance && data.shortcut == right.data.shortcut && data.forward == right.data.forward && data.backward == right.data.backward && data.middleName.middle == right.data.middleName.middle );
        }
    };

    ContractionCleanup( int numNodes, const std::vector< Edge >& edges ) {
        _graph = edges;
        _numNodes = numNodes;
    }

    ~ContractionCleanup() {

    }

    void Run() {
        RemoveUselessShortcuts();
    }

    template< class EdgeT >
    void GetData( std::vector< EdgeT >& edges ) {
        for ( int edge = 0, endEdges = ( int ) _graph.size(); edge != endEdges; ++edge ) {
            EdgeT newEdge;
            newEdge.source = _graph[edge].source;
            newEdge.target = _graph[edge].target;

            newEdge.data.distance = _graph[edge].data.distance;
            newEdge.data.shortcut = _graph[edge].data.shortcut;
            newEdge.data.middleName = _graph[edge].data.middleName;
            newEdge.data.forward = _graph[edge].data.forward;
            newEdge.data.backward = _graph[edge].data.backward;
            newEdge.data.type = _graph[edge].data.type;
            edges.push_back( newEdge );
        }
#ifdef _GLIBCXX_PARALLEL
        __gnu_parallel::sort( edges.begin(), edges.end() );
#else
        sort( edges.begin(), edges.end() );
#endif
    }

private:

    class AllowForwardEdge {
    public:
        bool operator()( const Edge& data ) const {
            return data.data.forward;
        }
    };

    class AllowBackwardEdge {
    public:
        bool operator()( const Edge& data ) const {
            return data.data.backward;
        }
    };

    double _Timestamp() {
#ifdef _WIN32
		// not currently used
		time_t t = time(NULL);
		return (double)t;
#else
        struct timeval tp;
        gettimeofday(&tp, NULL);
        return double(tp.tv_sec) + tp.tv_usec / 1000000.;
#endif
    }

    void BuildOutgoingGraph() {
        //sort edges by source
#ifdef _GLIBCXX_PARALLEL
        __gnu_parallel::sort( _graph.begin(), _graph.end(), Edge::CompareBySource );
#else
        sort( _graph.begin(), _graph.end(), Edge::CompareBySource );
#endif
        try {
            _firstEdge.resize( _numNodes + 1 );
        } catch(...) {
            cerr << "Not enough RAM on machine" << endl;
        }
        _firstEdge[0] = 0;
        for ( NodeID i = 0, node = 0; i < ( NodeID ) _graph.size(); i++ ) {
            while ( _graph[i].source != node )
                _firstEdge[++node] = i;
            if ( i == ( NodeID ) _graph.size() - 1 )
                while ( node < _numNodes )
                    _firstEdge[++node] = ( int ) _graph.size();
        }
    }

    void RemoveUselessShortcuts() {
        int maxThreads = omp_get_max_threads();
        std::vector < _ThreadData* > threadData;
        for ( int threadNum = 0; threadNum < maxThreads; ++threadNum ) {
            threadData.push_back( new _ThreadData( _numNodes ) );
        }

        //cout << "Scanning for useless shortcuts" << endl;
        BuildOutgoingGraph();
#pragma omp parallel for
        for ( int i = 0; i < (int)_graph.size(); i++ ) {
            for ( unsigned edge = _firstEdge[_graph[i].source]; edge < _firstEdge[_graph[i].source + 1]; ++edge ) {
                if ( edge == (unsigned)i )
                    continue;
                if ( _graph[edge].target != _graph[i].target )
                    continue;
                if ( _graph[edge].data.distance < _graph[i].data.distance )
                    continue;

                _graph[edge].data.forward &= !_graph[i].data.forward;
                _graph[edge].data.backward &= !_graph[i].data.backward;
            }

            if ( !_graph[i].data.forward && !_graph[i].data.backward )
                continue;

            //only remove shortcuts
            if ( !_graph[i].data.shortcut )
                continue;

            if ( _graph[i].data.forward ) {
                int result = _ComputeDistance( _graph[i].source, _graph[i].target, threadData[omp_get_thread_num()] );
                if ( result < _graph[i].data.distance ) {
                    _graph[i].data.forward = false;
                }
            }
            if ( _graph[i].data.backward ) {
                int result = _ComputeDistance( _graph[i].target, _graph[i].source, threadData[omp_get_thread_num()] );

                if ( result < _graph[i].data.distance ) {
                    _graph[i].data.backward = false;
                }
            }
        }

        //cout << "Removing edges" << endl;
        int usefull = 0;
        for ( int i = 0; i < ( int ) _graph.size(); i++ ) {
            if ( !_graph[i].data.forward && !_graph[i].data.backward && _graph[i].data.shortcut )
                continue;
            _graph[usefull] = _graph[i];
            usefull++;
        }
        //cout << "Removed " << _graph.size() - usefull << " useless shortcuts" << endl;
        _graph.resize( usefull );
        for ( int threadNum = 0; threadNum < maxThreads; ++threadNum ) {
            delete threadData[threadNum];
        }
    }

    template< class EdgeAllowed, class StallEdgeAllowed > void _ComputeStep( _Heap* heapForward, _Heap* heapBackward, const EdgeAllowed& edgeAllowed, const StallEdgeAllowed& stallEdgeAllowed, NodeID* middle, int* targetDistance ) {

        const NodeID node = heapForward->DeleteMin();
        const int distance = heapForward->GetKey( node );

        if ( heapBackward->WasInserted( node ) ) {
            const int newDistance = heapBackward->GetKey( node ) + distance;
            if ( newDistance < *targetDistance ) {
                *middle = node;
                *targetDistance = newDistance;
            }
        }

        if ( distance > *targetDistance ) {
            heapForward->DeleteAll();
            return;
        }
        for ( int edge = _firstEdge[node], endEdges = _firstEdge[node + 1]; edge != endEdges; ++edge ) {
            const NodeID to = _graph[edge].target;
            const int edgeWeight = _graph[edge].data.distance;
            assert( edgeWeight > 0 );
            const int toDistance = distance + edgeWeight;

            if ( edgeAllowed( _graph[edge] ) ) {
                //New Node discovered -> Add to Heap + Node Info Storage
                if ( !heapForward->WasInserted( to ) )
                    heapForward->Insert( to, toDistance, node );

                //Found a shorter Path -> Update distance
                else if ( toDistance < heapForward->GetKey( to ) ) {
                    heapForward->DecreaseKey( to, toDistance );
                    //new parent
                    heapForward->GetData( to ) = node;
                }
            }
        }
    }

    int _ComputeDistance( NodeID source, NodeID target, _ThreadData * data, std::vector< NodeID >* path = NULL ) {
        data->_heapForward->Clear();
        data->_heapBackward->Clear();
        //insert source into heap
        data->_heapForward->Insert( source, 0, source );
        data->_heapBackward->Insert( target, 0, target );

        int targetDistance = std::numeric_limits< int >::max();
        NodeID middle = 0;
        AllowForwardEdge forward;
        AllowBackwardEdge backward;

        while ( data->_heapForward->Size() + data->_heapBackward->Size() > 0 ) {

            if ( data->_heapForward->Size() > 0 ) {
                _ComputeStep( data->_heapForward, data->_heapBackward, forward, backward, &middle, &targetDistance );
            }

            if ( data->_heapBackward->Size() > 0 ) {
                _ComputeStep( data->_heapBackward, data->_heapForward, backward, forward, &middle, &targetDistance );
            }
        }

        if ( targetDistance == std::numeric_limits< int >::max() )
            return std::numeric_limits< unsigned >::max();

        return targetDistance;
    }
    NodeID _numNodes;
    std::vector< Edge > _graph;
    std::vector< unsigned > _firstEdge;
};

#endif // CONTRACTIONCLEANUP_H_INCLUDED
