package jp.kirikiri.tjs2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;

public class NativeClass extends CustomObject {

	static private final int
		NIS_REGISTER	= 0x00000001,	// set native pointer
		CII_ADD			= 0x00000001,	// register name
		MEMBERENSURE	= 0x00000200,	// create a member if not exists
		IGNOREPROP		= 0x00000800,	// ignore property invoking
		STATICMEMBER	= 0x00010000;	// member is not registered to the
										// object (internal use)

	static private final int
		E_MEMBERNOTFOUND = -1001,
		E_NOTIMPL		= -1002,
		E_INVALIDOBJECT	= -1006,
		S_OK			= 0,
		S_TRUE			= 1;

	static private final int // enum tTJSNativeInstanceType
		nitClass	= 0,
		nitMethod	= 1,
		nitProperty = 2;

	protected int mClassIDInternal;
	protected String mClassName;

	public NativeClass( String name ) {
		super();
		mCallFinalize = false;
		mClassName = TJS.mapGlobalStringMap(name);
	}
	public void registerNCM( final String name, Dispatch2 dsp, final String className, int type ) throws VariantException, TJSException {
		registerNCM( name, dsp, className, type, 0 );
	}
	public void registerNCM( final String name, Dispatch2 dsp, final String className, int type, int flags ) throws VariantException, TJSException {
		String tname = TJS.mapGlobalStringMap(name);
		/* デバッグ機能は未実装
		// set object type for debugging
		if(TJSObjectHashMapEnabled()) {
			switch(type) {
			case nitMethod:
				TJSObjectHashSetType(dsp, ttstr(TJS_W("(native function) ")) + classname + TJS_W(".") + name);
				break;
			case nitProperty:
				TJSObjectHashSetType(dsp, ttstr(TJS_W("(native property) ")) + classname + TJS_W(".") + name);
				break;
			/*
			case nitClass:
				The information is not set here
				(is to be set in tTJSNativeClass::tTJSNativeClass)
			*//*
			}
		}
		*/
		// add to this
		Variant val = new Variant(dsp);
		if( propSetByVS( (MEMBERENSURE | IGNOREPROP) | flags, tname, val, this) == E_NOTIMPL )
				propSet( (MEMBERENSURE | IGNOREPROP) | flags, tname, null, val, this);
	}
	protected void finalizeObject() throws VariantException, TJSException {
		super.finalizeObject();
	}
	protected Dispatch2 createBaseTJSObject() { return new CustomObject(); }
	protected NativeInstance createNativeInstance() { return null; }
	public final String getClassName() { return mClassName; }
	public void setClassID( int classid ) { mClassIDInternal = classid; }

