package jp.kirikiri.tjs2;

import java.util.ArrayList;
import java.util.HashMap;

public class TJS {
	private static final String TAG ="TJS";

	private static final int VERSION_MAJOR   = 2;
	private static final int VERSION_MINOR   = 4;
	private static final int VERSION_RELEASE = 28;
	private static final int VERSION_HEX = VERSION_MAJOR * 0x1000000 + VERSION_MINOR * 0x10000 + VERSION_RELEASE;

	private static final int ENV_WINDOWS = 0; // プリプロセッサでは未定義の時この値が入る
	private static final int ENV_ANDROID = 1;
	private static final int ENV_JAVA_APPLICATION = 2;
	private static final int ENV_JAVA_APPLET = 3;

	private static final int GLOBAL_HASH_BITS = 7;
	private static final int MEMBERENSURE = 0x00000200; // create a member if not exists

	private HashMap<String,Integer>	mPPValues;
	private static GlobalStringMap mGlobalStringMap = null;
	private static ArrayList<String> mNativeClassNames = null;

	private ArrayList<ScriptBlock> mScriptBlocks;
	private static ConsoleOutput mConsoleOutput;
	private CustomObject mGlobal;
	private ScriptCache mCache;

	public static boolean mWarnOnNonGlobalEvalOperator = false;
	public static boolean mUnaryAsteriskIgnoresPropAccess = false;
	public static boolean mEvalOperatorIsOnGlobal = false;
	public static boolean EnableDebugMode = true;

	private static Dispatch2 mArrayClass;
	private static Dispatch2 mDictionayClass;

	//public static int mCompactVariantArrayMagic;
	//public static VariantArrayStack mVariantArrayStack;

	public TJS() throws VariantException, TJSException {
		// create script cache object
		mCache = new ScriptCache(this);

		mPPValues = new HashMap<String,Integer>();
		setPPValue( "version", VERSION_HEX );
		setPPValue( "environment", ENV_JAVA_APPLICATION ); // TODO 適切な値を入れる

		mGlobal = new CustomObject(GLOBAL_HASH_BITS);

		mScriptBlocks = new ArrayList<ScriptBlock>();

		Dispatch2 dsp;
		Variant val;

		// Array
		dsp = new ArrayClass();
		val = new Variant(dsp,null);
		mGlobal.propSet(MEMBERENSURE, "Array", null, val, mGlobal);

		// Dictionary
		dsp = new DictionaryClass();
		val = new Variant(dsp, null);
		mGlobal.propSet(MEMBERENSURE, "Dictionary", null, val, mGlobal );

		// Date
		dsp = new DateClass();
		val = new Variant(dsp, null);
		mGlobal.propSet( MEMBERENSURE, "Date", null, val, mGlobal);

		// Math
		{
			Dispatch2 math;

			dsp = math = new MathClass();
			val = new Variant(dsp, null);
			mGlobal.propSet( MEMBERENSURE, "Math", null, val, mGlobal );

			// Math.RandomGenerator
			dsp = new RandomGeneratorClass();
			val = new Variant(dsp, null);
			math.propSet( MEMBERENSURE, "RandomGenerator", null, val, math );
		}
		// Exception
		dsp = new ExceptionClass();
		val = new Variant(dsp, null);
		mGlobal.propSet(MEMBERENSURE, "Exception", null, val, mGlobal );

		// RegExp
		dsp = new RegExpClass();
		val = new Variant(dsp,null);
		mGlobal.propSet(MEMBERENSURE, "RegExp", null, val, mGlobal);
	}
	public void setPPValue( final String name, int value ) {
		if( name != null ) {
			mPPValues.put( name, Integer.valueOf(value) );
		}
	}
	public int getPPValue( final String name ) {
		Integer ret = mPPValues.get(name);
		if( ret == null ) {
			return 0;
		}
		return ret.intValue();
	}
	public static void outputExceptionToConsole( String msg ) { outputToConsole(msg); }
	public static void outputToConsole(String mes) {
		if( mConsoleOutput == null ) {
			Logger.log( mes );
		} else {
			mConsoleOutput.print(mes);
		}
	}
	public static void outputToConsoleSeparator(String text, int count) {
		int len = text.length();
		StringBuilder builder = new StringBuilder(len*count);
		while( count > 0 ) {
			builder.append(text);
			count--;
		}
		outputToConsole(builder.toString());
	}
	public static String mapGlobalStringMap( final String str ) {
		return mGlobalStringMap.map( str );
	}
	public static int registerNativeClass( final String name ) {
		final int count = mNativeClassNames.size();
		for( int i = 0; i < count; i++ ) {
			if( mNativeClassNames.get(i).equals(name) ) return i;
		}
		mNativeClassNames.add( mapGlobalStringMap(name) );
		return mNativeClassNames.size() - 1;
	}
	public static int findNaitveClassID( final String name ) {
		final int count = mNativeClassNames.size();
		for( int i = 0; i < count; i++ ) {
			if( mNativeClassNames.get(i).equals(name) ) return i;
		}
		return -1;
	}
	public static final String findNativeClassName( int id ) {
		if( id < 0 || id >= mNativeClassNames.size() ) return null;
		return mNativeClassNames.get(id);
	}
	public static void setConsoleOutput( ConsoleOutput console ) {
		mConsoleOutput = console;
	}
	public static int getDictionaryClassID() {
		return DictionaryNI.ClassID_Dictionary;
	}
	public static int getArrayClassID() {
		return ArrayNI.ClassID_Array;
	}
	// static 関係はここで初期化
	public static void initialize() {
		mWarnOnNonGlobalEvalOperator = false;
		mUnaryAsteriskIgnoresPropAccess = false;
		mNativeClassNames = new ArrayList<String>();
		mGlobalStringMap = new GlobalStringMap();
		//mCompactVariantArrayMagic = 0;
		//mVariantArrayStack = new VariantArrayStack();
		mEvalOperatorIsOnGlobal = false;
		mConsoleOutput = null;

		RandomGeneratorNI.setRandomBits128(null);
		//ArrayNI.register();

		DictionaryObject.initialize();
		try {
			mArrayClass = new ArrayClass();
			mDictionayClass = new DictionaryClass();
		} catch (VariantException e) {
		} catch (TJSException e) {
		}
	}
	/*
	public static void variantArrayStackCompact() {
		mCompactVariantArrayMagic++;
	}
	public static void VariantArrayStackCompactNow() {
		if( mVariantArrayStack != null ) mVariantArrayStack.compact();
	}
	*/

