///////////////////////////////////////////////////////////////////////////////
// Name:        tests/events/evthandler.cpp
// Purpose:     Test the new event types and wxEvtHandler-methods
// Author:      Peter Most
// Created:     2009-01-24
// Copyright:   (c) 2009 Peter Most
///////////////////////////////////////////////////////////////////////////////

// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------

#include "testprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#include "wx/event.h"

// ----------------------------------------------------------------------------
// test events and their handlers
// ----------------------------------------------------------------------------

const wxEventType LegacyEventType = wxNewEventType();

class MyEvent;
wxDEFINE_EVENT(MyEventType, MyEvent);

class MyEvent : public wxEvent
{
public:
    MyEvent() : wxEvent(0, MyEventType) { }

    virtual wxEvent *Clone() const { return new MyEvent; }
};

typedef void (wxEvtHandler::*MyEventFunction)(MyEvent&);
#ifndef wxHAS_EVENT_BIND
    #define MyEventHandler(func) wxEVENT_HANDLER_CAST(MyEventFunction, func)
#else
    #define MyEventHandler(func) &func
#endif
#define EVT_MYEVENT(func) \
    wx__DECLARE_EVT0(MyEventType, MyEventHandler(func))

class AnotherEvent : public wxEvent
{
};

namespace
{

struct Called
{
    Called() { Reset(); }

    void Reset()
    {
        function =
        functor =
        method =
        smethod = false;
    }

    bool function,
         functor,
         method,
         smethod;
} g_called;

void GlobalOnMyEvent(MyEvent&)
{
    g_called.function = true;
}

void GlobalOnEvent(wxEvent&)
{
    g_called.function = true;
}

#ifdef TEST_INVALID_BIND_GLOBAL
void GlobalOnAnotherEvent(AnotherEvent&);
#endif

void GlobalOnIdle(wxIdleEvent&)
{
    g_called.function = true;
}

struct MyFunctor
{
    void operator()(MyEvent &) { g_called.functor = true; }
};

struct IdleFunctor
{
    void operator()(wxIdleEvent &) { g_called.functor = true; }
};

class MyHandler : public wxEvtHandler
{
public:
    static void StaticOnMyEvent(MyEvent &) { g_called.smethod = true; }
    static void StaticOnAnotherEvent(AnotherEvent &);
    static void StaticOnIdle(wxIdleEvent&) { g_called.smethod = true; }

    void OnMyEvent(MyEvent&) { g_called.method = true; }
    void OnEvent(wxEvent&) { g_called.method = true; }
    void OnAnotherEvent(AnotherEvent&);
    void OnIdle(wxIdleEvent&) { g_called.method = true; }
};

// we can also handle events in classes not deriving from wxEvtHandler
struct MySink
{
    void OnMyEvent(MyEvent&) { g_called.method = true; }
    void OnEvent(wxEvent&) { g_called.method = true; }
    void OnIdle(wxIdleEvent&) { g_called.method = true; }
};

// also test event table compilation
class MyClassWithEventTable : public wxEvtHandler
{
public:
    void OnMyEvent(MyEvent&) { g_called.method = true; }
    void OnEvent(wxEvent&) { g_called.method = true; }
    void OnAnotherEvent(AnotherEvent&);
    void OnIdle(wxIdleEvent&) { g_called.method = true; }

private:
    DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(MyClassWithEventTable, wxEvtHandler)
    EVT_IDLE(MyClassWithEventTable::OnIdle)

    EVT_MYEVENT(MyClassWithEventTable::OnMyEvent)
#ifdef wxHAS_EVENT_BIND
    EVT_MYEVENT(MyClassWithEventTable::OnEvent)
#endif

