package railo.runtime.type;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Date;

import railo.commons.digest.WangJenkins;
import railo.commons.lang.SizeOf;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.CasterException;
import railo.runtime.exp.PageException;
import railo.runtime.op.Castable;
import railo.runtime.op.Caster;
import railo.runtime.op.Operator;
import railo.runtime.op.date.DateCaster;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.dt.DateTime;

public class KeyImpl implements Collection.Key,Castable,Comparable,Sizeable,Externalizable,WangJenkins {

	private static final long serialVersionUID = -8864844181140115609L; // do not change

	
	private static final long[] byteTable = createLookupTable();
	private static final long HSTART = 0xBB40E64DA205B064L;
	private static final long HMULT = 7664345821815920749L;
	
	//private boolean intern;
	private String key;
	private transient String lcKey;
	private transient String ucKey;
	private transient int wjh;
	private transient int sfm=-1;
	private transient long h64;
	
	public KeyImpl() {
		// DO NOT USE, JUST FOR UNSERIALIZE
	}
	
	private static final long[] createLookupTable() {
		long[] _byteTable = new long[256];
		long h = 0x544B2FBACAAF1684L;
		for (int i = 0; i < 256; i++) {
			for (int j = 0; j < 31; j++) {
				h = (h >>> 7) ^ h;
				h = (h << 11) ^ h;
				h = (h >>> 10) ^ h;
			}
			_byteTable[i] = h;
		}
		return _byteTable;
	}

	private static final long createHash64(CharSequence cs) {
		long h = HSTART;
		final long hmult = HMULT;
		final long[] ht = byteTable;
		final int len = cs.length();
		for (int i = 0; i < len; i++) {
			char ch = cs.charAt(i);
			h = (h * hmult) ^ ht[ch & 0xff];
			h = (h * hmult) ^ ht[(ch >>> 8) & 0xff];
		}
		return h;
	}
	
	@Override
	public int wangJenkinsHash() {
		if(wjh==0) {
			int h = hashCode();
			h += (h <<  15) ^ 0xffffcd7d;
	        h ^= (h >>> 10);
	        h += (h <<   3);
	        h ^= (h >>>  6);
	        h += (h <<   2) + (h << 14);
	        wjh= h ^ (h >>> 16);
		}
		return wjh;
	}
	
