/**
 * This source code is Copyright (C) 2008 Danushka Abeysuriya (aka. "silvermace").
 * You may use this source code however you see fit, any derived works must be clearly 
 * marked as such and should not misrepresent the original work. No warranty is implied 
 * or given, use this source code and any derivatives at your own risk. 
 * This notice must be duplicated on any derived works
 *
 * Any questions please post a comment on http://www.silvermace.com or e-mail silvermace@gmail.com
 *
 * 9:08 pm 24/Aug/2008 NZST
 *
 */
#include <vector>
#include <algorithm>
#include <hash_map>

#include <boost/timer.hpp>
#include <boost/signals.hpp>
#include <boost/bind.hpp>
#include <boost/random.hpp>

#include <sigc++/sigc++.h>

#include "print.hpp"

static int id_generator = 0;

template<typename track_type>
struct test_subscriber : track_type {
    
    typedef float result_type;

    virtual void fire(int sig_id, result_type& result) {
        result += std::cosf((float)sig_id);
    }
};

template<typename signal_type, typename slot_type>
struct test_publisher {

    typedef typename slot_type::result_type result_type;

    int id; /* used hackishly for sorting results */

    //typedef boost::function<void (signal_type&, slot_type&)> connect_function;
    //typedef boost::function<void (signal_type&, int, int&)>   signal_function;

    /* use native function pointers to make sure boost::bind isn't causing a bottleneck */
    typedef void(*connect_function)(signal_type&, slot_type&);
    typedef void(*signal_function)(signal_type&, int, result_type&);

    test_publisher(const connect_function& connect_, const signal_function& signal_) 
        : connect(connect_), signal(signal_) { id = id_generator++; }

    virtual void make_results() = NULL;
    virtual result_type use_results() = NULL;
    virtual int slot_count() const = NULL;
    virtual int signal_count() const = NULL;

    connect_function connect;
    signal_function  signal;
};

template< typename signal_type, typename slot_type, const int num_sigs, const int num_slots, const bool connect_on_construct = true >
struct test_publisher_impl : test_publisher<signal_type, slot_type> {

    typename signal_type events [num_sigs];
    typename slot_type   slots  [num_slots];

    result_type event_result[num_sigs];
   
    test_publisher_impl(const test_publisher<signal_type, slot_type>::connect_function& connect_ , 
                        const test_publisher<signal_type, slot_type>::signal_function& signal_   ) 
        : test_publisher(connect_, signal_) 
    {
        if( connect_on_construct ) {
            setup_connections();
        }
    }

    inline void setup_connections() {
        for(int i=0; i<num_sigs; ++i) {
            event_result[i] = 0;
            for(int j=0; j<num_slots; ++j) {
                connect(events[i], slots[j]);
            }
        }
    }

    virtual void make_results() {
        if( !connect_on_construct ) setup_connections();

        for(int i=0; i<num_sigs; ++i) {
            signal(events[i], i, event_result[i]);
        }
    }

    virtual result_type use_results() {
        slot_type::result_type accum = 0;
        for(int i=0; i<num_sigs; ++i) {
            accum += event_result[i];
        }
        return accum;
    }

    virtual int slot_count() const { return num_slots; }
    virtual int signal_count() const { return num_sigs; }
};

enum bench_flags {
    BENCH_SIGNAL_TIME  = 1 << 0,
    BENCH_CONNECT_TIME = 1 << 1,
};

template<typename signal_type, typename slot_type, const int benchFlags = BENCH_SIGNAL_TIME >
struct benchmark {
private:

    struct benchmark_result {
        double setup_time;
        double run_time;
        typename slot_type::result_type returned_result;
    };

    typedef test_publisher<signal_type, slot_type>
        publisher_base_type;

    typedef std::vector< publisher_base_type* >
        publisher_vector_type;

    struct results_sorter {
        bool operator()(publisher_base_type* const a, publisher_base_type* const b) const {
            return (a->id > b->id);
        }
    };
    
    typedef std::map< publisher_base_type*, benchmark_result, results_sorter> 
        result_map_type;

    typedef benchmark<signal_type, slot_type, benchFlags> 
        this_type;

    publisher_vector_type   publishers;
    result_map_type         results;
    std::string             test_name;

    template<const int num_sigs, const int num_slots, const int step_divisor>
    struct meta_creator {
        this_type* t;
        meta_creator(this_type* this_) : t(this_) {
            create<num_slots>();
        }
        template<const int num_slots> void create() { 
            t->create<num_sigs, num_slots>(); 
            create<num_slots / step_divisor>(); 
        }
        template< > void create<1>() { 
            t->create<num_sigs, 1>(); 
            /* specialise here and stop recursing */
        }
    };
public:
    typedef typename const publisher_base_type::connect_function& connect_funcref;
    typedef typename const publisher_base_type::signal_function&  signal_funcref;

