// -*- C++ -*- (c) 2014 Vladimír Štill <xstill@fi.muni.cz>

#include <vector>

#include <brick-types.h>
#include <brick-unittest.h>

#include <divine/graph/probability.h>

#ifndef DIVINE_TOOLKIT_LABEL_H
#define DIVINE_TOOLKIT_LABEL_H

namespace divine {
namespace graph {

struct NoLabel : brick::types::Comparable {
    NoLabel() {}
    NoLabel( int ) {}
    NoLabel levelup( int ) const { return NoLabel(); }
    int cluster( int = 0 ) { return 0; }
    NoLabel operator *( std::pair< int, int > ) const { return NoLabel(); }
    bool operator<=( NoLabel ) const { return true; }
};

template< typename BS >
typename BS::bitstream &operator<<( BS &bs, const NoLabel & ) {
    return bs;
}
template< typename BS >
typename BS::bitstream &operator>>( BS &bs, NoLabel & ) {
    return bs;
}

struct ControlLabel {
    ControlLabel() : tid( -1 ) { }
    ControlLabel( int tid ) : tid( tid ) { }
    ControlLabel levelup( int ) { return *this; }
    int cluster( int = 0 ) { return 0; }
    ControlLabel operator *( std::pair< int, int > ) const { return *this; }
    int tid;
};

template< typename BS >
typename BS::bitstream &operator<<( BS &bs, const ControlLabel &cl ) {
    bs << cl.tid;
    return bs;
}
template< typename BS >
typename BS::bitstream &operator>>( BS &bs, ControlLabel &cl ) {
    bs >> cl.tid;
    return bs;
}

/* unified label data structure, can be converted to Probability,
 * ControlLabel or NoLablel
 */
struct Label {

    Label() : Label( 0 ) { }
    Label( int tid ) : Label( tid, 0, 0, { } ) { }
    Label( int tid, int numerator, int denominator, std::vector< int > choices ) :
        tid( tid ), numerator( numerator ), denominator( denominator ),
        choices( choices )
    { }

    Label operator*( std::pair< int, int > x ) const {
        return Label( tid, numerator * x.first, denominator * x.second, choices );
    }

    Label levelup( int c ) const {
        Label l = *this;
        l.choices.push_back( c );
        return l;
    }

    explicit operator NoLabel() const { return NoLabel(); }

    explicit operator ControlLabel() const {
        return ControlLabel( tid );
    }

    explicit operator Probability() const {
        ASSERT_LEQ( 0, denominator );
        Probability p{ int( pow( 2, tid + 1 ) ), numerator, denominator };
        ASSERT_LEQ( p.numerator, numerator );
        ASSERT_EQ( int( p.denominator ), denominator );
        for ( int c : choices )
            p = p.levelup( c );
        return p;
    }

    int tid;
    int numerator;
    int denominator;
    std::vector< int > choices;
};


static inline std::string showLabel( NoLabel ) { return ""; }
static inline std::string showLabel( ControlLabel cl ) {
    return "tid = " + std::to_string( cl.tid );
}
static inline std::string showLabel( Probability p ) { return p.text(); }

} // namespace toolkit
} // namespace divine

#endif // DIVINE_TOOLKIT_LABEL_H