/* ************************************************************************
#
#  DivConq
#
#  http://divconq.com/
#
#  Copyright:
#    Copyright 2014 eTimeline, LLC. All rights reserved.
#
#  License:
#    See the license.txt file in the project's top-level directory for details.
#
#  Authors:
#    * <NAME>
#
************************************************************************ */
package divconq.schema;

import java.util.ArrayList;
import java.util.List;

import divconq.lang.op.FuncResult;
import divconq.lang.op.OperationContext;
import divconq.struct.ListStruct;
import divconq.struct.RecordStruct;
import divconq.struct.Struct;
import divconq.util.StringUtil;
import divconq.xml.XElement;

public class Field {
	enum ReqTypes {
		True(1),
		False(2),
		IfPresent(3);
	    
	    private int code;

	    private ReqTypes(int c) {
	      code = c;
	    }

	    public int getCode() {
	      return code;
	    }
	}

	protected Schema schema = null;
	protected List<DataType> options = new ArrayList<DataType>();
	protected String name = null;
	protected ReqTypes required = ReqTypes.False;
	
	public Field(Schema schema) {
		this.schema = schema;
	}

	public RecordStruct toJsonDef(int lvl) {
		RecordStruct def = new RecordStruct();
		
		def.setField("Name", this.name);
		
		ListStruct rests = new ListStruct();
		
		for (DataType dt : this.options) 
			rests.addItem(dt.toJsonDef(lvl));
		
		def.setField("Options", rests);
		
		def.setField("Required", this.required.getCode());
		
		return def;
	}
	
	public void compile(XElement fel) {
		this.name = fel.getAttribute("Name");
		
		String req = fel.getAttribute("Required");
		
		if ("True".equals(req))
			this.required = ReqTypes.True;
		else if ("IfPresent".equals(req))
			this.required = ReqTypes.IfPresent;
		
		String t1 = fel.getAttribute("Type");
		
		if (StringUtil.isNotEmpty(t1)) {
			this.options = this.schema.manager.lookupOptionsType(t1);
			return;
		}
		
		String f1 = fel.getAttribute("ForeignKey");
		
		if (StringUtil.isNotEmpty(f1)) {
			this.options = this.schema.manager.lookupOptionsType("Id");
			return;
		}
		
		for (XElement dtel : fel.selectAll("*")) { 
			DataType dt = new DataType(this.schema);
			dt.load(dtel);
			dt.compile();
			this.options.add(dt);
		}
	}
		
	// don't call this with data == null from a field if field required - required means "not null" so put the error in
	public void validate(boolean present, Struct data) {
		if (data == null) {
			this.valueUnresolved(present, data);
			return;
		}   
		
		if (this.options.size() == 0) {
			OperationContext.get().errorTr(423, data);			
			return;
		}
		
		if (this.options.size() == 1) { 
			if (!this.options.get(0).validate(data))
				this.valueUnresolved(present, data);
			
			return;
		}
		
		for (DataType dt : this.options) {
			if (dt.match(data)) {
				if (!dt.validate(data))
					this.valueUnresolved(present, data);
				
				return;
			}
		}
		
		OperationContext.get().errorTr(440, data);			
		return;
	}
		
	// don't call this with data == null from a field if field required - required means "not null" so put the error in
	public Struct normalizeValidate(boolean present, Struct data) {
		if (data == null) {
			this.valueUnresolved(present, data);
			return null;
		}   
		
		if (this.options.size() == 0) {
			OperationContext.get().errorTr(423, data);			
			return null;
		}
		
		if (this.options.size() == 1) { 
			Struct nv = this.options.get(0).normalizeValidate(data);
			
			if (nv == null) {
				this.valueUnresolved(present, data);
				return null;
			}
			
			return nv;
		}
		
		for (DataType dt : this.options) {
			if (dt.match(data)) {
				Struct nv = dt.normalizeValidate(data);
				
				if (nv == null) {
					this.valueUnresolved(present, data);
					return null;
				}
				
				return nv;
			}
		}
		
		OperationContext.get().errorTr(440, data);			
		return null;
	}
	
	protected void valueUnresolved(boolean present, Object data) {
		if (data != null) {
			OperationContext.get().errorTr(440, data);			
			return;
		}
		
		if (this.required == ReqTypes.False)
			return;
		
		if (this.required == ReqTypes.IfPresent && !present)
			return;
		
		OperationContext.get().errorTr(424, data, this.name);			
	}
	
	public Struct wrap(Object data) {
		if (data == null) 
			return null;
		
		if (this.options.size() == 0) 
			return null;
		
		if (this.options.size() == 1) 
			return this.options.get(0).wrap(data);
		
		for (DataType dt : this.options) {
			if (dt.match(data)) 
				return dt.wrap(data);
		}
		
		return null;
	}
	
	public FuncResult<Struct> create() {
		if (this.options.size() == 0) 
			return null;
		
		return this.options.get(0).create();
	}
	
	public DataType getPrimaryType() {
		if (this.options.size() == 0) 
			return null;
		
		return this.options.get(0);
	}
}
