#ifndef SMOKE_H
#define SMOKE_H

#include <string.h>

/*
 *   Copyright (C) 2002, Ashley Winters <qaqortog@nwlink.com>
 */

class SmokeBinding;

class Smoke {
public:
    union StackItem; // defined below
    /**
     * A stack is an array of arguments, passed to a method when calling it.
     */
    typedef StackItem* Stack;

    enum EnumOperation {
	EnumNew,
	EnumDelete,
	EnumFromLong,
	EnumToLong
    };

    typedef short Index;
    typedef void (*ClassFn)(Index method, void* obj, Stack args);
    typedef void* (*CastFn)(void* obj, Index from, Index to);
    typedef void (*EnumFn)(EnumOperation, Index, void*&, long&);

    enum ClassFlags {
        cf_constructor = 0x01,  // has a constructor
        cf_deepcopy = 0x02,     // has copy constructor
        cf_virtual = 0x04,      // has virtual destructor
        cf_undefined = 0x10     // defined elsewhere
    };
    /**
     * Describe one class.
     */
    struct Class {
	const char *className;	// Name of the class
	Index parents;		// Index into inheritanceList
	ClassFn classFn;	// Calls any method in the class
	EnumFn enumFn;		// Handles enum pointers
        unsigned short flags;   // ClassFlags
    };

    enum MethodFlags {
        mf_static = 0x01,
        mf_const = 0x02
    };
    /**
     * Describe one method of one class.
     */
    struct Method {
	Index classId;		// Index into classes
	Index name;		// Index into methodNames; real name
	Index args;		// Index into argumentList
	unsigned char numArgs;	// Number of arguments
	unsigned char flags;	// MethodFlags (const/static/etc...)
	Index ret;		// Index into types for the return type
	Index method;		// Passed to Class.classFn, to call method
    };

    /**
     * One MethodMap entry maps the munged method prototype
     * to the Method entry.
     *
     * The munging works this way:
     * $ is a plain scalar
     * # is an object
     * ? is a non-scalar (reference to array or hash, undef)
     *
     * e.g. QApplication(int &, char **) becomes QApplication$?
     */
    struct MethodMap {
	Index classId;		// Index into classes
	Index name;		// Index into methodNames; munged name
	Index method;		// Index into methods
    };

    enum TypeFlags {
        // The first 4 bits indicate the TypeId value, i.e. which field
        // of the StackItem union is used.
        tf_elem = 0x0F,

	// Always only one of the next three flags should be set
	tf_stack = 0x10, 	// Stored on the stack, 'type'
	tf_ptr = 0x20,   	// Pointer, 'type*'
	tf_ref = 0x30,   	// Reference, 'type&'
	// Can | whatever ones of these apply
	tf_const = 0x40		// const argument
    };
    /**
     * One Type entry is one argument type needed by a method.
     * Type entries are shared, there is only one entry for "int" etc.
     */
    struct Type {
	const char *name;	// Stringified type name
	Index classId;		// Index into classes. -1 for none
        unsigned short flags;   // TypeFlags
    };

    // We could just pass everything around using void* (pass-by-reference)
    // I don't want to, though. -aw
    union StackItem {
	void* s_voidp;
	bool s_bool;
	char s_char;
	unsigned char s_uchar;
	short s_short;
	unsigned short s_ushort;
	int s_int;
	unsigned int s_uint;
	long s_long;
	unsigned long s_ulong;
	float s_float;
	double s_double;
        long s_enum;
        void* s_class;
    };
    enum TypeId {
	t_voidp,
	t_bool,
	t_char,
	t_uchar,
	t_short,
	t_ushort,
	t_int,
	t_uint,
	t_long,
	t_ulong,
	t_float,
	t_double,
        t_enum,
        t_class,
	t_last		// number of pre-defined types
    };

    // Passed to constructor
    /**
     * The classes array defines every class for this module
     */
    Class *classes;
    Index numClasses;

    /**
     * The methods array defines every method in every class for this module
     */
    Method *methods;
    Index numMethods;

    /**
     * methodMaps maps the munged method prototypes
     * to the methods entries.
     */
    MethodMap *methodMaps;
    Index numMethodMaps;

    /**
     * Array of method names, for Method.name and MethodMap.name
     */
    const char **methodNames;
    Index numMethodNames;

    /**
     * List of all types needed by the methods (arguments and return values)
     */
    Type *types;
    Index numTypes;

    /**
     * Groups of class IDs (-1 separated) used as super class lists.
     * For classes with super classes: Class.parents = index into this array.
     */
    Index *inheritanceList;
    /**
     * Groups of type IDs (-1 separated), describing the types of argument for a method.
     * Method.args = index into this array.
     */
    Index *argumentList;
    /**
     * Groups of method prototypes with the same number of arguments, but different types.
     * Used to resolve overloading.
     */
    Index *ambiguousMethodList;
    /**
     * Function used for casting from/to the classes defined by this module.
     */
    CastFn castFn;

