#include <iostream>
using namespace std;

#include "../FbTk/Signal.hh"
#include "../FbTk/MemFun.hh"
#include "../FbTk/RelaySignal.hh"
#include "../FbTk/Observer.hh"
#include "../FbTk/Subject.hh"

#include <string>



struct NoArgument {
    void operator() () const {
        cout << "No Argument." << endl;
    }
};

struct OneArgument {
    void operator ()( int value ) {
        cout << "One argument = " << value << endl;
    }
};

struct TwoArguments {
    template <typename T1, typename T2>
    void operator ()( const T1& value, const T2& message ) {
        cout << "Two arguments, (1) = " << value << ", (2) = " << message << endl;
    }
};

struct ThreeArguments {
    void operator ()( int value, const string& message, double value2 ) {
        cout << "Two arguments, (1) = " << value << ", (2) = " << message
             << ", (3) = " << value2 << endl;
    }
};

struct FunctionClass {
    FunctionClass() {
        cout << "FunctionClass created." << endl;
    }
    ~FunctionClass() {
        cout << "FunctionClass deleted." << endl;
    }
    void print() {
        cout << "Printing." << endl;
    }

    void takeIt( string& str ) {
        cout << "FunctionClass::takeIt( " << str << " )" << endl;
    }

    void showMessage( int value, const string& message ) {
        cout << "(" << value << "): " << message << endl;
    }
    void showMessage2( const string& message1, const string& message2) {
        cout << "(" << message1 << ", " << message2 << ")" << endl;
    }
    void threeArgs( int value, const string& str, double pi ) {
        cout << "(" << value << "): " << str << ", pi = " << pi << endl;
    }

};

struct Printer {
    void printInt(int value) {
        cout << "Int:" << value << endl;
    }
    void printString(string value) {
        cout << "string:" << value << endl;
    }
    void printFloat(float value) {
        cout << "Float:" << value << endl;
    }
}; 

int main() {
    using FbTk::Signal;
    using FbTk::SignalTracker;

    Signal<void> no_arg;
    no_arg.connect( NoArgument() );

    Signal<void, int> one_arg;
    one_arg.connect( OneArgument() );

    Signal<void, int, const string&> two_args;
    two_args.connect( TwoArguments() );
    
    Signal<void, int, const string&, double> three_args;
    three_args.connect( ThreeArguments() );

    // emit test
    no_arg.emit();
    one_arg.emit( 10 );
    two_args.emit( 10, "Message" );
    three_args.emit( 10, "Three", 3.141592 );

    // test signal tracker
    {
        cout << "---- tracker ----" << endl;
        SignalTracker tracker;
        // setup two new slots and track them
        SignalTracker::TrackID id_no_arg = tracker.join( no_arg, NoArgument() );
        SignalTracker::TrackID id_one_arg = tracker.join( one_arg, OneArgument() );

        // two outputs each from these two signals
        no_arg.emit();
        one_arg.emit( 31 );

        // stop tracking id_one_arg, which should keep the slot after this scope,
        // the id_no_arg connection should be destroyed after this.
        tracker.leave( id_one_arg );
        cout << "---- tracker end ----" << endl;
    }

    // now we should have one output from no_arg and two outputs from one_arg
    no_arg.emit();
    one_arg.emit( 2 );

    using FbTk::MemFun;
    FunctionClass obj;
    no_arg.clear();
    no_arg.connect(MemFun(obj, &FunctionClass::print));
    no_arg.emit();

    string takeThis("Take this");
    Signal<void, string&> ref_arg;
    ref_arg.connect(MemFun(obj, &FunctionClass::takeIt));
    ref_arg.emit( takeThis );

    two_args.clear();
    two_args.connect(MemFun(obj, &FunctionClass::showMessage));
    two_args.emit(10, "This is a message");
    
    three_args.clear();
    three_args.connect(MemFun(obj, &FunctionClass::threeArgs));
    three_args.emit(9, "nine", 3.141592);

    // Test ignore signals
    {
        cout << "----------- Testing ignoring arguments for signal." << endl;
        using FbTk::MemFunIgnoreArgs;
        // Create a signal that emits with three arguments, and connect
        // sinks that takes less than three arguments.
        Signal<void, string, string, float> more_args;
        more_args.connect(MemFunIgnoreArgs(obj, &FunctionClass::print));
        more_args.connect(MemFunIgnoreArgs(obj, &FunctionClass::takeIt));
        more_args.connect(MemFunIgnoreArgs(obj, &FunctionClass::showMessage2));
        more_args.emit("This should be visible for takeIt(string)",
                       "Visible to the two args function.",
                       2.9);

    }
    // Test relay new signals to old
    {
        cout << "---------- Testing relay of signals" << endl;
        struct Observer: public FbTk::Observer {
            void update(FbTk::Subject* subj) {
                cout << "Observer called." << endl;
            }
        };
        // setup old subject->observer listening
        FbTk::Subject destination;
        Observer obs;
        destination.attach(&obs);
        // create a new signal and relay it to the
        // old subject
        FbTk::Signal<void, string> source;
        FbTk::relaySignal(source, destination);
        // the new signal should now make the old
        // subject notify its observers
        source.emit("hello world");
    }

    // Test argument selector
    {
        using namespace FbTk;
        Signal<void, int, string, float> source;

        Printer printer;
        source.connect(MemFunSelectArg0(printer, &Printer::printInt));
        source.connect(MemFunSelectArg1(printer, &Printer::printString));
        source.connect(MemFunSelectArg2(printer, &Printer::printFloat));

        source.emit(10, "hello", 3.141592);

        Signal<void, string, int> source2;
        source2.connect(MemFunSelectArg0(printer, &Printer::printString));
        source2.connect(MemFunSelectArg1(printer, &Printer::printInt));
        source2.emit("world", 37);
    }
}