    // this shouldn't compile:
    //EVT_MYEVENT(MyClassWithEventTable::OnIdle)
    //EVT_IDLE(MyClassWithEventTable::OnAnotherEvent)
END_EVENT_TABLE()

} // anonymous namespace


// --------------------------------------------------------------------------
// test class
// --------------------------------------------------------------------------

class EvtHandlerTestCase : public CppUnit::TestCase
{
public:
    EvtHandlerTestCase() {}

private:
    CPPUNIT_TEST_SUITE( EvtHandlerTestCase );
        CPPUNIT_TEST( BuiltinConnect );
        CPPUNIT_TEST( LegacyConnect );
        CPPUNIT_TEST( DisconnectWildcard );
        CPPUNIT_TEST( AutoDisconnect );
#ifdef wxHAS_EVENT_BIND
        CPPUNIT_TEST( BindFunction );
        CPPUNIT_TEST( BindStaticMethod );
        CPPUNIT_TEST( BindFunctor );
        CPPUNIT_TEST( BindMethod );
        CPPUNIT_TEST( BindMethodUsingBaseEvent );
        CPPUNIT_TEST( BindFunctionUsingBaseEvent );
        CPPUNIT_TEST( BindNonHandler );
        CPPUNIT_TEST( InvalidBind );
#endif // wxHAS_EVENT_BIND
    CPPUNIT_TEST_SUITE_END();

    void BuiltinConnect();
    void LegacyConnect();
    void DisconnectWildcard();
    void AutoDisconnect();
#ifdef wxHAS_EVENT_BIND
    void BindFunction();
    void BindStaticMethod();
    void BindFunctor();
    void BindMethod();
    void BindMethodUsingBaseEvent();
    void BindFunctionUsingBaseEvent();
    void BindNonHandler();
    void InvalidBind();
#endif // wxHAS_EVENT_BIND


    // these member variables exceptionally don't use "m_" prefix because
    // they're used so many times
    MyHandler handler;
    MyEvent e;

    DECLARE_NO_COPY_CLASS(EvtHandlerTestCase)
};

// register in the unnamed registry so that these tests are run by default
CPPUNIT_TEST_SUITE_REGISTRATION( EvtHandlerTestCase );

// also include in its own registry so that these tests can be run alone
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( EvtHandlerTestCase, "EvtHandlerTestCase" );

void EvtHandlerTestCase::BuiltinConnect()
{
    handler.Connect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle));
    handler.Disconnect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle));

    handler.Connect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle), NULL, &handler);
    handler.Disconnect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle), NULL, &handler);

    // using casts like this is even uglier than using wxIdleEventHandler but
    // it should still continue to work for compatibility
    handler.Connect(wxEVT_IDLE, (wxObjectEventFunction)(wxEventFunction)&MyHandler::OnIdle);
    handler.Disconnect(wxEVT_IDLE, (wxObjectEventFunction)(wxEventFunction)&MyHandler::OnIdle);

#ifdef wxHAS_EVENT_BIND
    handler.Bind(wxEVT_IDLE, GlobalOnIdle);
    handler.Unbind(wxEVT_IDLE, GlobalOnIdle);

    IdleFunctor f;
    handler.Bind(wxEVT_IDLE, f);
    handler.Unbind(wxEVT_IDLE, f);

    handler.Bind(wxEVT_IDLE, &MyHandler::OnIdle, &handler);
    handler.Unbind(wxEVT_IDLE, &MyHandler::OnIdle, &handler);

    handler.Bind(wxEVT_IDLE, &MyHandler::StaticOnIdle);
    handler.Unbind(wxEVT_IDLE, &MyHandler::StaticOnIdle);
#endif // wxHAS_EVENT_BIND
}

