/*

see http://www.ishiboo.com/~nirva/Projects/tclcplusplus/ for more docs

Put this code in C++:


	    class TCLCPPCLASS(foo) {
	    public:
		int method(int oc, Tcl_Obj *ov[]) {
		    return TCL_OK;
		}

		TCLCPPOBJCONSTRUCTOR(foo) {
		    RegisterMethod("method", &foo::method);
		}
	    };

	    TclCPPObjFactory<foo> objcreator(interp, "FOO");


Now in TCL, you can do:


	    set obj [::FOO::new]
	    $obj method 1 2 3


and it'll call the C++ class foo's method() with 3 args, 1 2 3.

You can also do:


	    set obj [::FOO::new a b c]


and the constructor will get called with 3 args, a b c. The
TCLCPPOBJCONSTRUCTOR() macro will create 2 local vars: int oc, Tcl_Obj *ov[]
which contain the 3 args passed to ::FOO::new

*/


#ifndef TCL_EXT_HELPER__H_
#define TCL_EXT_HELPER__H_

#ifdef WIN32
// C4800: 'int' : forcing value to bool 'true' or 'false' (performance warning)
#pragma warning(disable : 4800)
// Win32 templates generate hundreds of "overly-long identifier" warnings
#pragma warning(disable : 4786)
#pragma warning(disable : 4503)
#endif

#include <tcl.h>
#include <string>
#include <map>

using namespace std;

class _TclCPPObjFactory {
protected:
    static unsigned int _tclcppobjid;
    static map<void *, string> _obj2name_map;
    static map<string, void *> _name2obj_map;

public:
    _TclCPPObjFactory() { }
    virtual ~_TclCPPObjFactory() { }
    virtual void unregisterobj(const char *name) = 0;
};

unsigned int _TclCPPObjFactory::_tclcppobjid = 0;
map<void *, string> _TclCPPObjFactory::_obj2name_map;
map<string, void *> _TclCPPObjFactory::_name2obj_map;

template<class OBJ>
class TclCPPObjFactory : public _TclCPPObjFactory {
    Tcl_Interp *_interp;
    string _nmspc;

    static int __tclcreate(ClientData f, Tcl_Interp *interp,
			  int oc, Tcl_Obj *const ov[])
    {
	return static_cast<TclCPPObjFactory<OBJ> *>(f)->_create(oc, ov);
    }
    static int __tclruncmd(ClientData f, Tcl_Interp *interp,
			   int oc, Tcl_Obj *const ov[])
    {
	return static_cast<TclCPPObjFactory<OBJ> *>(f)->_run(oc, ov);
    }

    int _run(int oc, Tcl_Obj *const ov[])
    {
	if (oc < 2) {
	    Tcl_AddErrorInfo(_interp, "invalid arg count to command");
	    return TCL_ERROR;
	}

	char *name = Tcl_GetString(ov[0]);
	for (char *p = name; *p; p++)
	    *p = tolower(*p);

	return ((OBJ*)_name2obj_map[Tcl_GetString(ov[0])])->run(oc-1, ov+1);
    }

    int _create(int oc, Tcl_Obj *const ov[])
    {
	char buf[40];
	sprintf(buf, "cppobj%x", _tclcppobjid++);
	string name = buf;

	OBJ *p = new OBJ(this, _interp,
			 name.c_str(), _nmspc.c_str(), oc-1, ov+1);

	_obj2name_map[p] = name;
	_name2obj_map[name] = p;

	Tcl_Obj *objv = Tcl_NewStringObj(name.c_str(), name.size());
	Tcl_SetListObj(Tcl_GetObjResult(_interp), 1, &objv);

	Tcl_CreateObjCommand(_interp, (char*)name.c_str(), __tclruncmd, this, 0);

	return TCL_OK;
    }

public:
    // "::nmspc::new" returns prefix0, prefix1, etc..
    TclCPPObjFactory(Tcl_Interp *interp, const char *nmspc)
    {
	_interp = interp;
	_nmspc = nmspc;

	string s = "::";
	s += nmspc;
	s += "::new";
	Tcl_CreateObjCommand(interp, (char*)s.c_str(), __tclcreate, this, 0);
    }

    virtual ~TclCPPObjFactory() { }

    void unregisterobj(const char *name)
    {
	int ret;
	if (_name2obj_map.find(name) == _name2obj_map.end())
	    return;
	if ((ret = Tcl_DeleteCommand(_interp, (char*)name)) != 0)
	    fprintf(stderr, "COULD NOT REMOVE TCL COMMAND (of type %s) FROM INTERP: \"%s\", err(%d)\n",
		    _nmspc.c_str(), (char*)name, ret);
	_obj2name_map.erase(_name2obj_map[name]);
	_name2obj_map.erase(name);
    }
};

template<class OBJ>
class TclCPPObj {
    Tcl_Interp *_interp;
    string _tn;
    string _type;
    _TclCPPObjFactory *_factory;

    typedef int (OBJ::*memfunc)(int oc, Tcl_Obj * const ov[]);
    map<string, memfunc> _methods;

protected:
    const char *get_tclname() { return _tn.c_str(); }
    const char *get_tcltype() { return _type.c_str(); }
    Tcl_Interp *get_interp() { return _interp; }

    void RegisterMethod(const char *name, memfunc fn)
    {
	_methods[name] = fn;
    }

    int method_destroy(int oc, Tcl_Obj * const ov[])
    {
	if (oc != 0) {
	    Tcl_AddErrorInfo(_interp, "invalid arg count to command");
	    return TCL_ERROR;
	}

	_factory->unregisterobj(_tn.c_str());
	delete this;
	return TCL_OK;
    }

public:
    TclCPPObj(_TclCPPObjFactory *factory, Tcl_Interp *i,
	      const char *tn, const char *t)
	: _factory(factory), _interp(i), _tn(tn), _type(t)
	{
	    RegisterMethod("destroy", &TclCPPObj::method_destroy);
	}

    virtual ~TclCPPObj() { }

    int run(int oc, Tcl_Obj * const ov[])
    {
	if (_methods.find(Tcl_GetString(ov[0])) == _methods.end()) {
	    Tcl_AddErrorInfo(_interp, "invalid graph command");
	    return TCL_ERROR;
	}

	memfunc fn = _methods[Tcl_GetString(ov[0])];
	return (((OBJ*)this)->*(fn))(oc-1, ov+1);
    }
};

#define TCLCPPOBJCONSTRUCTOR(cls) \
    cls(_TclCPPObjFactory *factory, Tcl_Interp *interp, const char *tclname, \
	const char *type, \
	int oc, Tcl_Obj * const ov[]) : TclCPPObj<cls>(factory, interp, tclname, type)

#define TCLCPPCLASS(cls) cls : public TclCPPObj<cls>

#define TCLCPP_ARGS_EQ(cnt) do { \
    if (oc != cnt) { \
	Tcl_AddErrorInfo(get_interp(), "invalid arg count to command"); \
	return TCL_ERROR; \
    } } while (0)

#endif
