/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2012 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * Andy White
#
************************************************************************ */
package divconq.struct;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;

import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.LocalTime;

import divconq.lang.BigDateTime;
import divconq.lang.Memory;
import divconq.lang.OperationResult;
import divconq.schema.DataType;
import divconq.struct.builder.ICompositeBuilder;
import divconq.struct.scalar.AnyStruct;
import divconq.struct.scalar.BigDateTimeStruct;
import divconq.struct.scalar.BigIntegerStruct;
import divconq.struct.scalar.BinaryStruct;
import divconq.struct.scalar.BooleanStruct;
import divconq.struct.scalar.DateTimeStruct;
import divconq.struct.scalar.DecimalStruct;
import divconq.struct.scalar.IntegerStruct;
import divconq.struct.scalar.StringStruct;
import divconq.util.StringUtil;
import divconq.util.TimeUtil;
import divconq.xml.XElement;
import divconq.xml.XmlReader;

abstract public class Struct {
	protected DataType type = null;
	
	public DataType getType() {
		return this.type;
	}

	public void setType(DataType v) {
		this.type = v;
	}	
	
	public Struct() {
	}
	
	public Struct(DataType type) {
		this.type = type;
	}

	// just a reminder of the things to override in types
	
	@Override
	abstract public Object clone();
	
	@Override
	abstract public String toString();

	/**
	 * @return true if contains no data or insufficient data to constitute a complete value
	 */
	abstract public boolean isEmpty();
		
	public void validate(OperationResult mr) {
		this.validate(mr, this.type);
	}
	
	public void validate(OperationResult mr, DataType type) {
		if (type == null)
			mr.errorTr(522);		
		else
			type.validate(this, mr);
	}
		
	public OperationResult validate() {
		OperationResult mr = new OperationResult(false);
		this.validate(mr, this.type);
		return mr;
	}
	
	public OperationResult validate(DataType type) {
		OperationResult mr = new OperationResult(false);
		this.validate(mr, type);
		return mr;
	}
	
	/**
	 * used in conjunction with ComponentStruct - used in scripting primarily
	 * 
	 * In scripting ComponentStruct must be executed on a variable (not as a part of a composite)
	 * and, therefore, when the variable scope goes so must the component that is wrapped.
	 * Otherwise dispose doesn't do much as we trust and prefer GC.
	 */
	public void dispose() {
		
	}
	
	// statics
	
	static public Long objectToInteger(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToInteger(((FieldStruct)o).getValue());
		
		if (o instanceof Long)
			return (Long)o;
		
		if (o instanceof Number)
			return ((Number)o).longValue();
		
		if (o instanceof IntegerStruct)
			return ((IntegerStruct)o).getValue();
		
		if (o instanceof CharSequence) {
			try {
				return new Long(o.toString());
			}
			catch (Exception x) {
			}
		}
		
		return null;
	}
	
	static public BigInteger objectToBigInteger(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToBigInteger(((FieldStruct)o).getValue());
		
		if (o instanceof BigInteger)
			return (BigInteger)o;
		
		if (o instanceof Number)
			return new BigInteger(o.toString());
		
		if (o instanceof BigIntegerStruct)
			return ((BigIntegerStruct)o).getValue();
		
		if (o instanceof IntegerStruct)
			return new BigInteger(((IntegerStruct)o).getValue() + "");
		
		if (o instanceof CharSequence) {
			try {
				return new BigInteger(o.toString());
			}
			catch (Exception x) {
			}
		}
		
		return null;
	}
	
	static public BigDecimal objectToDecimal(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToDecimal(((FieldStruct)o).getValue());
		
		if (o instanceof DecimalStruct)
			return ((DecimalStruct)o).getValue();
		
		if (o instanceof Number) {
			if (o instanceof BigDecimal)
				return (BigDecimal)o;
			
			return new BigDecimal(((Number)o).doubleValue());
		}
		
		if (o instanceof CharSequence) {
			try {
				return new BigDecimal(o.toString());
			}
			catch (Exception x) {
			}
		}
		
		return null;
	}

	// returns only true only if can be a valid Number types - Long, BigInteger or BigDecimal
	public static boolean objectIsNumber(Object o) {
		if (o == null)
			return false;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectIsNumber(((FieldStruct)o).getValue());
		
		if (o instanceof DecimalStruct)
			return true;
		
		if (o instanceof IntegerStruct)
			return true;
		
		if (o instanceof BigIntegerStruct)
			return true;
		
		if (o instanceof Number) {
			if (o instanceof BigDecimal)
				return true;
			
			if (o instanceof BigInteger)
				return true;
			
			if (o instanceof Long)
				return true;
			
			if (o instanceof Integer)
				return true;
			
			if (o instanceof Short)
				return true;
			
			if (o instanceof Byte)
				return true;
			
			if (o instanceof Float)
				return true;
			
			if (o instanceof Double)
				return true;
			
			return false;
		}
		
		if (o instanceof CharSequence) {
			try {
				new BigDecimal(o.toString());
				return true;
			}
			catch (Exception x) {
			}
		}
		
		return false;
	}

