// This is where the magic happens. EJBindingBase is the Base class for all
// native Obj-C classes that expose functions and properties to JavaScript.

// A subclass of EJBindingBase whose name begins with "EJBinding" is
// automatically exposed to JavaScript at the global "Ejecta" object. E.g. the
// class "EJBindingFoo" can be instantiated in JavaScript with:
// var foo = new Ejecta.Foo();

// This header provides some nasty C-macros that let you easily expose functions
// and properties of your class.

// The class itself just provide a reference to the JavaScript "jsObject" of the
// instance, a reference to the EJJavaScriptView where this instance was created
// and constructor and destructor functions.

#import <Foundation/Foundation.h>
#import "EJAppViewController.h"
#import "EJJavaScriptView.h"

#include <objc/message.h>


// (Not sure if clever hack or really stupid...)

// All classes derived from this EJBindingBase will return a JSClassRef through
// the 'getJSClass' class method. The properties and functions that are exposed
// to JavaScript are defined through the 'staticFunctions' and 'staticValues' in
// this JSClassRef.

// Since these functions don't have extra data (e.g. a void*), we have to define
// a C callback function for each js function, for each js getter and for each
// js setter.

// Furthermore, a class method is added to the objc class to return the function
// pointer to the particular C callback function - this way we can later inflect
// the objc class and gather all function pointers.


#define EJ_BINDING_CLASS_PREFIX "EJBinding"

// We need to cast objc_msgSend into the proper function types for bound
// functions to ensure 64bit compatability

#define _EJ_CALL_BOUND_OBJC_FUNC(INSTANCE, NAME, CTX, ARGC, ARGV) \
	((JSValueRef(*)(id, SEL, JSContextRef, size_t, const JSValueRef [])) \
		objc_msgSend)(INSTANCE, @selector(NAME), CTX, ARGC, ARGV)

#define _EJ_CALL_BOUND_OBJC_GET(INSTANCE, NAME, CTX) \
	((JSValueRef(*)(id, SEL, JSContextRef)) \
		objc_msgSend)(INSTANCE, @selector(NAME), CTX)

#define _EJ_CALL_BOUND_OBJC_SET(INSTANCE, NAME, CTX, VALUE) \
	((void(*)(id, SEL, JSContextRef, JSValueRef)) \
		objc_msgSend)(INSTANCE, @selector(NAME), CTX, VALUE)
		

// The class method that returns a pointer to the static C callback function
#define __EJ_GET_POINTER_TO(NAME) \
	+ (void *)_ptr_to##NAME { \
		return (void *)&NAME; \
	}




// Function - use with EJ_BIND_FUNCTION( functionName, ctx, argc, argv ) { ... }