void EvtHandlerTestCase::LegacyConnect()
{
    handler.Connect( LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );
    handler.Connect( 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );
    handler.Connect( 0, 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );

    handler.Disconnect( LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );
    handler.Disconnect( 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );
    handler.Disconnect( 0, 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent );


    handler.Connect( LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
    handler.Connect( 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
    handler.Connect( 0, 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );

    handler.Disconnect( LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
    handler.Disconnect( 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
    handler.Disconnect( 0, 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
}

void EvtHandlerTestCase::DisconnectWildcard()
{
    // should be able to disconnect a different handler using "wildcard search"
    MyHandler sink;
    wxEvtHandler source;
    source.Connect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle), NULL, &sink);
    CPPUNIT_ASSERT(source.Disconnect(wxID_ANY, wxEVT_IDLE));
    // destruction of source and sink here should properly clean up the
    // wxEventConnectionRef without crashing
}

void EvtHandlerTestCase::AutoDisconnect()
{
    wxEvtHandler source;
    {
        MyHandler sink;
        source.Connect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnIdle), NULL, &sink);
        // mismatched event type, so nothing should be disconnected
        CPPUNIT_ASSERT(!source.Disconnect(wxEVT_THREAD, wxIdleEventHandler(MyHandler::OnIdle), NULL, &sink));
    }
    // destruction of sink should have automatically disconnected it, so
    // there should be nothing to disconnect anymore
    CPPUNIT_ASSERT(!source.Disconnect(wxID_ANY, wxEVT_IDLE));
}

#ifdef wxHAS_EVENT_BIND

void EvtHandlerTestCase::BindFunction()
{
    // function tests
    handler.Bind( MyEventType, GlobalOnMyEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.function );
    handler.Unbind( MyEventType, GlobalOnMyEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.function ); // check that it was disconnected

    handler.Bind( MyEventType, GlobalOnMyEvent, 0 );
    handler.Unbind( MyEventType, GlobalOnMyEvent, 0 );

    handler.Bind( MyEventType, GlobalOnMyEvent, 0, 0 );
    handler.Unbind( MyEventType, GlobalOnMyEvent, 0, 0 );
}

void EvtHandlerTestCase::BindStaticMethod()
{
    // static method tests (this is same as functions but still test it just in
    // case we hit some strange compiler bugs)
    handler.Bind( MyEventType, &MyHandler::StaticOnMyEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.smethod );
    handler.Unbind( MyEventType, &MyHandler::StaticOnMyEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.smethod );

    handler.Bind( MyEventType, &MyHandler::StaticOnMyEvent, 0 );
    handler.Unbind( MyEventType, &MyHandler::StaticOnMyEvent, 0 );

    handler.Bind( MyEventType, &MyHandler::StaticOnMyEvent, 0, 0 );
    handler.Unbind( MyEventType, &MyHandler::StaticOnMyEvent, 0, 0 );
}

void EvtHandlerTestCase::BindFunctor()
{
    // generalized functor tests
    MyFunctor functor;

    handler.Bind( MyEventType, functor );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.functor );
    handler.Unbind( MyEventType, functor );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.functor );

    handler.Bind( MyEventType, functor, 0 );
    handler.Unbind( MyEventType, functor, 0 );

    handler.Bind( MyEventType, functor, 0, 0 );
    handler.Unbind( MyEventType, functor, 0, 0 );

    // test that a temporary functor is working as well and also test that
    // unbinding a different (though equal) instance of the same functor does
    // not work
    MyFunctor func;
    handler.Bind( MyEventType, MyFunctor() );
    CPPUNIT_ASSERT( !handler.Unbind( MyEventType, func ));

    handler.Bind( MyEventType, MyFunctor(), 0 );
    CPPUNIT_ASSERT( !handler.Unbind( MyEventType, func, 0 ));

    handler.Bind( MyEventType, MyFunctor(), 0, 0 );
    CPPUNIT_ASSERT( !handler.Unbind( MyEventType, func, 0, 0 ));
}

void EvtHandlerTestCase::BindMethod()
{
    // class method tests
    handler.Bind( MyEventType, &MyHandler::OnMyEvent, &handler );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.method );
    handler.Unbind( MyEventType, &MyHandler::OnMyEvent, &handler );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.method );

    handler.Bind( MyEventType, &MyHandler::OnMyEvent, &handler, 0 );
    handler.Unbind( MyEventType, &MyHandler::OnMyEvent, &handler, 0 );

    handler.Bind( MyEventType, &MyHandler::OnMyEvent, &handler, 0, 0 );
    handler.Unbind( MyEventType, &MyHandler::OnMyEvent, &handler, 0, 0 );
}