    // Not passed to constructor
    SmokeBinding *binding;

    /**
     * Constructor
     */
    Smoke(Class *_classes, Index _numClasses,
	  Method *_methods, Index _numMethods,
	  MethodMap *_methodMaps, Index _numMethodMaps,
	  const char **_methodNames, Index _numMethodNames,
	  Type *_types, Index _numTypes,
	  Index *_inheritanceList,
	  Index *_argumentList,
	  Index *_ambiguousMethodList,
	  CastFn _castFn) :
		classes(_classes), numClasses(_numClasses),
		methods(_methods), numMethods(_numMethods),
		methodMaps(_methodMaps), numMethodMaps(_numMethodMaps),
		methodNames(_methodNames), numMethodNames(_numMethodNames),
		types(_types), numTypes(_numTypes),
		inheritanceList(_inheritanceList),
		argumentList(_argumentList),
		ambiguousMethodList(_ambiguousMethodList),
		castFn(_castFn),

		binding(0)
		{}

    inline void *cast(void *ptr, Index from, Index to) {
	if(!castFn) return ptr;
	return (*castFn)(ptr, from, to);
    }

    // return classname directly
    inline const char *className(Index classId) {
	return classes[classId].className;
    }

    inline int leg(Index a, Index b) {  // ala Perl's <=>
	if(a == b) return 0;
	return (a > b) ? 1 : -1;
    }

    inline Index idType(const char *t) {
	if(!t) return 0;
	Index imax = numTypes;
	Index imin = 0;
	Index icur = -1;
	int icmp = -1;

	while(imax >= imin) {
	    icur = (imin + imax) / 2;
	    if(icur > 0)
		icmp = strcmp(types[icur].name, t);
	    else
		icmp = -1;
	    if(!icmp) break;
	    if(icmp > 0)
		imax = icur - 1;
	    else
		imin = icur + 1;
	}

	return (!icmp) ? icur : 0;
    }

    inline Index idClass(const char *c) {
	if(!c) return 0;
	Index imax = numClasses;
	Index imin = 0;
	Index icur = -1;
	int icmp = -1;

	while(imax >= imin) {
	    icur = (imin + imax) / 2;
	    if(icur > 0)
		icmp = strcmp(classes[icur].className, c);
	    else
		icmp = -1;
	    if(!icmp) break;
	    if(icmp > 0)
		imax = icur - 1;
	    else
		imin = icur + 1;
	}

	return (!icmp) ? icur : 0;
    }

    inline Index idMethodName(const char *m) {
	if(!m) return 0;
	Index imax = numMethodNames;
	Index imin = 0;
	Index icur = -1;
	int icmp = -1;
	while(imax >= imin) {
	    icur = (imin + imax) / 2;
	    icmp = strcmp(methodNames[icur], m);
	    if(!icmp) break;
	    if(icmp > 0)
		imax = icur - 1;
	    else
		imin = icur + 1;
	}

	return (!icmp) ? icur : 0;
    }

    inline Index idMethod(Index c, Index name) {
	Index imax = numMethodMaps;
	Index imin = 0;
	Index icur = -1;
	int icmp = -1;
	while(imax >= imin) {
	    icur = (imin + imax) / 2;
	    icmp = leg(methodMaps[icur].classId, c);
	    if(!icmp) {
		icmp = leg(methodMaps[icur].name, name);
		if(!icmp) break;
	    }
	    if(icmp > 0)
		imax = icur - 1;
	    else
		imin = icur + 1;
	}

	return (!icmp) ? icur : 0;
    }

    inline Index findMethod(Index c, Index name) {
	// TODO: If method is in a parent module, forward the call from here
	if(!c || !name) return 0;
	Index mid = idMethod(c, name);
	if(mid) return mid;
	if(!classes[c].parents) return 0;
	for(int p = classes[c].parents; inheritanceList[p] ; p++) {
	    mid = findMethod(inheritanceList[p], name);
	    if(mid) return mid;
	}
	return 0;
    }

    inline Index findMethod(const char *c, const char *name) {
	Index idc = idClass(c);
	Index idname = idMethodName(name);
	return findMethod(idc, idname);
    }
};

class SmokeBinding {
protected:
    Smoke *smoke;
public:
    SmokeBinding(Smoke *s) : smoke(s) {}
    virtual void deleted(Smoke::Index classId, void *obj) = 0;
    virtual bool callMethod(Smoke::Index method, void *obj, Smoke::Stack args, bool isAbstract = false) = 0;
    virtual char* className(Smoke::Index classId) = 0;
    virtual ~SmokeBinding() {}
};

#endif
