import my_metastrings;
import std.traits;
import std.stdio;

void main() {}

unittest
{
    class Bar
    {
       int foo(int, bool) { return 1; }
       int foo(int) { return 2; } // covariant parameter types
     
       string bar() { return "Bar.bar"; }

       void VoidFunc() { return 0; } // including over void
    }
    
    void fooCalled(int a, bool b) { writefln("foo(%s, %s)", a, b); }
    void fooCalled2(int a) { writefln("foo(%s)", a); }
    void barCalled() { writefln("bar()"); }
    
    auto b = new Bar();
    auto w = new Watched!(Bar)(b);
    w.attach!("foo").fn(&fooCalled);
//    w.attach!("foo").fn(&fooCalled2); // should work, but causes compiler crash
    w.attach!("bar").fn(&barCalled);

    assert(w.foo(5, true) == 1);
    assert(w.foo(6) == 2);
    assert(w.bar() == "Bar.bar");
}

class Watched(T)
{
    T impl;

    this(T val) { impl = val; }

    mixin(GenerateFunctions!(T).gen());
    
    template attach(string s)
    {
    	void fn(U)(U u)
    	{
    		static assert(is(U == delegate));
    		static assert(is(std.traits.ReturnType!(U) == void));
    		_attach!(s, U)(u);
    	}
    }
    
    template detach(string s)
    {
    	void fn(U)(U u)
    	{
    		static assert(is(U == delegate));
    		static assert(is(std.traits.ReturnType!(U) == void));
    		_detach!(s, U)(u);
    	}
    }
}

template GenerateFunctions(T)
{
    const members = GetMembers!(T); 
    pragma(msg, members.stringof);
    const uint[] counts = GetCounts!(T);
    pragma(msg, counts.stringof);

    string gen()
    {
        string val = "";

        foreach (i, name; members)
        {
            for (uint x = 0; x < counts[i]; x++)
            {
                string fnType = formatStringTango(`typeof(__traits(getVirtualFunctions, T, "{}")[{}])`, name, x);
                string retType = formatStringTango(`std.traits.ReturnType!({})`, fnType);
                string fnCall = formatStringTango(`impl.{}(t)`, name);
                string params = formatStringTango(`std.traits.ParameterTypeTuple!({})`, fnType);
                string watcherName = formatStringTango(`{}{}_watcher`, name, x);
                string fnNameUnique = formatStringTango(`{}{}`, name, x);
                val ~= formatStringTango(
                   `public {0} {1}({2} t) {{
                		foreach (watcher; {3})
                			watcher(t);
                		return impl.{1}(t);
                     }
            
            		private void _attach(string name : "{1}", S : void delegate({2})) (S dg)
            		{{
            			{3} ~= dg;
            		}
            		
            		private void _detach(string name : "{1}", S : void delegate({2})) (S dg)
            		{{
            			for (size_t i = 0; i < {3}.length; i++)
            			{{
            				if ({3}[i] == dg)
            				{{
            					{3} = {3}[0..i] ~ {3}[i+1..$];
            					i--;
            				}
            			}
            		}
                    
            		private void delegate({2})[] {3};
`,
                     retType, name, params, watcherName);
            }
        }
                
        return val;
    }
}