void EvtHandlerTestCase::BindMethodUsingBaseEvent()
{
    // test connecting a method taking just wxEvent and not MyEvent: this
    // should work too if we don't need any MyEvent-specific information in the
    // handler
    handler.Bind( MyEventType, &MyHandler::OnEvent, &handler );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.method );
    handler.Unbind( MyEventType, &MyHandler::OnEvent, &handler );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.method );

    handler.Bind( MyEventType, &MyHandler::OnEvent, &handler, 0 );
    handler.Unbind( MyEventType, &MyHandler::OnEvent, &handler, 0 );

    handler.Bind( MyEventType, &MyHandler::OnEvent, &handler, 0, 0 );
    handler.Unbind( MyEventType, &MyHandler::OnEvent, &handler, 0, 0 );
}


void EvtHandlerTestCase::BindFunctionUsingBaseEvent()
{
    // test connecting a function taking just wxEvent and not MyEvent: this
    // should work too if we don't need any MyEvent-specific information in the
    // handler
    handler.Bind( MyEventType, GlobalOnEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.function );
    handler.Unbind( MyEventType, GlobalOnEvent );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.function );

    handler.Bind( MyEventType, GlobalOnEvent, 0 );
    handler.Unbind( MyEventType, GlobalOnEvent, 0 );

    handler.Bind( MyEventType, GlobalOnEvent, 0, 0 );
    handler.Unbind( MyEventType, GlobalOnEvent, 0, 0 );
}



void EvtHandlerTestCase::BindNonHandler()
{
    // class method tests for class not derived from wxEvtHandler
    MySink sink;

    handler.Bind( MyEventType, &MySink::OnMyEvent, &sink );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( g_called.method );
    handler.Unbind( MyEventType, &MySink::OnMyEvent, &sink );
    g_called.Reset();
    handler.ProcessEvent(e);
    CPPUNIT_ASSERT( !g_called.method );
}

void EvtHandlerTestCase::InvalidBind()
{
    // these calls shouldn't compile but we unfortunately can't check this
    // automatically, you need to uncomment them manually and test that
    // compilation does indeed fail

    // connecting a handler with incompatible signature shouldn't work
#ifdef TEST_INVALID_BIND_GLOBAL
    handler.Bind(MyEventType, GlobalOnAnotherEvent);
#endif
#ifdef TEST_INVALID_BIND_STATIC
    handler.Bind(MyEventType, &MyHandler::StaticOnAnotherEvent);
#endif
#ifdef TEST_INVALID_BIND_METHOD
    handler.Bind(MyEventType, &MyHandler::OnAnotherEvent, &handler);
#endif
#ifdef TEST_INVALID_BIND_FUNCTOR
    IdleFunctor f;
    handler.Bind(MyEventType, f);
#endif

    // the handler can't be omitted when calling Bind()
#ifdef TEST_INVALID_BIND_NO_HANDLER
    handler.Bind(MyEventType, &MyHandler::OnMyEvent);
#endif

    // calling a derived class method with a base class pointer must not work
#ifdef TEST_INVALID_BIND_DERIVED
    struct C1 : wxEvtHandler { };
    struct C2 : wxEvtHandler { void OnWhatever(wxEvent&); };
    C1 c1;
    c1.Bind(&C2::OnWhatever);
#endif

    // using object pointer incompatible with the method must not work
#ifdef TEST_INVALID_BIND_WRONG_CLASS
    MySink mySink;
    MyHandler myHandler;
    myHandler.Bind(MyEventType, &MyHandler::OnMyEvent, &mySink);
#endif
}

#endif // wxHAS_EVENT_BIND
