package jp.kirikiri.tvp2.base;

import jp.kirikiri.tjs2.CompileException;
import jp.kirikiri.tjs2.Dispatch2;
import jp.kirikiri.tjs2.NativeClass;
import jp.kirikiri.tjs2.NativeInstance;
import jp.kirikiri.tjs2.TJS;
import jp.kirikiri.tjs2.TJSException;
import jp.kirikiri.tjs2.TJSScriptError;
import jp.kirikiri.tjs2.TJSScriptException;
import jp.kirikiri.tjs2.TextReadStream;
import jp.kirikiri.tjs2.Variant;
import jp.kirikiri.tjs2.VariantException;
import jp.kirikiri.tjs2.VariantClosure;
import jp.kirikiri.tjs2.Error;
import jp.kirikiri.tvp2.env.Application;
import jp.kirikiri.tvp2.env.PadForm;
import jp.kirikiri.tvp2.msg.Message;
import jp.kirikiri.tvp2.utils.DebugClass;

public class ScriptsClass extends NativeClass {
	static private int mClassID = -1;
	static private final int
		E_MEMBERNOTFOUND = -1001,
		E_NOTIMPL		= -1002,
		E_BADPARAMCOUNT	= -1004,
		E_INVALIDOBJECT	= -1006,
		E_NATIVECLASSCRASH = -1008,
		S_OK			= 0,
		S_TRUE			= 1;
	private static final int
		MEMBERENSURE		= 0x00000200, // create a member if not exists
		MEMBERMUSTEXIST     = 0x00000400, // member *must* exist ( for Dictionary/Array )
		IGNOREPROP			= 0x00000800, // ignore property invoking
		HIDDENMEMBER		= 0x00001000, // member is hidden
		STATICMEMBER		= 0x00010000, // member is not registered to the
										  // object (internal use)
		ENUM_NO_VALUE		= 0x00100000; // values are not retrieved
										  // (for EnumMembers)
	static private final int
		NIS_GETINSTANCE		= 0x00000002; // get native pointer

	static private TJS mScriptEngine;

	public static void initScriptEnging() throws VariantException, TJSException {
		TJS.initialize();
		mScriptEngine = new TJS();

		// mScriptEngine.execScript( InitTJSScript );

		Variant val;
		Dispatch2 dsp;
		Dispatch2 global = mScriptEngine.getGlobal();

		/* classes */
		dsp = new DebugClass();
		val = new Variant(dsp/*, dsp*/);
		global.propSet( MEMBERENSURE|IGNOREPROP, "Debug", null, val, global );

		dsp = new ScriptsClass();
		val = new Variant(dsp/*, dsp*/);
		global.propSet( MEMBERENSURE|IGNOREPROP, "Scripts", null, val, global );

		dsp = new SystemClass();
		val = new Variant(dsp/*, dsp*/);
		global.propSet( MEMBERENSURE|IGNOREPROP, "System", null, val, global );
	}
	public static void executeStartupScript() {
		// TODO 他は後で実装する
		// execute "startup.tjs"
		try {
			DebugClass.addLog("(info) Loading startup script : startup.tjs");
			executeStorage("startup.tjs",null,null,false,null);
			DebugClass.addLog("(info) Startup script ended.");
			// 他の例外はここで受けて変換する
		} catch( TJSScriptException e ) {
			//beforeProcessUnhandledException();
			e.addTrace( "startup" );
			if( !processUnhandledException(e) )
				showScriptException(e);
		} catch( TJSScriptError e) {
			//beforeProcessUnhandledException(); \
			e.addTrace( "startup" );
			if( !processUnhandledException(e) )
				showScriptException(e);
		} catch (TJSException e) {
			//beforeProcessUnhandledException();
			if( !processUnhandledException(e) ) {
				showScriptException(e);
			}
		} catch (CompileException e) {
			e.printStackTrace();
		}
	}
	static private final boolean getSystem_exceptionHandler_Object( VariantClosure dest ) throws VariantException, TJSException {
		// get System.exceptionHandler
		Dispatch2 global = mScriptEngine.getGlobal();
		if( global == null ) return false;

		Variant val = new Variant();
		int er = global.propGet( MEMBERMUSTEXIST, "System", null, val, global);
		if( er < 0 ) return false;
		if( val.isObject() != true ) return false;

		VariantClosure clo = val.asObjectClosure();
		if( clo.mObject == null ) return false;

		Variant val2 = new Variant();
		clo.propGet( MEMBERMUSTEXIST, "exceptionHandler", null, val2, null );
		if( val2.isObject() != true ) return false;

		clo = val2.asObjectClosure();
		dest.set( clo.mObject, clo.mObjThis );
		if( dest.mObject == null ) {
			dest.set( null, null );
			return false;
		}

		return true;
	}
	private static final boolean processUnhandledException( TJSException e ) {
		boolean result = false;
		VariantClosure clo = new VariantClosure(null,null);
		try {
			// get the script engine
			TJS engine = mScriptEngine;
			if( engine == null )
				return false; // the script engine had been shutdown

			// get System.exceptionHandler
			if(!getSystem_exceptionHandler_Object(clo))
				return false; // System.exceptionHandler cannot be retrieved

			// execute clo
			Variant obj = new Variant();
			Variant msg = new Variant( e.getMessage() );
			Error.getExceptionObject( engine, obj, msg, null );

			Variant[] pval = new Variant[1];
			pval[0] = obj;

			Variant res = new Variant();
			clo.funcCall(0, null, null, res, pval, null);

			result = res.asBoolean();
		} catch( TJSScriptError es ) {
			showScriptException(es);
		} catch( TJSException et ) {
			showScriptException(et);
		}
		return result;
	}
	/**
	 * These functions display the error location, reason, etc.
	 * And disable the script event dispatching to avoid massive occurrence of
	 * errors.
	*/
	static private void showScriptException( TJSException e ) {
		//TVPSetSystemEventDisabledState(true);
		//TVPOnError();

		//if(!TVPSystemUninitCalled)
		{
			//if(TVPMainForm) TVPMainForm->Visible = true;
			String errstr = Message.ScriptExceptionRaised + "\n" + e.getMessage();
			DebugClass.addLog( Message.ScriptExceptionRaised + "\n" + e.getMessage() );
			Application.messageBox( errstr, "Error", 0 );
		}
	}
	//---------------------------------------------------------------------------
	static private void showScriptException( TJSScriptError e ) {
		//TVPSetSystemEventDisabledState(true);
		//TVPOnError();

		//if(!TVPSystemUninitCalled)
		{

			//if(TVPMainForm) TVPMainForm->Visible = true;
			/* TODO 例外発生箇所を表示する */

			PadForm pad = new PadForm();
			pad.setFreeOnTerminate( true );
			pad.setExecButtonEnabled( false );
			pad.setLines( e.getBlock().getScript() );
			pad.setReadOnly( true );
			pad.setStatusText( e.getMessage() );
			pad.setCaption( Message.ExceptionCDPName );
			pad.setVisible( true );
			pad.goToLine( e.getBlock().srcPosToLine(e.getPosition() ) - e.getBlock().getLineOffset() );

			String errstr = Message.ScriptExceptionRaised + "\n" + e.getMessage();
			DebugClass.addLog( Message.ScriptExceptionRaised + "\n" + e.getMessage() );
			if( e.getTrace().length() != 0) {
				DebugClass.addLog( "trace : " + e.getTrace() );
			}
			Application.messageBox( errstr, "Error", 0 );
			//	throw EAbort("Script Error Abortion");
		}
	}

