// Written in the D programming language
// This is part of the Phobos runtime libary for the D programming language.

/********************************
 * Signals and Slots are an implementation of the Observer Pattern.
 * Essentially, when a Signal is emitted, a list of connected Observers
 * (called slots) are called.
 *
 * There have been several D implementations of Signals and Slots.
 * This version makes use of several new features in D, which make
 * using it simpler and less error prone. In particular, it is no
 * longer necessary to instrument the slots.
 *
 * References:
 *	$(LINK2 http://scottcollins.net/articles/a-deeper-look-at-_signals-and-slots.html, A Deeper Look at Signals and Slots)$(BR)
 *	$(LINK2 http://en.wikipedia.org/wiki/Observer_pattern, Observer pattern)$(BR)
 *	$(LINK2 http://en.wikipedia.org/wiki/Signals_and_slots, Wikipedia)$(BR)
 *	$(LINK2 http://boost.org/doc/html/$(SIGNALS).html, Boost Signals)$(BR)
 *	$(LINK2 http://doc.trolltech.com/4.1/signalsandslots.html, Qt)$(BR)
 *
 *	There has been a great deal of discussion in the D newsgroups
 *	over this, and several implementations:
 *
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/signal_slots_library_4825.html, signal slots library)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Signals_and_Slots_in_D_42387.html, Signals and Slots in D)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dynamic_binding_--_Qt_s_Signals_and_Slots_vs_Objective-C_42260.html, Dynamic binding -- Qt's Signals and Slots vs Objective-C)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/Dissecting_the_SS_42377.html, Dissecting the SS)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/dwt/about_harmonia_454.html, about harmonia)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/announce/1502.html, Another event handling module)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/41825.html, Suggestion: signal/slot mechanism)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/13251.html, Signals and slots?)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/10714.html, Signals and slots ready for evaluation)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/digitalmars/D/1393.html, Signals &amp; Slots for Walter)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/28456.html, Signal/Slot mechanism?)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/19470.html, Modern Features?)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/16592.html, Delegates vs interfaces)$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/16583.html, The importance of component programming (properties, signals and slots, etc))$(BR)
 *	$(LINK2 http://www.digitalmars.com/d/archives/16368.html, signals and slots)$(BR)
 *
 * Bugs:
 *	Slots can only be delegates formed from class objects or
 *	interfaces to class objects. If a delegate to something else
 *	is passed to connect(), such as a struct member function,
 *	a nested function or a COM interface, undefined behavior
 *	will result.
 *
 *	Not safe for multiple threads operating on the same signals
 *	or slots.
 * Macros:
 *	WIKI = Phobos/StdSignals
 *	SIGNALS=signals
 * Copyright:
 *	Public Domain
 * Author: Walter Bright, Digital Mars, www.digitalmars.com
 */

module std.signals;

import std.stdio;
import std.c.stdlib : calloc, realloc, free;
import core.exception : onOutOfMemoryError;

// Special function for internal use only.
// Use of this is where the slot had better be a delegate
// to an object or an interface that is part of an object.
extern (C) Object _d_toObject(void* p);

// Used in place of Object.notifyRegister and Object.notifyUnRegister.
alias void delegate(Object) DisposeEvt;
extern (C) void  rt_attachDisposeEvent( Object obj, DisposeEvt evt );
extern (C) void  rt_detachDisposeEvent( Object obj, DisposeEvt evt );
//debug=signal;

/************************
 * Mixin to create a signal within a class object.
 *
 * Different signals can be added to a class by naming the mixins.
 *
 * Example:
---
import std.signals;
import std.stdio;

class Observer
{   // our slot
    void watch(string msg, int i)
    {
	writefln("Observed msg '%s' and value %s", msg, i);
    }
}

class Foo
{
    int value() { return _value; }

    int value(int v)
    {
	if (v != _value)
	{   _value = v;
	    // call all the connected slots with the two parameters
	    emit("setting new value", v);
	}
	return v;
    }

    // Mix in all the code we need to make Foo into a signal
    mixin Signal!(string, int);

  private :
    int _value;
}

void main()
{
    Foo a = new Foo;
    Observer o = new Observer;

    a.value = 3;		// should not call o.watch()
    a.connect(&o.watch);	// o.watch is the slot
    a.value = 4;		// should call o.watch()
    a.disconnect(&o.watch);	// o.watch is no longer a slot
    a.value = 5;		// so should not call o.watch()
    a.connect(&o.watch);	// connect again
    a.value = 6;		// should call o.watch()
    delete o;			// destroying o should automatically disconnect it
    a.value = 7;		// should not call o.watch()
}
---
 * which should print:
 * <pre>
 * Observed msg 'setting new value' and value 4
 * Observed msg 'setting new value' and value 6
 * </pre>
 *
 */