    connect_funcref         connect;
    signal_funcref          signal;

    template<const int num_sigs, const int num_slots>
    void create() 
    {
        const bool connect_on_construct = ((benchFlags & BENCH_CONNECT_TIME) == 0);

        benchmark_result r;
        boost::timer setup_time;
        publisher_base_type *p = 
            new test_publisher_impl< signal_type, slot_type, num_sigs, num_slots, connect_on_construct >
                (connect, signal);

        r.setup_time = setup_time.elapsed();

        publishers.push_back( p );
        results[p] = r;
    }

    benchmark( const char* test_name_, connect_funcref connect_, signal_funcref signal_ ) 
        : test_name(test_name_), connect(connect_), signal(signal_)
    {
        const int s = 10;
        const int m = 100000;
        meta_creator<1, m, s> one(this);
        meta_creator<2, m, s> two(this);
        meta_creator<3, m, s> three(this);
        meta_creator<4, m, s> four(this);
        meta_creator<5, m, s> five(this);

        meta_creator<100, 100, 10> ten(this);

        create<1000, 1000>();
    }

    virtual ~benchmark() {
        struct del : std::unary_function<void, publisher_base_type*> {
            void operator()(publisher_base_type* a) {
                delete a;
            }
        };
        std::for_each( publishers.begin(), publishers.end(), del() );
        publishers.resize(0);
    }

    void run() {
        publisher_vector_type::iterator p = publishers.begin();
        boost::timer timer;

        double time = 0;
        for(; p != publishers.end(); ++p ) {
            publisher_base_type* pub = (*p);
            timer.restart();
            
            pub->make_results();
            publisher_base_type::result_type result = pub->use_results();
            
            time = timer.elapsed();

            results[pub].run_time = time;
            results[pub].returned_result = result; /* save the result! */
        }
    }

    void print_formatted_results() {

        result_map_type::const_iterator ri = results.begin();
        for(; ri != results.end(); ++ri ) {
            const benchmark_result& r = ri->second;
            boost::format fmt;
            
            fmt = boost::format("| %24s | %16lf | %16lf | %10d | %10d |") 
                % test_name
                % r.setup_time 
                % r.run_time 
                % ri->first->signal_count()
                % ri->first->slot_count();

            print(fmt);
        }
    }
};

template<typename signal_type, typename boost_slot_type>
void default_signal(signal_type& s, int sig_id, typename boost_slot_type::result_type& result) {
    s(sig_id, result);
}

template<typename signal_type, typename slot_type>
void boost_connect(signal_type& s, slot_type& slot) {
    s.connect( boost::bind( boost::mem_fn(&slot_type::fire), boost::ref(slot), _1, _2 ) );
}

template<typename signal_type, typename slot_type>
void sigc_connect(signal_type& s, slot_type& slot) {
    s.connect( sigc::mem_fun(&slot, &slot_type::fire) );
}


int main()
{
    typedef test_subscriber<boost::signals::trackable> boost_slot_type;
    typedef boost::signal<void(int, typename boost_slot_type::result_type&)> boost_signal_type;

    //benchmark<boost_signal_type, boost_slot_type>  boost_test
    //(
    //    boost::bind(&boost_connect<boost_signal_type, boost_slot_type>, _1, _2),
    //    boost::bind(&boost_signal<boost_signal_type>,  _1, _2, _3)
    //);

    const int settings = BENCH_CONNECT_TIME | BENCH_SIGNAL_TIME;

    benchmark<boost_signal_type, boost_slot_type, settings>  boost_test
    (
        "boost::signals 1.36.0",
        &boost_connect<boost_signal_type, boost_slot_type>,
        &default_signal<boost_signal_type, boost_slot_type>
    );

    typedef test_subscriber<sigc::trackable> sigc_slot_type;
    typedef sigc::signal<void, int, typename sigc_slot_type::result_type&> sigc_signal_type;

    benchmark<sigc_signal_type, sigc_slot_type, settings> sigc_test
    (
        "sigc++ 2.0.18",
        &sigc_connect<sigc_signal_type, sigc_slot_type>,
        &default_signal<sigc_signal_type, sigc_slot_type>
    );

    boost_test.run();
    sigc_test.run();

    print_header();
    
    boost_test.print_formatted_results();
    sigc_test.print_formatted_results();
    
    print_footer();

    return 0;
}