	public void execScript( final char[] script, Variant result, Dispatch2 context, final char[] name, int lineofs ) throws VariantException, TJSException, CompileException {
		if( mCache != null ) mCache.execScript( script, result, context, name, lineofs );
	}

	public void execScript( final String script, Variant result, Dispatch2 context, final String name, int lineofs ) throws VariantException, TJSException, CompileException {
		if( mCache != null ) mCache.execScript( script, result, context, name, lineofs );
	}
	public void evalExpression( final char[] expression, Variant result, Dispatch2 context, final char[] name, int lineofs ) throws VariantException, TJSException, CompileException {
		if( mCache != null ) mCache.evalExpression(expression, result, context, name, lineofs);

	}
	public void evalExpression( final String expression, Variant result, Dispatch2 context, final String name, int lineofs ) throws VariantException, TJSException, CompileException {
		if( mCache != null ) mCache.evalExpression(expression, result, context, name, lineofs);
	}
	public Dispatch2 getGlobal() { return mGlobal; }
	public static Dispatch2 createArrayObject() throws VariantException, TJSException { return createArrayObject(null); }
	public static Dispatch2 createArrayObject( Holder<Dispatch2> classout ) throws VariantException, TJSException {
		if( classout != null ) classout.mValue = mArrayClass;
		Holder<Dispatch2> holder = new Holder<Dispatch2>(null);
		mArrayClass.createNew(0, null, null, holder, null, mArrayClass );
		return holder.mValue;
	}
	public static Dispatch2 createDictionaryObject() throws VariantException, TJSException { return createDictionaryObject(null); }
	public static Dispatch2 createDictionaryObject( Holder<Dispatch2> classout ) throws VariantException, TJSException {
		if( classout != null ) classout.mValue = mArrayClass;
		Holder<Dispatch2> holder = new Holder<Dispatch2>(null);
		mDictionayClass.createNew(0, null, null, holder, null, mDictionayClass );
		return holder.mValue;
	}
	public void addScriptBlock(ScriptBlock block) {
		mScriptBlocks.add(block);
	}
	public void removeScriptBlock(ScriptBlock block) {
		mScriptBlocks.remove(block);
	}
	public void dump() throws VariantException { dump(80); }
	// dumps all existing script block
	public void dump( int width ) throws VariantException {

		// dumps all existing script block
		String version = String.format( "TJS version %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_RELEASE);

		outputToConsoleSeparator( "#", width);
		//outputToConsoleWithCentering( "TJS Context Dump", width);
		outputToConsole( "TJS Context Dump" );
		outputToConsoleSeparator( "#", width);
		outputToConsole( version );
		outputToConsole( "" );

		if( mScriptBlocks.size() > 0 ) {
			String buf = String.format( "Total %d script block(s)", mScriptBlocks.size() );
			outputToConsole(buf);
			outputToConsole( "" );

			int totalcontexts = 0;
			int totalcodesize = 0;
			int totaldatasize = 0;
			for( int i = 0; i < mScriptBlocks.size(); i++ ) {
				ScriptBlock b = mScriptBlocks.get(i);
				int n;
				final String name = b.getName();

				String title;
				if( name != null )
					title = b.getNameInfo();
				else
					title = "(no-named script block)";

				String ptr = String.format( " 0x%08X", b.hashCode() );

				title += ptr;

				outputToConsole( title );

				n = b.getContextCount();
				totalcontexts += n;
				buf = String.format( "\tCount of contexts      : %d", n );
				outputToConsole(buf);

				n = b.getTotalVMCodeSize();
				totalcodesize += n;
				buf = String.format( "\tVM code area size      : %d words", n);
				outputToConsole(buf);

				n = b.getTotalVMDataSize();
				totaldatasize += n;
				buf = String.format( "\tVM constant data count : %d", n);
				outputToConsole(buf);
				outputToConsole( "" );
			}

			buf = String.format( "Total count of contexts      : %d", totalcontexts);
			outputToConsole(buf);
			buf = String.format( "Total VM code area size      : %d words", totalcodesize);
			outputToConsole(buf);
			buf = String.format( "Total VM constant data count : %d", totaldatasize);
			outputToConsole(buf);
			outputToConsole( "" );


			for( int i = 0; i < mScriptBlocks.size(); i++ ) {
				ScriptBlock b = mScriptBlocks.get(i);
				outputToConsoleSeparator( "-", width);
				final String name = b.getName();
				String title;
				if( name != null )
					title = b.getNameInfo();
				else
					title = "(no-named script block)";

				String ptr;
				ptr = String.format( " 0x%08X", b.hashCode() );
				title += ptr;

				//outputToConsoleWithCentering(title, width);
				outputToConsole( title );

				outputToConsoleSeparator( "-", width);

				b.dump();

				outputToConsole( "" );
				outputToConsole( "" );
			}
		} else {
			outputToConsole( "" );
			outputToConsole( "There are no script blocks in the system." );
		}
	}
}