	public int slotForMap() {
		if(sfm == -1) {
	    	int h = 0;
	        h ^= hashCode();
	        h ^= (h >>> 20) ^ (h >>> 12);
	        sfm = h ^ (h >>> 7) ^ (h >>> 4);
		}
		return sfm;
    }

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(key);
	}

	public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
		key=(String) in.readObject();
		ucKey=key.toUpperCase();
		h64=createHash64(ucKey);	
	}

	public KeyImpl(String key) {
		this.key=key;
		this.ucKey=key.toUpperCase();
		h64=createHash64(ucKey);
	}

	/**
	 * for dynamic loading of key objects
	 * @param string
	 * @return
	 */
	public static Collection.Key init(String key) {
		//return KeyConstants.getKey(key);
		//if(KeyConstants.getFieldName(key)!=null)print.ds(key);
		return new KeyImpl(key);
	}
	

	public static Collection.Key _const(String key) {
		//return KeyConstants.getKey(key);
		//if(KeyConstants.getFieldName(key)!=null)print.ds(key);
		return new KeyImpl(key);
	}
	
	public synchronized static Collection.Key getInstance(String key) {
		//return KeyConstants.getKey(key);
		//if(KeyConstants.getFieldName(key)!=null)print.ds(key);
		return new KeyImpl(key);
	}
	

	public synchronized static Collection.Key intern(String key) {
		//return KeyConstants.getKey(key);
		//if(KeyConstants.getFieldName(key)!=null)print.ds(key);
		return new KeyImpl(key);
	}
	
	@Override
	public char charAt(int index) {
		return key.charAt(index);
	}

	@Override
	public char lowerCharAt(int index) {
		return getLowerString().charAt(index);
	}
	
	public char upperCharAt(int index) {
		return ucKey.charAt(index);
	}

	@Override
	public String getLowerString() {
		if(lcKey==null)lcKey=StringUtil.toLowerCase(key);
		return lcKey;
	}
	
	public String getUpperString() {
		return ucKey;
	}

	@Override
	public String toString() {
		return key;
	}

	@Override
	public String getString() {
		return key;
	}

	@Override
	public boolean equals(Object other) {
		if(this==other) return true;
		if(other instanceof KeyImpl)	{
			return hash()==((KeyImpl)other).hash();
		}
		if(other instanceof String)	{
			return key.equalsIgnoreCase((String)other);
		}
		if(other instanceof Key)	{
			return ucKey.equalsIgnoreCase(((Key)other).getUpperString());
		}
		return false;
	}


	@Override
	public boolean equalsIgnoreCase(Key other) {
		if(this==other) return true;
		if(other instanceof KeyImpl)	{
			return h64==((KeyImpl)other).h64;//return lcKey.equals((((KeyImpl)other).lcKey));
		}
		return ucKey.equalsIgnoreCase(other.getLowerString());
	}

	@Override
	public int hashCode() {
		return ucKey.hashCode();
	}
	
	// FUTURE add to interface
	public long hash() {
		return h64;
	}

	@Override
	public int getId() {// set to deprecated, use instead hash()
		return hashCode();
	}

	@Override
	public boolean castToBooleanValue() throws PageException {
		return Caster.toBooleanValue(key);
	}
    
    @Override
    public Boolean castToBoolean(Boolean defaultValue) {
        return Caster.toBoolean(key,defaultValue);
    }

	@Override
	public DateTime castToDateTime() throws PageException {
		return Caster.toDatetime(key,null);
	}
    
    @Override
    public DateTime castToDateTime(DateTime defaultValue) {
        return DateCaster.toDateAdvanced(key,true,null,defaultValue);
    }

	@Override
	public double castToDoubleValue() throws PageException {
		return Caster.toDoubleValue(key);
	}
    
    @Override
    public double castToDoubleValue(double defaultValue) {
    	return Caster.toDoubleValue(key,defaultValue);
    }

	@Override
	public String castToString() throws PageException {
		return key;
	}

	@Override
	public String castToString(String defaultValue) {
		return key;
	}

	@Override
	public int compareTo(boolean b) throws PageException {
		return Operator.compare(key, b);
	}

	@Override
	public int compareTo(DateTime dt) throws PageException {
		return Operator.compare(key, (Date)dt);
	}

	@Override
	public int compareTo(double d) throws PageException {
		return Operator.compare(key, d);
	}

	@Override
	public int compareTo(String str) throws PageException {
		return Operator.compare(key, str);
	}
	

	public int compareTo(Object o) {
		try {
			return Operator.compare(key, o);
		} catch (PageException e) {
			ClassCastException cce = new ClassCastException(e.getMessage());
			cce.setStackTrace(e.getStackTrace());
			throw cce;
			
		}
	}
	

	public static Array toUpperCaseArray(Key[] keys) {
		ArrayImpl arr=new ArrayImpl();
		for(int i=0;i<keys.length;i++) {
			arr._append(((KeyImpl)keys[i]).getUpperString());
		}
		return arr;
	}
	public static Array toLowerCaseArray(Key[] keys) {
		ArrayImpl arr=new ArrayImpl();
		for(int i=0;i<keys.length;i++) {
			arr._append(((KeyImpl)keys[i]).getLowerString());
		}
		return arr;
	}
	
	public static Array toArray(Key[] keys) {
		ArrayImpl arr=new ArrayImpl();
		for(int i=0;i<keys.length;i++) {
			arr._append(((KeyImpl)keys[i]).getString());
		}
		return arr;
	}

	public static String toUpperCaseList(Key[] array, String delimiter) {
		if(array.length==0) return "";
		StringBuffer sb=new StringBuffer(((KeyImpl)array[0]).getUpperString());
		
		if(delimiter.length()==1) {
			char c=delimiter.charAt(0);
			for(int i=1;i<array.length;i++) {
				sb.append(c);
				sb.append(((KeyImpl)array[i]).getUpperString());
			}
		}
		else {
			for(int i=1;i<array.length;i++) {
				sb.append(delimiter);
				sb.append(((KeyImpl)array[i]).getUpperString());
			}
		}
		return sb.toString();
	}

	public static String toList(Key[] array, String delimiter) {
		if(array.length==0) return "";
		StringBuilder sb=new StringBuilder(((KeyImpl)array[0]).getString());
		
		if(delimiter.length()==1) {
			char c=delimiter.charAt(0);
			for(int i=1;i<array.length;i++) {
				sb.append(c);
				sb.append((array[i]).getString());
			}
		}
		else {
			for(int i=1;i<array.length;i++) {
				sb.append(delimiter);
				sb.append((array[i]).getString());
			}
		}
		return sb.toString();
	}

	public static String toLowerCaseList(Key[] array, String delimiter) {
		if(array.length==0) return "";
		StringBuffer sb=new StringBuffer(((KeyImpl)array[0]).getLowerString());
		
		if(delimiter.length()==1) {
			char c=delimiter.charAt(0);
			for(int i=1;i<array.length;i++) {
				sb.append(c);
				sb.append(((KeyImpl)array[i]).getLowerString());
			}
		}
		else {
			for(int i=1;i<array.length;i++) {
				sb.append(delimiter);
				sb.append(((KeyImpl)array[i]).getLowerString());
			}
		}
		return sb.toString();
	}

	public static Collection.Key toKey(Object obj, Collection.Key defaultValue) {
		if(obj instanceof Collection.Key) return (Collection.Key) obj;
		String str = Caster.toString(obj,null);
		if(str==null) return defaultValue;
		return init(str);
	}

	public static Collection.Key toKey(Object obj) throws CasterException {
		if(obj instanceof Collection.Key) return (Collection.Key) obj;
		String str = Caster.toString(obj,null);
		if(str==null) throw new CasterException(obj,Collection.Key.class);
		return init(str);
	}


	public long sizeOf() {
		return 
		SizeOf.size(this.key)+
		SizeOf.size(this.lcKey)+
		SizeOf.size(this.ucKey)+
		SizeOf.REF_SIZE;
	}

	@Override
	public int length() {
		return key.length();
	}


	public static Key[] toKeyArray(String[] arr) {
		if(arr==null) return null;
		
		Key[] keys=new Key[arr.length];
		for(int i=0;i<keys.length;i++){
			keys[i]=init(arr[i]);
		}
		return keys;
	}	  
}