#define EJ_BIND_FUNCTION(NAME, CTX_NAME, ARGC_NAME, ARGV_NAME) \
	\
	/* The C callback function for the exposed method and class method that returns it */ \
	static JSValueRef _func_##NAME( \
		JSContextRef ctx, \
		JSObjectRef function, \
		JSObjectRef object, \
		size_t argc, \
		const JSValueRef argv[], \
		JSValueRef* exception \
	) { \
		id instance = (id)JSObjectGetPrivate(object); \
		JSValueRef ret = _EJ_CALL_BOUND_OBJC_FUNC(instance, _func_##NAME:argc:argv:, ctx, argc, argv); \
		return ret ? ret : ((EJBindingBase *)instance)->scriptView->jsUndefined; \
	} \
	__EJ_GET_POINTER_TO(_func_##NAME)\
	\
	/* The actual implementation for this method */ \
	- (JSValueRef)_func_##NAME:(JSContextRef)CTX_NAME argc:(size_t)ARGC_NAME argv:(const JSValueRef [])ARGV_NAME




// Getter - use with EJ_BIND_GET( propertyName, ctx ) { ... }

#define EJ_BIND_GET(NAME, CTX_NAME) \
	\
	/* The C callback function for the exposed getter and class method that returns it */ \
	static JSValueRef _get_##NAME( \
		JSContextRef ctx, \
		JSObjectRef object, \
		JSStringRef propertyName, \
		JSValueRef* exception \
	) { \
		id instance = (id)JSObjectGetPrivate(object); \
		return _EJ_CALL_BOUND_OBJC_GET(instance, _get_##NAME:, ctx); \
	} \
	__EJ_GET_POINTER_TO(_get_##NAME)\
	\
	/* The actual implementation for this getter */ \
	- (JSValueRef)_get_##NAME:(JSContextRef)CTX_NAME




// Setter - use with EJ_BIND_SET( propertyName, ctx, value ) { ... }

#define EJ_BIND_SET(NAME, CTX_NAME, VALUE_NAME) \
	\
	/* The C callback function for the exposed setter and class method that returns it */ \
	static bool _set_##NAME( \
		JSContextRef ctx, \
		JSObjectRef object, \
		JSStringRef propertyName, \
		JSValueRef value, \
		JSValueRef* exception \
	) { \
		id instance = (id)JSObjectGetPrivate(object); \
		_EJ_CALL_BOUND_OBJC_SET(instance, _set_##NAME:value:, ctx, value); \
		return true; \
	} \
	__EJ_GET_POINTER_TO(_set_##NAME) \
	\
	/* The actual implementation for this setter */ \
	- (void)_set_##NAME:(JSContextRef)CTX_NAME value:(JSValueRef)VALUE_NAME
		



// Shorthand to define a function that logs a "not implemented" warning

#define EJ_BIND_FUNCTION_NOT_IMPLEMENTED(NAME) \
	static JSValueRef _func_##NAME( \
		JSContextRef ctx, \
		JSObjectRef function, \
		JSObjectRef object, \
		size_t argc, \
		const JSValueRef argv[], \
		JSValueRef* exception \
	) { \
		static bool didShowWarning; \
		if( !didShowWarning ) { \
			NSLog(@"Warning: method " @ #NAME @" is not yet implemented!"); \
			didShowWarning = true; \
		} \
		id instance = (id)JSObjectGetPrivate(object); \
		return ((EJBindingBase *)instance)->scriptView->jsUndefined; \
	} \
	__EJ_GET_POINTER_TO(_func_##NAME)




// Shorthand to bind enums with name tables - use with
// EJ_BIND_ENUM( name, target, "name1", "name2", ...) );


#define EJ_BIND_ENUM(NAME, TARGET, ...) \
	static const char *_##NAME##EnumNames[] = {__VA_ARGS__}; \
	EJ_BIND_GET(NAME, ctx) { \
		JSStringRef src = JSStringCreateWithUTF8CString( _##NAME##EnumNames[TARGET] ); \
		JSValueRef ret = JSValueMakeString(ctx, src); \
		JSStringRelease(src); \
		return ret; \
	} \
	\
	EJ_BIND_SET(NAME, ctx, value) { \
		JSStringRef _str = JSValueToStringCopy(ctx, value, NULL); \
		const JSChar *_strptr = JSStringGetCharactersPtr( _str ); \
		size_t _length = JSStringGetLength(_str)-1; \
		const char ** _names = _##NAME##EnumNames; \
		int _target; \
		EJ_MAP_EXT(0, _EJ_LITERAL(else), _EJ_BIND_ENUM_COMPARE, __VA_ARGS__) \
		else { JSStringRelease( _str ); return; } \
		TARGET = _target; \
		JSStringRelease( _str );\
	}

#define _EJ_BIND_ENUM_COMPARE(INDEX, NAME) \
	if( _length == sizeof(NAME)-2 && JSStrIsEqualToStr( _strptr, _names[INDEX], _length) ) { _target = INDEX; }
	
static inline bool JSStrIsEqualToStr( const JSChar *s1, const char *s2, size_t length ) {
	for( size_t i = 0; i < length && *s1 == *s2; i++ ) {
		s1++;
		s2++;
	}
	return (*s1 == *s2);
}




// Shorthand to bind const numbers and strings

#define EJ_BIND_CONST(NAME, VALUE) \
	+ (NSObject *)_const_##NAME { \
		return @(VALUE); \
	}


// Commas can't be used directly in macro arguments
#define _EJ_COMMA() ,
#define _EJ_EMPTY()
#define _EJ_LITERAL(X) X _EJ_EMPTY




// Expand a F(INDEX, ARG) macro for each argument, max 16 arguments - use with
// EJ_MAP( F, arg1, arg2, ... );

// An offset for the index as well as a joiner (a macro that is expanded between
// each F() expansion) can be specified with:
// EJ_MAP_EXT( OFFSET, JOINER, F, arg1, arg2 );

// Adopted from https://github.com/swansontec/map-macro

#define EJ_MAP_EXT(OFFSET, JOINER, F, ...) _EJ_EVAL(_EJ_MAP1(OFFSET, JOINER, F, __VA_ARGS__, (), 0))
#define EJ_MAP(F, ...) _EJ_EVAL(_EJ_MAP1(0, _EJ_EMPTY, F, __VA_ARGS__, (), 0))

#define _EJ_EVAL0(...) __VA_ARGS__
#define _EJ_EVAL1(...) _EJ_EVAL0( _EJ_EVAL0(__VA_ARGS__) )
#define _EJ_EVAL2(...) _EJ_EVAL1( _EJ_EVAL1(__VA_ARGS__) )
#define _EJ_EVAL(...) _EJ_EVAL2( _EJ_EVAL2(__VA_ARGS__) )

#define _EJ_MAP_END(...)
#define _EJ_MAP_OUT
#define _EJ_MAP_GET_END() 0, _EJ_MAP_END
#define _EJ_MAP_NEXT0(ITEM, NEXT, ...) NEXT _EJ_MAP_OUT
#define _EJ_MAP_NEXT1(JOINER, ITEM, NEXT) _EJ_MAP_NEXT0 (ITEM, JOINER() NEXT, 0)
#define _EJ_MAP_NEXT(JOINER, ITEM, NEXT) _EJ_MAP_NEXT1 (JOINER, _EJ_MAP_GET_END ITEM, NEXT)

#define _EJ_MAP0(IDX, JOINER, F, NAME, PEEK, ...) F(IDX, NAME) _EJ_MAP_NEXT(JOINER, PEEK, _EJ_MAP1) (IDX+1, JOINER, F, PEEK, __VA_ARGS__)
#define _EJ_MAP1(IDX, JOINER, F, NAME, PEEK, ...) F(IDX, NAME) _EJ_MAP_NEXT(JOINER, PEEK, _EJ_MAP0) (IDX+1, JOINER, F, PEEK, __VA_ARGS__)




// EJ_ARGC(...) - get the argument count in a macro with __VA_ARGS__; max 16 args

#define EJ_ARGC(...) _EJ_ARGC_SEQ(__VA_ARGS__,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1)
#define _EJ_ARGC_SEQ(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,n,...) n




// Unpack JavaScript values to numbers - use with
// EJ_UNPACK_ARGV(float value1, int value2, ...);
// or with an offset into argv
// EJ_UNPACK_ARGV_OFFSET(OFFSET, float value1, int value2, ...);

#define EJ_UNPACK_ARGV(...) EJ_UNPACK_ARGV_OFFSET(0, __VA_ARGS__)
#define EJ_UNPACK_ARGV_OFFSET(OFFSET, ...) \
	if( argc < EJ_ARGC(__VA_ARGS__)+OFFSET ) { \
		return NULL; \
	} \
	EJ_MAP_EXT(OFFSET, _EJ_LITERAL(;), _EJ_UNPACK_NUMBER, __VA_ARGS__)
	
#define _EJ_UNPACK_NUMBER(INDEX, NAME) NAME = JSValueToNumberFast(ctx, argv[INDEX])




@class EJJavaScriptView;
@interface EJBindingBase : NSObject {
	JSObjectRef jsObject;
	
	// Puplic for fast access to instance->scriptView->jsUndefined in bound functions
	@public	EJJavaScriptView *scriptView;
}

- (id)initWithContext:(JSContextRef)ctxp argc:(size_t)argc argv:(const JSValueRef [])argv;
- (void)createWithJSObject:(JSObjectRef)obj scriptView:(EJJavaScriptView *)view;
- (void)prepareGarbageCollection;
+ (JSObjectRef)createJSObjectWithContext:(JSContextRef)ctx
	scriptView:(EJJavaScriptView *)scriptView
	instance:(EJBindingBase *)instance;
void EJBindingBaseFinalize(JSObjectRef object);

@property (nonatomic, readonly) EJJavaScriptView *scriptView;

@end