	// returns only valid Number types - Long, BigInteger or BigDecimal
	public static Number objectToNumber(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToNumber(((FieldStruct)o).getValue());
		
		if (o instanceof IntegerStruct)
			return ((IntegerStruct)o).getValue();
		
		if (o instanceof BigIntegerStruct)
			return ((BigIntegerStruct)o).getValue();
		
		if (o instanceof DecimalStruct)
			return ((DecimalStruct)o).getValue();
		
		if (o instanceof Number) {
			if (o instanceof BigDecimal)
				return (Number) o;
			
			if (o instanceof BigInteger)
				return (Number) o;
			
			if (o instanceof Long)
				return (Number) o;
			
			if (o instanceof Integer)
				return ((Number) o).longValue();
			
			if (o instanceof Short)
				return ((Number) o).longValue();
			
			if (o instanceof Byte)
				return ((Number) o).longValue();
			
			if (o instanceof Float)
				return new BigDecimal(((Number) o).floatValue());
			
			if (o instanceof Double)
				return new BigDecimal(((Number) o).doubleValue());
			
			return null;
		}
		
		if (o instanceof CharSequence) {
			String num = o.toString();
			
			if (StringUtil.isNotEmpty(num)) {
				if (!num.contains(".")) {
					// try to fit in 64 bit
					try {
						return new Long(num);
					}
					catch (Exception x) {
					}
					
					// otherwise try to fit in big int
					try {
						return new BigInteger(num);
					}
					catch (Exception x) {
					}
				}
				else {
					try {
						return new BigDecimal(num);
					}
					catch (Exception x) {
					}
				}
			}
		}
		
		return null;
	}
	
	static public boolean objectIsBoolean(Object o) {
		if (o == null)
			return false;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectIsBoolean(((FieldStruct)o).getValue());
		
		if (o instanceof Boolean)
			return true;
		
		if (o instanceof BooleanStruct)
			return true;
		
		if (o instanceof CharSequence) {
			try {
				new Boolean(o.toString());
				return true;
			}
			catch (Exception x) {
			}
		}
		
		return false;
	}
	
	static public Boolean objectToBoolean(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToBoolean(((FieldStruct)o).getValue());
		
		if (o instanceof Boolean)
			return (Boolean)o;
		
		if (o instanceof BooleanStruct)
			return ((BooleanStruct)o).getValue();
		
		if (o instanceof CharSequence) {
			try {
				return new Boolean(o.toString().trim());
			}
			catch (Exception x) {
			}
		}

		return null;
	}
	
	static public DateTime objectToDateTime(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToDateTime(((FieldStruct)o).getValue());
		
		if (o instanceof DateTime)
			return (DateTime)o;
		
		if (o instanceof DateTimeStruct)
			return ((DateTimeStruct)o).getValue();
		
		if (o instanceof CharSequence) {
			try {
				return TimeUtil.parseDateTime(o.toString());
			}
			catch (Exception x) {
			}
		}
		
		return null;
	}
	
	static public BigDateTime objectToBigDateTime(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o instanceof FieldStruct)
			return Struct.objectToBigDateTime(((FieldStruct)o).getValue());
		
		if (o instanceof DateTime)
			return new BigDateTime((DateTime) o);
		
		if (o instanceof BigDateTime)
			return (BigDateTime)o;
		
		if (o instanceof DateTimeStruct)
			return new BigDateTime(((DateTimeStruct)o).getValue());
		
		if (o instanceof BigDateTimeStruct)
			return ((BigDateTimeStruct)o).getValue();
		
		if (o instanceof CharSequence) 
			return TimeUtil.parseBigDateTime(o.toString());
		
