/* ************************************************************************
#
#  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.util.ArrayList;
import java.util.Arrays;
import java.util.List;

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

import divconq.lang.OperationResult;
import divconq.schema.DataType;
import divconq.struct.builder.BuilderStateException;
import divconq.struct.builder.ICompositeBuilder;
import divconq.struct.builder.ICompositeOutput;
import divconq.xml.XElement;

/**
 * DivConq uses a specialized type system that provides type consistency across services 
 * (including web services), database fields and stored procedures, as well as scripting.
 * 
 * All scalars (including primitives) and composites (collections) are wrapped by some
 * subclass of Struct.  List/array collections are expressed by this class.  
 * This class is analogous to an Array in JSON but may contain type information as well, 
 * similar to Yaml.
 * 
 *  TODO link to blog entries.
 * 
 * @author Andy
 *
 */
public class ListStruct extends CompositeStruct {
	protected List<Struct> items = new ArrayList<Struct>();
	
	/**
	 * Provide data type info (schema for fields) and a list of initial items
	 * 
	 * @param type field schema
	 * @param items initial values
	 */
	public ListStruct(DataType type, Object... items) {
		super(type);
		this.addItem(items);
	}
	
	/**
	 * Optionally provide a list of initial items
	 * 
	 * @param items initial values
	 */
	public ListStruct(Object... items) {
		this.addItem(items);
	}
	
	/* (non-Javadoc)
	 * @see divconq.struct.CompositeStruct#select(divconq.struct.PathPart[])
	 */
	@Override
	public Struct select(PathPart... path) {
		if (path.length == 0)
			return this;
		
		PathPart part = path[0];
		
		if (part.isField()) {
			OperationResult log = part.getLog();
			
			if (log != null) 
				log.warnTr(501, this);
			
			return null;
		}
		
		int idx = part.getIndex();
		
		if (idx >= this.items.size()) {
			OperationResult log = part.getLog();
			
			if (log != null) 
				log.warnTr(502, part.getIndex());
			
			return null;
		}
		
		Struct o = this.items.get(idx);
		
		if (o instanceof CompositeStruct) 
			return ((CompositeStruct)o).select(Arrays.copyOfRange(path, 1, path.length));		
		
		if (path.length == 1) 
			return o;			
		
		OperationResult log = part.getLog();
		
		if (log != null) 
			log.warnTr(503, o);
		
		return null;
	}
	
	/* (non-Javadoc)
	 * @see divconq.struct.Struct#isBlank()
	 */
	@Override
	public boolean isEmpty() {
		return (this.items.size() == 0);
	}
	
	/* (non-Javadoc)
	 * @see divconq.struct.builder.ICompositeOutput#toBuilder(divconq.struct.builder.ICompositeBuilder)
	 */
	@Override
	public void toBuilder(ICompositeBuilder builder) throws BuilderStateException {
		builder.startList();
		
		for (Object o : this.items) {
			if (o instanceof ICompositeOutput)
				((ICompositeOutput)o).toBuilder(builder);
			else
				builder.scalar(o);
		}
		
		builder.endList();
	}

	/**
	 * Attempt to add items to the list, but if there is a schema the items
	 * must match the schema.
	 * 
	 * @param items to add
	 * @return log of the result of the call (check hasErrors)
	 */
	public OperationResult addItem(Object... items) {
		OperationResult or = new OperationResult(false);
		
		for (Object o : items) {
			Object value = o;
			Struct svalue = null;
			
			if (value instanceof ICompositeBuilder)
				value = ((ICompositeBuilder)value).toLocal();
			
			if (this.type != null) {
				Struct sv = this.type.wrapItem(value, or);
				
				if (sv != null)
					svalue = sv;
			}
			
			if (svalue == null) 
				svalue = Struct.objectToStruct(value); 
			
			this.items.add(svalue);
		}
		
		return or;
	}
	
	/**
	 * 
	 * @return collection of all the items the list holds
	 */
	public Iterable<Struct> getItems() {
		return this.items;
	}
	
	/**
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return the struct for that field
	 */
	public Struct getItem(int idx) {
		if (idx >= this.items.size())
			return null;
		
		return this.items.get(idx);
	}
	
	/**
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return true if an item is at that position
	 */
	public boolean hasItem(int idx) {
		if (idx >= this.items.size())
			return false;
		
		return true;
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Integer (DivConq thinks of integers as 64bit)
	 */
	public Long getItemAsInteger(int idx) {
		return Struct.objectToInteger(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as BigDecimal
	 */
	public BigDecimal getItemAsDecimal(int idx) {
		return Struct.objectToDecimal(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Boolean
	 */
	public Boolean getItemAsBoolean(int idx) {
		return Struct.objectToBoolean(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Datetime
	 */
	public DateTime getItemAsDateTime(int idx) {
		return Struct.objectToDateTime(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Date
	 */
	public LocalDate getItemAsDate(int idx) {
		return Struct.objectToDate(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Time
	 */
	public LocalTime getItemAsTime(int idx) {
		return Struct.objectToTime(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as String
	 */
	public String getItemAsString(int idx) {
		return Struct.objectToString(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as CompositeStruct
	 */
	public CompositeStruct getItemAsComposite(int idx) {
		return Struct.objectToComposite(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Struct
	 */
	public Struct getItemAsStruct(int idx) {
		return Struct.objectToStruct(this.getItem(idx));
	}
	
	/**
	 * Unlike getItem, this returns the value (inner) rather than struct wrapping 
	 * the value.
	 * 
	 * @param idx position in list of the item desired (0 based)
	 * @return field's "inner" value as Xml (will parse if value is string)
	 */
	public XElement getItemAsXml(int idx) {
		return Struct.objectToXml(this.getItem(idx));
	}
	
	/**
	 * @param idx position in list of the item desired (0 based)
	 * @return true if item does not exist or if item is string and its value is empty 
	 */
	public boolean isItemEmpty(int idx) {
		if (idx >= this.items.size())
			return true;
		
		Object o = this.items.get(idx);
		
		if (o == null)
			return true;
		
		if (o instanceof CharSequence)
			return o.toString().isEmpty();
		
		return false;
	}
	
	/**
	 * @return number of items in this list
	 */
	public int getSize() {
		return this.items.size();
	}

	/**
	 * @param idx position in list of the item to remove from list
	 */
	public void removeItem(long idx) {		
		if (idx >= this.items.size())
			return;
		
		//Struct old = this.items.get((int) idx);
		
		//if (old != null)
		//	old.dispose();
		
		this.items.remove(idx);
	}

	/* (non-Javadoc)
	 * @see divconq.struct.Struct#clone()
	 */
	@Override
	public Object clone() {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * 
	 * @return schema for the primary/default data type of the list items
	 */
	public DataType getChildType() {
		if (this.type != null) 
			return this.type.getPrimaryItemType();
		
		return null;
	}

	/* (non-Javadoc)
	 * @see divconq.struct.CompositeStruct#clear()
	 */
	@Override
	public void clear() {
		//for (Struct var : this.items) 
		//	var.dispose();
		
		this.items.clear();
	}
	
	/* (non-Javadoc)
	 * @see divconq.struct.Struct#dispose()
	 */
	@Override
	public void dispose() {
		//this.clear();
		super.dispose();		
	}
}