	public int funcCall( int flag, final String membername, IntWrapper hint, Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		if( !getValidity() )
			return E_INVALIDOBJECT;

		if( membername != null ) return super.funcCall(flag, membername, hint, result, param, objthis);

		Variant name = new Variant( mClassName );
		objthis.classInstanceInfo( CII_ADD, 0, name ); // add class name

		// create base native object
		NativeInstance nativeptr = createNativeInstance();
		Holder<NativeInstance> hnativeptr = new Holder<NativeInstance>(nativeptr);

		// register native instance information to the object;
		// even if "nativeptr" is null
		objthis.nativeInstanceSupport( NIS_REGISTER, mClassIDInternal, hnativeptr );

		// register members to "objthis"

		// a class to receive member callback from class
		class Callback extends Dispatch {
			public Dispatch2 mDest; // destination object
			public int funcCall( int flag, final String membername, IntWrapper hint, Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
				// *param[0] = name   *param[1] = flags   *param[2] = value
				int flags = param[1].asInteger();
				if( (flags & STATICMEMBER) == 0 ) {
					Variant val = new Variant( param[2] );
					if( val.isObject() ) {
						// change object's objthis if the object's objthis is null
						if( val.asObjectThis() == null ) val.changeClosureObjThis(mDest);
					}

					if( mDest.propSetByVS( MEMBERENSURE|IGNOREPROP|flags, param[0].asString(), val, mDest) == E_NOTIMPL )
						mDest.propSet( MEMBERENSURE|IGNOREPROP|flags, param[0].getString(), null, val, mDest );
				}
				if( result != null ) result.set(1); // returns true
				return S_OK;
			}
		};

		Callback callback = new Callback();
		callback.mDest = objthis;

		// enumerate members
		enumMembers( IGNOREPROP, new VariantClosure( callback, null), this);
		return S_OK;
	}
	public int createNew( int flag, final String membername, IntWrapper hint, Holder<Dispatch2> result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// CreateNew
		Dispatch2 dsp = createBaseTJSObject();
		/* デバッグ機能は省く
		// set object type for debugging
		if(TJSObjectHashMapEnabled())
			TJSObjectHashSetType(dsp, TJS_W("instance of class ") + ClassName);
		*/

		// instance initialization
		int hr = funcCall( 0, null, null, null, null, dsp); // add member to dsp
		if( hr < 0 ) return hr;

		IntWrapper chint = new IntWrapper( mClassName.hashCode() );
		hr = funcCall( 0, mClassName, chint, null, param, dsp);
			// call the constructor
		if( hr == E_MEMBERNOTFOUND ) hr = S_OK;
			// missing constructor is OK ( is this ugly ? )
		if( hr >= 0 ) result.set( dsp );

		return hr;
	}
	public int isInstanceOf( int flag, final String membername, IntWrapper hint, final String classname, Dispatch2 objthis ) throws VariantException, TJSException {
		if( membername == null ) {
			if( "Class".equals(classname) ) return S_TRUE;
			if( mClassName != null && mClassName.equals(classname) ) return S_TRUE;
		}
		return super.isInstanceOf(flag, membername, hint, classname, objthis);
	}
	protected void registerMethods( Class<?> c, final String classname ) throws VariantException, TJSException {
		//クラスに存在するメソッドをすべて取得し登録する
		@SuppressWarnings("rawtypes")
		Class[] propArgClass = new Class[]{ Variant.class, Dispatch2.class };
		try {
			Method dennySetMethod;
			Method dennyGetMethod;
			try {
				dennyGetMethod = c.getMethod( "dennyPropGet", propArgClass );
				dennySetMethod = c.getMethod( "dennyPropSet", propArgClass );
			} catch (NoSuchMethodException e1) {
				throw new TJSException(Error.InternalError);
			}
			HashSet<String> registProp = new HashSet<String>(); // set/getで重複しないようにチェック

			Method[] methods = c.getMethods();
			for( Method m : methods ) {
				final String methodName = m.getName();
				final Annotation[] a = m.getDeclaredAnnotations();
				int flag = 0;
				if( a != null ) {
					final int acount = a.length;
					for( int i = 0; i < acount; i++ ) {
						if( a[i] instanceof TJSStatic ) {
							flag |= STATICMEMBER;
						}
					}
				}
				if( "constructor" .equals( methodName ) ) {
					// コンストラクタ
					// パラメータチェック
					@SuppressWarnings("rawtypes")
					Class[] params = m.getParameterTypes();
					if( params.length == 3 ) {
						if( params[0] == Variant.class &&
							params[1] == Variant[].class &&
							params[2] == Dispatch2.class ) {
							registerNCM( classname, new NativeClassConstructor(m), classname, nitMethod, flag );
						}
					}
				} else if( methodName.startsWith("prop_") ) {
					// プロパティ
					@SuppressWarnings("rawtypes")
					Class[] params = m.getParameterTypes();
					if( params.length == 2 ) {
						if( params[0] == Variant.class && params[1] == Dispatch2.class ) {
							Method setMethod = null;
							Method getMethod = null;
							String propName = null;
							if( methodName.startsWith("prop_set_") ) {
								setMethod = m;
								propName = methodName.substring( "prop_set_".length() );
								if( registProp.contains(propName) == false ) {
									try {
										getMethod = c.getMethod( "prop_get_" + propName, propArgClass );
									} catch (NoSuchMethodException e) {
										getMethod = null;
									}
								}
							} else if( methodName.startsWith("prop_get_") ) {
								getMethod = m;
								propName = methodName.substring( "prop_get_".length() );
								if( registProp.contains(propName) == false ) {
									try {
										setMethod = c.getMethod( "prop_set_" + propName, propArgClass );
									} catch (NoSuchMethodException e) {
										setMethod = null;
									}
								}
							}
							if( registProp.contains(propName) == false ) {
								if( setMethod != null || getMethod != null ) {
									if( setMethod == null ) setMethod = dennySetMethod;
									if( getMethod == null ) getMethod = dennyGetMethod;
									registerNCM( propName, new NativeClassProperty(getMethod, setMethod), classname, nitProperty, flag );
									registProp.add(propName);
								}
							}
						}
					}
				} else {
					// 通常メソッド
					// パラメータチェック
					@SuppressWarnings("rawtypes")
					Class[] params = m.getParameterTypes();
					if( params.length == 3 ) {
						if( params[0] == Variant.class &&
							params[1] == Variant[].class &&
							params[2] == Dispatch2.class ) {
							registerNCM( methodName, new NativeClassMethod(m), classname, nitMethod, flag );
						}
					}
				}
			}
			registProp = null;
		} catch (SecurityException e) {
			throw new TJSException(Error.InternalError + e.toString());
		}
	}
	static private final int E_ACCESSDENYED = (-1007);
	public static int dennyPropGet( Variant result, Dispatch2 objthis ) { return E_ACCESSDENYED; }
	public static int dennyPropSet( Variant param, Dispatch2 objthis ) { return E_ACCESSDENYED; }
}