		return null;
	}
	
	static public LocalDate objectToDate(Object o) {
		// TODO
		return null;
	}
	
	static public LocalTime objectToTime(Object o) {
		// TODO
		return null;
	}
	
	static public String objectToString(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return null;
		
		if (o instanceof String)
			return (String)o;
		
		if (o instanceof FieldStruct)
			return Struct.objectToString(((FieldStruct)o).getValue());
		
		if (o instanceof StringStruct)
			return ((StringStruct)o).getValue();
		
		return o.toString();		
	}
	
	static public boolean objectIsCharsStrict(Object o) {
		if (o == null)
			return false;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return false;
		
		if (o instanceof FieldStruct)
			return Struct.objectIsCharsStrict(((FieldStruct)o).getValue());
		
		if (o instanceof CharSequence)		
			return true;
		
		if (o instanceof StringStruct)
			return true;
		
		if (o instanceof Memory)		
			return true;
		
		return false;
	}
	
	static public CharSequence objectToCharsStrict(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return null;
		
		if (o instanceof FieldStruct)
			return Struct.objectToCharsStrict(((FieldStruct)o).getValue());
		
		if (o instanceof CharSequence)		
			return (CharSequence)o;
		
		if (o instanceof StringStruct)
			return ((StringStruct)o).getValue();
		
		if (o instanceof Memory)		
			return o.toString();
		
		return null;
	}
	
	static public RecordStruct objectToRecord(Object o) {
		CompositeStruct cs = Struct.objectToComposite(o);
		
		if (cs instanceof RecordStruct)
			return (RecordStruct)cs;
		
		return null;
	}
	
	static public ListStruct objectToList(Object o) {
		CompositeStruct cs = Struct.objectToComposite(o);
		
		if (cs instanceof ListStruct)
			return (ListStruct)cs;
		
		return null;
	}
	
	static public CompositeStruct objectToComposite(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return null;
		
		if (o instanceof CompositeStruct)
			return (CompositeStruct)o;
		
		if (o instanceof FieldStruct)
			return Struct.objectToComposite(((FieldStruct)o).getValue());
		
		if (o instanceof ICompositeBuilder)
			return ((ICompositeBuilder)o).toLocal();
		
		if (o instanceof Memory) {
			((Memory)o).setPosition(0);			
			return CompositeParser.parseJson((Memory)o);
		}
		
		if (o instanceof StringStruct)
			return CompositeParser.parseJson(((StringStruct)o).getValue());
		
		if (o instanceof CharSequence)
			return CompositeParser.parseJson(o.toString());
		
		// TODO add some other obvious types - List, Array, Map?, etc
		return null;		
	}
	
	static public XElement objectToXml(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return null;
		
		if (o instanceof FieldStruct)
			return Struct.objectToXml(((FieldStruct)o).getValue());
		
		if (o instanceof XElement)
			return (XElement)o;
		
		if (o instanceof StringStruct)
			return XmlReader.parse(((StringStruct)o).getValue());
		
		if (o instanceof CharSequence)
			return XmlReader.parse(o.toString());
		
		// TODO add some other obvious types - List, Array, Map?, etc
		return null;		
	}

	public static boolean objectIsBinary(Object o) {
		if (o == null)
			return false;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return false;
		
		if (o instanceof FieldStruct)
			return Struct.objectIsBinary(((FieldStruct)o).getValue());
		
		if (o instanceof Memory)
			return true;
		
		if (o instanceof BinaryStruct)
			return true;
		
		if (o instanceof byte[])
			return true;
		
		if (o instanceof ByteBuffer)
			return true;
		
		return false;
	}

	public static Memory objectToBinary(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof AnyStruct)
			o = ((AnyStruct)o).getValue();
		
		if (o == null)
			return null;
		
		if (o instanceof FieldStruct)
			return Struct.objectToBinary(((FieldStruct)o).getValue());
		
		if (o instanceof Memory)
			return (Memory)o;
		
		if (o instanceof BinaryStruct)
			return ((BinaryStruct)o).getValue();
		
		if (o instanceof byte[])
			return new Memory((byte[])o);
		
		if (o instanceof ByteBuffer)
			return new Memory(((ByteBuffer)o).array());
		
		return null;
	}
	
	static public Struct objectToStruct(Object o) {
		if (o == null)
			return null;
		
		if (o instanceof Struct)
			return (Struct)o;
		
		if (o instanceof FieldStruct)
			return Struct.objectToStruct(((FieldStruct)o).getValue());

		Struct svalue = null;
		
		if (o instanceof DateTime) {
			svalue = new DateTimeStruct();
			((DateTimeStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if (o instanceof BigDateTime) {
			svalue = new BigDateTimeStruct();
			((BigDateTimeStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if ((o instanceof BigDecimal) || (o instanceof Double) || (o instanceof Float)) {
			svalue = new DecimalStruct();
			((DecimalStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if (o instanceof BigInteger) {
			svalue = new BigIntegerStruct();
			((BigIntegerStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if (o instanceof Number) {
			svalue = new IntegerStruct();
			((IntegerStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if (o instanceof Boolean) {
			svalue = new BooleanStruct();
			((BooleanStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		if (o instanceof CharSequence) {
			svalue = new StringStruct();
			((StringStruct)svalue).adaptValue(o);
			return svalue;
		}
		
		
		// TODO
		//if (o instanceof CharSequence)
		//	return CompositeParser.parseJson(o.toString());
		//
		//if (o instanceof StringStruct)
		//	return CompositeParser.parseJson(((StringStruct)o).getValue());
		
		// TODO add some other obvious types - List, Array, Map?, etc 
		// bytebuffer, bytearray, memory...
		
		svalue = new AnyStruct();
		((AnyStruct)svalue).adaptValue(o);
		return svalue;
	}
}