template Signal(T1...)
{
    /***
     * A slot is implemented as a delegate.
     * The slot_t is the type of the delegate.
     * The delegate must be to an instance of a class or an interface
     * to a class instance.
     * Delegates to struct instances or nested functions must not be
     * used as slots.
     */
    alias void delegate(T1) slot_t;

    /***
     * Call each of the connected slots, passing the argument(s) i to them.
     */
    void emit( T1 i )
    {
        foreach (slot; slots[0 .. slots_idx])
	{   if (slot)
		slot(i);
	}
    }

    /***
     * Add a slot to the list of slots to be called when emit() is called.
     */
    void connect(slot_t slot)
    {
	/* Do this:
	 *    slots ~= slot;
	 * but use malloc() and friends instead
	 */
	auto len = slots.length;
	if (slots_idx == len)
	{
	    if (slots.length == 0)
	    {
		len = 4;
		auto p = std.signals.calloc(slot_t.sizeof, len);
		if (!p)
		    onOutOfMemoryError();
		slots = (cast(slot_t*)p)[0 .. len];
	    }
	    else
	    {
		len = len * 2 + 4;
		auto p = std.signals.realloc(slots.ptr, slot_t.sizeof * len);
		if (!p)
		    onOutOfMemoryError();
		slots = (cast(slot_t*)p)[0 .. len];
		slots[slots_idx + 1 .. length] = null;
	    }
	}
	slots[slots_idx++] = slot;

     L1:
	Object o = _d_toObject(slot.ptr);
	rt_attachDisposeEvent(o, &unhook);
    }

    /***
     * Remove a slot from the list of slots to be called when emit() is called.
     */
    void disconnect( slot_t slot)
    {
	debug (signal) writefln("Signal.disconnect(slot)");
	for (size_t i = 0; i < slots_idx; )
	{
	    if (slots[i] == slot)
	    {	slots_idx--;
		slots[i] = slots[slots_idx];
		slots[slots_idx] = null;	// not strictly necessary

		Object o = _d_toObject(slot.ptr);
		rt_detachDisposeEvent(o, &unhook);
	    }
	    else
		i++;
	}
    }

    /* **
     * Special function called when o is destroyed.
     * It causes any slots dependent on o to be removed from the list
     * of slots to be called by emit().
     */
    void unhook(Object o)
    {
	debug (signal) writefln("Signal.unhook(o = %s)", cast(void*)o);
	for (size_t i = 0; i < slots_idx; )
	{
	    if (_d_toObject(slots[i].ptr) is o)
	    {	slots_idx--;
		slots[i] = slots[slots_idx];
		slots[slots_idx] = null;	// not strictly necessary
	    }
	    else
		i++;
	}
    }

    /* **
     * There can be multiple destructors inserted by mixins.
     */
    ~this()
    {
	/* **
	 * When this object is destroyed, need to let every slot
	 * know that this object is destroyed so they are not left
	 * with dangling references to it.
	 */
	if (slots)
	{
	    foreach (slot; slots[0 .. slots_idx])
	    {
		if (slot)
		{   Object o = _d_toObject(slot.ptr);
		    rt_detachDisposeEvent(o, &unhook);
		}
	    }
	    std.signals.free(slots.ptr);
	    slots = null;
	}
    }

  private:
    slot_t[] slots;		// the slots to call from emit()
    size_t slots_idx;		// used length of slots[]
}

// A function whose sole purpose is to get this module linked in
// so the unittest will run.
void linkin() { }

unittest
{
    class Observer
    {
	void watch(string msg, int i)
	{
	    //writefln("Observed msg '%s' and value %s", msg, i);
            captured_value = i;
            captured_msg   = msg;
	}

        void clear()
        {
            captured_value = 0;
            captured_msg   = "";
        }

        public:
            int    captured_value;
            string captured_msg;
    }

    class Foo
    {
	int value() { return _value; }

	int value(int v)
	{
	    if (v != _value)
	    {   _value = v;
		emit("setting new value", v);
	    }
	    return v;
	}

	mixin Signal!(string, int);

      private:
	int _value;
    }

    Foo a = new Foo;
    Observer o = new Observer;

    // check initial condition
    assert(o.captured_value == 0);
    assert(o.captured_msg == "");

    // set a value while no observation is in place
    a.value = 3;
    assert(o.captured_value == 0);
    assert(o.captured_msg == "");

    // connect the watcher and trigger it
    a.connect(&o.watch);
    a.value = 4;
    assert(o.captured_value == 4);
    assert(o.captured_msg == "setting new value");

    // disconnect the watcher and make sure it doesn't trigger
    a.disconnect(&o.watch);
    a.value = 5;
    assert(o.captured_value == 4);
    assert(o.captured_msg == "setting new value");

    // reconnect the watcher and make sure it triggers
    a.connect(&o.watch);
    a.value = 6;
    assert(o.captured_value == 6);
    assert(o.captured_msg == "setting new value");

    // delete the underlying object and make sure it doesn't cause
    // a crash or other problems
    delete o;
    a.value = 7;
}
