package jp.kirikiri.tjs2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

public class ArrayClass extends NativeClass {
	static private int mClassID = -1;
	static private final int
		E_BADPARAMCOUNT	= -1004,
		E_NATIVECLASSCRASH = -1008,
		S_OK			= 0;
	static private final int
		NIS_GETINSTANCE		= 0x00000002; // get native pointer

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

		Class<? extends ArrayClass> c = getClass();
		registerMethods( c, __classname );
		ArrayNI.ClassID_Array = NCM_CLASSID;
	}
	protected NativeInstance createNativeInstance() {
		return new ArrayNI(); // return a native object instance.
	}
	protected Dispatch2 createBaseTJSObject() {
		return new ArrayObject();
	}
	public static int getArrayElementCount(Dispatch2 dsp ) throws TJSException {
		// returns array element count
		Holder<NativeInstance> holder = new Holder<NativeInstance>(null);
		if( dsp.nativeInstanceSupport( NIS_GETINSTANCE, mClassID, holder ) < 0 )
			throw new TJSException(Error.SpecifyArray);
		return ((ArrayNI)holder.mValue).mItems.size();
	}
	public static int copyArrayElementTo( Dispatch2 dsp, Variant[] dest, int dest_offset, int start, int count ) throws TJSException {
		// copy array elements to specified variant array.
		// returns copied element count.
		Holder<NativeInstance> holder = new Holder<NativeInstance>(null);
		if( dsp.nativeInstanceSupport( NIS_GETINSTANCE, mClassID, holder) < 0 )
			throw new TJSException(Error.SpecifyArray);

		ArrayNI ni = (ArrayNI) holder.mValue;
		if(count < 0) count = ni.mItems.size();

		if(start >= ni.mItems.size()) return 0;

		int limit = start + count;

		int d = dest_offset;
		for( int i = start; i < limit; i++ ) {
			dest[d] = ni.mItems.get(i);
			d++;
		}

		return limit - start;
	}

	public static ArrayNI getNativeInstance( Dispatch2 objthis ) {
		Holder<NativeInstance> holder = new Holder<NativeInstance>(null);
		int hr = objthis.nativeInstanceSupport( NIS_GETINSTANCE, mClassID, holder );
		if( hr < 0 ) return null;
		return (ArrayNI) holder.mValue;
	}
	public static int constructor( Variant result, Variant[] param, Dispatch2 objthis ) {
		ArrayNI _this;
		Holder<NativeInstance> holder = new Holder<NativeInstance>(null);
		{
			int hr = objthis.nativeInstanceSupport( NIS_GETINSTANCE, mClassID, holder );
			_this = (ArrayNI) holder.mValue;
			if( hr < 0 ) return E_NATIVECLASSCRASH;
			if( _this == null ) return E_NATIVECLASSCRASH;
			hr = _this.construct( param, objthis );
			if( hr < 0 ) return hr;
		}
		return S_OK;
	}
	public static int load( Variant result, Variant[] param, Dispatch2 objthis ) throws TJSException, VariantException {
		if( objthis == null ) return E_NATIVECLASSCRASH;

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		if( param.length < 1 ) return E_BADPARAMCOUNT;

		String name = param[0].asString();
		String mode = null;
		if( param.length >= 2 && param[1].isVoid() == false ) mode = param[1].asString();

		TextReadStream stream = new TextReadStream(name, mode);
		try {
			ni.mItems.clear();
			String content;
			content = stream.read(0); // 全部読み込む
			final int count = content.length();
			int lines = 0;
			int reamain = 0;
			for( int i = 0; i < count; i++ ) {
				reamain++;
				int ch = content.codePointAt(i);
				if( ch == '\r' || ch == '\n' ) {
					if( (i+1) < count ) {
						if( content.codePointAt(i+1) == '\n' && ch == '\r' ) {
							lines++;
							i++;
						}
					}
					reamain = 0;
				}
			}
			if( reamain > 0 ) {
				lines++;
			}
			ni.mItems.clear();
			ni.mItems.ensureCapacity(lines);

			// split to each line
			lines = 0;

			int start = 0;
			for( int i = 0; i < count; i++ ) {
				int ch = content.codePointAt(i);
				if( ch == '\r' || ch == '\n' ) {
					ni.mItems.add( new Variant(content.substring(start, i)) );
					if( (i+1) < count ) {
						if( content.codePointAt(i+1) == '\n' && ch == '\r' ) {
							i++;
						}
					}
					start = i + 1;
				}
			}
			if( start < count ) {
				ni.mItems.add( new Variant(content.substring(start, count)) );
			}
		} finally {
			stream.destruct();
			stream = null;
		}

		if( result != null ) result.set( new Variant(objthis, objthis) );
		return S_OK;
	}

	public static int save( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// saves the array into a file.
		// only string and number stuffs are stored.

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		if( param.length < 1 ) return E_BADPARAMCOUNT;

		String name = param[0].asString();
		String mode = null;
		if( param.length >= 2 && param[1].isVoid() == false ) mode = param[1].asString();
		TextWriteStream stream = new TextWriteStream(name, mode);
		try {
			Iterator<Variant> i = ni.mItems.iterator();
			final String cr = new String( "\n" );
			while( i.hasNext() ) {
				Variant o = i.next();
				if( o != null && (o.isString() || o.isNumber() ) ) {
					stream.write( o.asString() );
				}
				stream.write(cr);
	        }
		} finally {
			stream.destruct();
		}

		if( result != null ) result.set( new Variant(objthis, objthis) );
		return S_OK;
	}
	public static int saveStruct( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// saves the array into a file, that can be interpret as an expression.

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		if( param.length < 1 ) return E_BADPARAMCOUNT;

		String name = param[0].asString();
		String mode = null;
		if( param.length >= 2 && param[1].isVoid() == false ) mode = param[1].asString();
		TextWriteStream stream = new TextWriteStream(name, mode);
		try {
			ArrayList<Dispatch2> stack = new ArrayList<Dispatch2>();
			stack.add(objthis);
			ni.saveStructuredData(stack, stream, "" );
		} finally {
			stream.destruct();
		}

		if( result != null ) result.set( new Variant(objthis, objthis) );
		return S_OK;
	}
	public static int split( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// split string with given delimiters.

		// arguments are : <pattern/delimiter>, <string>, [<reserved>],
		// [<whether ignore empty element>]

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		if( param.length < 2 ) return E_BADPARAMCOUNT;

		ni.mItems.clear();
		String string = param[1].asString();
		boolean purgeempty = false;
		if( param.length >= 4 && param[3].isVoid() == false ) {
			purgeempty = param[3].asBoolean();
		}
		// Enable REGEX
		if( param[0].isObject() ) {
			RegExpNI re;
			VariantClosure clo = param[0].asObjectClosure();
			if( clo.mObject != null ) {
				Holder<NativeInstance> holder = new Holder<NativeInstance>(null);
				if( clo.mObject.nativeInstanceSupport(NIS_GETINSTANCE,RegExpClass.mClassID,holder) >= 0 ) {
					re = (RegExpNI) holder.mValue;
					// param[0] is regexp
					Holder<Dispatch2> array = new Holder<Dispatch2>(objthis);
					re.split( array, string, purgeempty );
					if( result != null ) result.set( new Variant(objthis, objthis) );
					return S_OK;
				}
			}
		}
		String pattern = param[0].asString();
		final int count = string.length();
		int start = 0;
		for( int i = 0; i < count; i++ ) {
			int ch = string.codePointAt(i);
			if( pattern.indexOf(ch) != -1 ) {
				// delimiter found
				if( purgeempty == false || (purgeempty==true && (i-start) != 0) ) {
					ni.mItems.add(  new Variant(string.substring(start,i)) );
				}
				start = i+1;
			}
		}
		if( purgeempty == false || (purgeempty==true && (count-start) >= 0) ) {
			ni.mItems.add( new Variant(string.substring(start,count)));
		}
		if( result != null ) result.set( new Variant(objthis, objthis) );
		return S_OK;
	}
	public static int join( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		// join string with given delimiters.
		// arguments are : <delimiter>, [<reserved>],
		// [<whether ignore empty element>]

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		if( param.length < 1 ) return E_BADPARAMCOUNT;

		String delimiter = param[0].asString();
		boolean purgeempty = false;
		if( param.length >= 3 && param[2].isVoid() != true  ) purgeempty = param[2].asBoolean();

		// join with delimiter
		boolean first = true;
		StringBuilder builer = new StringBuilder(1024);
		final int count = ni.mItems.size();
		for( int i = 0; i < count; i++ ) {
			Variant v = ni.mItems.get(i);
			if( purgeempty && v.isVoid() ) {
			} else {
				if(!first) builer.append(delimiter);
				first = false;
				builer.append(v.asString());
			}
		}
		if( result != null ) result.set( builer.toString() );
		return S_OK;
	}
	public static class ArraySortCompare_NormalAscending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				return lhs.greaterThanForSort( rhs );
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_NormalDescending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				return lhs.littlerThanForSort( rhs );
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_NumericAscending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				if( lhs.isString() && rhs.isString() ) {
					Number l = lhs.asNumber();
					Number r = rhs.asNumber();
					double ret = l.doubleValue() - r.doubleValue();
					if( ret == 0.0 ) return 0;
					else if( ret < 0.0 ) return -1;
					else return 1;
				}
				return lhs.greaterThanForSort( rhs );
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_NumericDescending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				if( lhs.isString() && rhs.isString() ) {
					Number l = lhs.asNumber();
					Number r = rhs.asNumber();
					double ret = r.doubleValue() - l.doubleValue();
					if( ret == 0.0 ) return 0;
					else if( ret < 0.0 ) return -1;
					else return 1;
				}
				return lhs.littlerThanForSort( rhs );
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_StringAscending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				if( lhs.isString() && rhs.isString() ) {
					String l = lhs.getString();
					String r = rhs.getString();
					return l.compareTo(r);
				} else {
					String l = lhs.asString();
					String r = rhs.asString();
					return l.compareTo(r);
				}
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_StringDescending implements Comparator<Variant> {
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				if( lhs.isString() && rhs.isString() ) {
					String l = lhs.getString();
					String r = rhs.getString();
					return r.compareTo(l);
				} else {
					String l = lhs.asString();
					String r = rhs.asString();
					return r.compareTo(l);
				}
			} catch (VariantException e ) {
				return 0;
			}
		}
    }
	public static class ArraySortCompare_Functional implements Comparator<Variant> {
		private VariantClosure mClosure;
		public ArraySortCompare_Functional( VariantClosure clo ) {
			mClosure = clo;
		}
		@Override
		public int compare(Variant lhs, Variant rhs) {
			try {
				Variant result = new Variant();
				Variant[] params = new Variant[2];
				params[0] = lhs;
				params[1] = rhs;
				int hr = mClosure.funcCall(0, null, null, result, params, null );
				if( hr < 0 ) InterCodeContext.throwFrom_tjs_error(hr,null);
				boolean ret = result.asBoolean();
				return ret ? -1 : 1;
			} catch (VariantException e ) {
				return 0;
			} catch (TJSException e) {
				return 0; // ソートの時例外の扱いが変わる
			}
		}
    }
	public static int sort( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		// sort array items.

		// arguments are : [<sort order/comparison function>], [<whether to do stable sort>]

		// sort order is one of:
		// '+' (default)   :  Normal ascending  (comparison by tTJSVariant::operator < )
		// '-'             :  Normal descending (comparison by tTJSVariant::operator < )
		// '0'             :  Numeric value ascending
		// '9'             :  Numeric value descending
		// 'a'             :  String ascending
		// 'z'             :  String descending

		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;

		char method = '+';
		//boolean do_stable_sort = false;
		VariantClosure closure = null;

		if( param.length >= 1 && param[0].isVoid() != true ) {
			// check first argument
			if( param[0].isObject() ) {
				// comarison function object
				closure = param[0].asObjectClosure();
				method = 0;
			} else {
				// sort order letter
				String me = param[0].asString();
				method = me.charAt(0);
				switch(method)
				{
				case '+':
				case '-':
				case '0':
				case '9':
				case 'a':
				case 'z':
					break;
				default:
					method = '+';
					break;
				}
			}
		}

		// Collections.sortが常にstable_sortなのでこれは意味がない
		/*
		if( param.length >= 2 && param[1].isVoid() != true ) {
			// whether to do a stable sort
			do_stable_sort = param[1].asBoolean();
		}
		*/

		// sort
		switch(method)
		{
		case '+':
			Collections.sort( ni.mItems, new ArraySortCompare_NormalAscending() );
			break;
		case '-':
			Collections.sort( ni.mItems, new ArraySortCompare_NormalDescending() );
			break;
		case '0':
			Collections.sort( ni.mItems, new ArraySortCompare_NumericAscending() );
			break;
		case '9':
			Collections.sort( ni.mItems, new ArraySortCompare_NumericDescending() );
			break;
		case 'a':
			Collections.sort( ni.mItems, new ArraySortCompare_StringAscending() );
			break;
		case 'z':
			Collections.sort( ni.mItems, new ArraySortCompare_StringDescending() );
			break;
		case 0:
			Collections.sort( ni.mItems, new ArraySortCompare_Functional(closure) );
			break;
		}
		return S_OK;
	}
	public static int reverse( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		Collections.reverse(ni.mItems);
		return S_OK;
	}
	public static int assign( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 1) return E_BADPARAMCOUNT;
		((ArrayObject)objthis).clear(ni);
		VariantClosure clo = param[0].asObjectClosure();
		if( clo.mObjThis != null )
			ni.assign( clo.mObjThis );
		else if( clo.mObject != null )
			ni.assign(clo.mObject);
		else throw new TJSException(Error.NullAccess);

		return S_OK;
	}
	public static int assignStruct( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 1) return E_BADPARAMCOUNT;
		((ArrayObject)objthis).clear(ni);
		ArrayList<Dispatch2> stack = new ArrayList<Dispatch2>();

		VariantClosure clo = param[0].asObjectClosure();
		if( clo.mObjThis != null )
			ni.assignStructure(clo.mObjThis, stack);
		else if( clo.mObject != null )
			ni.assignStructure(clo.mObject, stack);
		else throw new TJSException(Error.NullAccess);

		return S_OK;
	}
	public static int clear( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		((ArrayObject)objthis).clear(ni);
		return S_OK;
	}
	public static int erase( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// remove specified item number from the array
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if(param.length < 1) return E_BADPARAMCOUNT;
		int num = param[0].asInteger();
		((ArrayObject)objthis).erase(ni, num);
		return S_OK;
	}
	public static int remove( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		// remove specified item from the array wchich appears first or all
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 1) return E_BADPARAMCOUNT;

		boolean eraseall;
		if( param.length >= 2)
			eraseall = param[1].asBoolean();
		else
			eraseall = true;

		Variant val = param[0];
		int count = ((ArrayObject)objthis).remove(ni, val, eraseall);
		if( result != null ) result.set( count );
		return S_OK;
	}
	public static int insert( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// insert item at specified position
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 2) return E_BADPARAMCOUNT;
		int num = param[0].asInteger();
		((ArrayObject)objthis).insert(ni, param[1], num);
		return S_OK;
	}
	public static int add( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		// add item at last
		ArrayNI ni = getNativeInstance(objthis);		// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 1) return E_BADPARAMCOUNT;
		((ArrayObject)objthis).add( ni, param[0] );
		if( result != null ) result.set( ni.mItems.size() - 1 );
		return S_OK;
	}
	public static int push( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// add item(s) at last
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		((ArrayObject)objthis).insert(ni, param, ni.mItems.size());
		if(result != null) result.set( ni.mItems.size() );
		return S_OK;
	}
	public static int pop( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// pop item from last
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( ni.mItems.isEmpty() ) {
			if( result != null ) result.clear();
		} else {
			if( result != null ) result.set( ni.mItems.get(ni.mItems.size() - 1) );
			((ArrayObject)objthis).erase(ni, ni.mItems.size() - 1);
		}
		return S_OK;
	}
	public static int shift( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// shift item at head
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( ni.mItems.isEmpty() ) {
			if( result != null ) result.clear();
		} else {
			if( result != null ) result.set( ni.mItems.get(0) );
			((ArrayObject)objthis).erase(ni, 0);
		}
		return S_OK;
	}
	public static int unshift( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException, TJSException {
		// add item(s) at head
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		((ArrayObject)objthis).insert(ni, param, 0);
		if( result != null ) result.set( ni.mItems.size() );
		return S_OK;
	}
	public static int find( Variant result, Variant[] param, Dispatch2 objthis ) throws VariantException {
		// find item in the array,
		// return an index which points the item that appears first.
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( param.length < 1 ) return E_BADPARAMCOUNT;
		if( result != null ) {
			Variant val = param[0];
			int start = 0;
			if( param.length >= 2) start = param[1].asInteger();
			if(start < 0) start += ni.mItems.size();
			if(start < 0) start = 0;
			if(start >= ni.mItems.size()) { result.set( -1 ); return S_OK; }

			final int count = ni.mItems.size();
			int i;
			for( i = start; i < count; i++ ) {
				Variant v = ni.mItems.get(i);
				if( val.discernCompareInternal(v) ) break;
			}
			if(i == count )
				result.set( -1 );
			else
				result.set( i );
		}
		return S_OK;
	}
	public static int prop_get_count( Variant result, Dispatch2 objthis ) {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		if( result != null ) result.set( ni.mItems.size() );
		return S_OK;
	}
	public static int prop_set_count( Variant param, Dispatch2 objthis ) throws VariantException {
		ArrayNI ni = getNativeInstance(objthis);	// インスタンス所得
		if( ni == null ) return E_NATIVECLASSCRASH;
		int resize = param.asInteger();
		int count = ni.mItems.size();
		if( count < resize ) {
			int addcount = resize - count;
			ni.mItems.ensureCapacity(count);
			for( int i = 0; i < addcount; i++ ) {
				ni.mItems.add( new Variant() );
			}
		} else if( count > resize ) {
			for( int i = count-1; i >= resize; i-- ) {
				ni.mItems.remove( i );
			}
		}
		return S_OK;
	}
	public static int prop_get_length( Variant result, Dispatch2 objthis ) {
		return prop_get_count( result, objthis );
	}
	public static int prop_set_length( Variant param, Dispatch2 objthis ) throws VariantException {
		return prop_set_count( param, objthis);
	}
}
