// -*- C++ -*- (c) 2007, 2008 Petr Rockai <me@mornfall.net>

#include <divine/algorithm/common.h>
#include <divine/algorithm/metrics.h>

#include <divine/graph/ltlce.h>

#ifndef DIVINE_ALGORITHM_REACHABILITY_H
#define DIVINE_ALGORITHM_REACHABILITY_H

namespace divine {
namespace algorithm {

template < typename Handle >
struct ReachabilityShared {
    Handle goal;
    bool deadlocked;
    algorithm::Statistics stats;
    CeShared< Blob, Handle > ce;
    bool need_expand;
    ReachabilityShared() : need_expand( false ) {}
};

template< typename BS, typename Handle >
typename BS::bitstream &operator<<( BS &bs, ReachabilityShared< Handle > st )
{
    return bs << st.goal << st.deadlocked << st.stats << st.ce << st.need_expand;
}

template< typename BS, typename Handle >
typename BS::bitstream &operator>>( BS &bs, ReachabilityShared< Handle > &st )
{
    return bs >> st.goal >> st.deadlocked >> st.stats >> st.ce >> st.need_expand;
}

/**
 * A simple parallel reachability analysis implementation. Nothing to worry
 * about here.
 */
template< typename Setup >
struct Reachability : Algorithm, AlgorithmUtils< Setup >,
                      Parallel< Setup::template Topology, Reachability< Setup > >
{
    typedef Reachability< Setup > This;
    ALGORITHM_CLASS( Setup, ReachabilityShared< typename Setup::Store::Handle > );
    DIVINE_RPC( rpc::Root,
                &This::getShared, &This::setShared,
                &This::_visit, &This::_por, &This::_por_worker,
                &This::_parentTrace, &This::_successorTrace, &This::_ceIsInitial );

    Handle goal;
    bool deadlocked;

    struct Extension {
        Handle _parent;
        Handle &parent() {
            return _parent;
        }
    };

    typedef LtlCE< Setup, Shared, Extension, typename Store::Hasher > CE;
    CE ce;

    Pool& pool() {
        return this->graph().pool();
    }

    Extension &extension( Vertex n ) {
        return n.template extension< Extension >();
    }

    struct Main : Visit< This, Setup >
    {
        static visitor::ExpansionAction expansion( This &r, Vertex st )
        {
            r.shared.stats.addNode( r.graph(), st );
            return visitor::ExpansionAction::Expand;
        }

        static visitor::TransitionAction transition( This &r, Vertex f, Vertex t, Label )
        {
            if ( !r.store().valid( r.extension( t ).parent() ) ) {
                r.extension( t ).parent() = f.handle();
                assert( !r.store().valid( f )
                        || r.store().valid( r.extension( t ).parent() ) );
            }
            r.shared.stats.addEdge( r.store(), f, t );

            if ( r.meta().input.propertyType == graph::PT_Goal
                 && r.graph().isGoal( t.node() ) )
            {
                r.shared.goal = t.handle();
                assert( r.store().valid( r.shared.goal ) );
                r.shared.deadlocked = false;
                return visitor::TransitionAction::Terminate;
            }

            r.graph().porTransition( r.store(), f, t );
            return visitor::TransitionAction::Follow;
        }

        static visitor::DeadlockAction deadlocked( This &r, Vertex n )
        {
            r.shared.stats.addDeadlock();

            if ( r.meta().input.propertyType != graph::PT_Deadlock )
                return visitor::DeadlockAction::Ignore;

            r.shared.goal = n.handle();
            assert( r.store().valid( r.shared.goal ) );
            r.shared.deadlocked = true;
            return visitor::DeadlockAction::Terminate;
        }
    };

    void _visit() { // parallel
        this->visit( this, Main(), shared.need_expand );
    }

    void _por_worker() {
        this->graph()._porEliminate( *this );
    }

    Shared _por( Shared sh ) {
        shared = sh;
        if ( this->graph().porEliminate( *this, *this ) )
            shared.need_expand = true;
        return shared;
    }

    Reachability( Meta m ) : Algorithm( m, sizeof( Extension ) )
    {
        this->init( *this );
    }

    Reachability( Reachability &master, std::pair< int, int > id )
        : Algorithm( master.meta(), sizeof( Extension ) )
    {
        this->init( *this, master, id );
    }

    Shared runCe( Shared sh, void (CE::*ceCall)( This&, typename Setup::Store& ) ) {
        shared = sh;
        ce.setup( *this, shared );
        (ce.*ceCall)( *this, this->store() );
        return shared;
    }

    Shared _parentTrace( Shared sh ) {
        return runCe( sh, &CE::_parentTrace );
    }

    Shared _successorTrace( Shared sh ) {
        return runCe( sh, &CE::_successorTrace );
    }

    Shared _ceIsInitial( Shared sh ) {
        return runCe( sh, &CE::_ceIsInitial );
    }

    void counterexample( Handle n ) {
        shared.ce.initial = n;
        ce.setup( *this, shared );
        Node goal = ce.linear( *this, *this );
        ce.goal( *this, goal );
    }

    void collect() {
        deadlocked = false;
        for ( int i = 0; i < int( shareds.size() ); ++i ) {
            shareds[ i ].stats.update( meta().statistics );
            if ( this->store().valid( shareds[ i ].goal ) ) {
                goal = shareds[ i ].goal;
                if ( shareds[ i ].deadlocked )
                    deadlocked = true;
            }
        }
    }

    void run() {
        progress() << "  searching... \t" << std::flush;

        parallel( &This::_visit );
        collect();

        while ( !this->store().valid( goal ) ) {
            shared.need_expand = false;
            ring( &This::_por );

            if ( shared.need_expand ) {
                parallel( &This::_visit );
                collect();
            } else
                break;
        }

        progress() << meta().statistics.visited << " states, "
                   << meta().statistics.transitions << " edges" << std::flush;

        if ( this->store().valid( goal ) ) {
            if ( deadlocked )
                progress() << ", DEADLOCK";
            else
                progress() << ", GOAL";
        }
        progress() << std::endl;

        resultBanner( !this->store().valid( goal ) );
        if ( this->store().valid( goal ) && this->meta().output.wantCe ) {
            counterexample( goal );
            result().ceType = deadlocked ? meta::Result::CET::Deadlock : meta::Result::CET::Goal;
        }

        result().propertyHolds = this->store().valid( goal )
                                  ? meta::Result::R::No : meta::Result::R::Yes;
        result().fullyExplored = this->store().valid( goal )
                                  ? meta::Result::R::No : meta::Result::R::Yes;
    }
};

}
}

#endif