	protected NativeInstance createNativeInstance() {
		return null;
	}
	public static void executeStorage( final String name, Dispatch2 context, Variant result, boolean isexpression, final String modestr ) throws TJSException, VariantException, CompileException {
		if( mScriptEngine == null ) throw new TJSException( Error.InternalError );

		//String place = StorageClass.searchPlacedPath(name); TODO 後で実装すること
		String shortname = StorageClass.extractStorageName(name);
		TextReadStream stream = new TextReadStream(name,modestr);
		String buffer;
		try {
			buffer = stream.read(0);
		} finally {
			stream.destruct();
		}

		if( isexpression == false ) {
			mScriptEngine.execScript(buffer, result, context, shortname, 0 );
		} else {
			mScriptEngine.evalExpression( buffer, result, context, shortname, 0 );
		}
	}
	private static void dumpScriptEngine() throws VariantException {
		// TODO 他は後で実装する
		if( mScriptEngine != null ) mScriptEngine.dump();
	}

	public ScriptsClass() throws VariantException, TJSException {
		super("Scripts");
		final String __classname = "Scripts";
		final int NCM_CLASSID = TJS.registerNativeClass(__classname);
		setClassID( NCM_CLASSID );
		mClassID = NCM_CLASSID;

		Class<? extends ScriptsClass> c = getClass();
		registerMethods( c, __classname );
	}
	public static int constructor( Variant result, Variant[] param, Dispatch2 objthis ) {
		return S_OK;
	}
	public static int finalize( Variant result, Variant[] param, Dispatch2 objthis ) {
		return S_OK;
	}
	public static int execStorage( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException, CompileException {
		if( param.length < 1 ) return E_BADPARAMCOUNT;
		String name = param[0].asString();
		String modestr = null;
		if( param.length >=2 && param[1].isVoid() != true ) {
			modestr = param[1].asString();
		}
		Dispatch2 context = param.length >= 3 && param[2].isVoid() != true ? param[2].asObject() : null;
		executeStorage( name, context, result, false, modestr );
		return S_OK;
	}
	public static int evalStorage( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException, CompileException {
		if( param.length < 1 ) return E_BADPARAMCOUNT;
		String name = param[0].asString();
		String modestr = null;
		if( param.length >=2 && param[1].isVoid() != true ) {
			modestr = param[1].asString();
		}
		Dispatch2 context = param.length >= 3 && param[2].isVoid() != true ? param[2].asObject() : null;
		executeStorage( name, context, result, true, modestr );
		return S_OK;
	}
	public static int dump( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException, CompileException {
		// execute given string as a script
		dumpScriptEngine();
		return S_OK;
	}
}
