/* ************************************************************************
#
#  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.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.events.DocumentEndEvent;
import org.yaml.snakeyaml.events.DocumentStartEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.MappingEndEvent;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.events.SequenceEndEvent;
import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.reader.UnicodeReader;

import divconq.io.InputWrapper;
import divconq.lang.Memory;
import divconq.struct.builder.BuilderState;
import divconq.struct.builder.BuilderStateException;
import divconq.struct.builder.ObjectBuilder;
import divconq.util.TimeUtil;

public class CompositeParser {
	static public CompositeStruct parseJson(CharSequence data) {
		CompositeStruct ret = null;
		
		try {
			JsonFactory jsonF = new JsonFactory();
			JsonParser jp = jsonF.createJsonParser(data.toString());  // TODO make this more efficient by using the straight up char sequence
			ret = CompositeParser.parseJson(jp);
			jp.close();
		}
		catch (Exception x) {
			// TODO logging
			//System.out.println("err: " + x);
		}
		
		return ret;
	}
	
	static public CompositeStruct parseJson(Memory data) {
		return CompositeParser.parseJson(new InputWrapper(data));
	}
	
	static public CompositeStruct parseJson(InputStream data) {
		CompositeStruct ret = null;
		
		try {
			JsonFactory jsonF = new JsonFactory();
			JsonParser jp = jsonF.createJsonParser(data);
			ret = CompositeParser.parseJson(jp);
			jp.close();
		}
		catch (Exception x) {
			// TODO logging
			//System.out.println("err: " + x);
		}
		
		return ret;
	}
	
	static public CompositeStruct parseJson(JsonParser jp) throws IOException, BuilderStateException {
		ObjectBuilder eb = new ObjectBuilder();
		
		JsonToken token = jp.nextToken();
		
		while (token != null) {
			if (token == JsonToken.START_ARRAY) 
				eb.startList();
			else if (token == JsonToken.END_ARRAY) 
				eb.endList();
			else if (token == JsonToken.START_OBJECT) 
				eb.startRecord();
			else if (token == JsonToken.END_OBJECT) 
				eb.endRecord();
			else if (token == JsonToken.FIELD_NAME) 
				eb.field(jp.getText());		// TODO check for bad chars (controls chars)
			else if (token == JsonToken.VALUE_NULL) 
				eb.scalar(null);
			else if (token == JsonToken.VALUE_FALSE) 
				eb.scalar(false);
			else if (token == JsonToken.VALUE_TRUE) 
				eb.scalar(true);
			else if (token == JsonToken.VALUE_STRING) 
				eb.scalar(jp.getText());		// TODO check for bad chars (controls chars)
			else if (token == JsonToken.VALUE_NUMBER_FLOAT) 
				eb.scalar(jp.getDecimalValue());
			else if (token == JsonToken.VALUE_NUMBER_INT) 
				eb.scalar(jp.getNumberValue());   //BigIntegerValue());
			
			token = jp.nextToken();
		}
		
		return eb.getRoot();
	}
	
	static public CompositeStruct parseYaml(CharSequence data) {
		CompositeStruct ret = null;
		
		try {
			ret = CompositeParser.parseYaml(new StringReader(data.toString()));
		}
		catch (Exception x) {
			// TODO logging
			//System.out.println("err: " + x);
		}
		
		return ret;
	}
	
	static public CompositeStruct parseYaml(Memory data) {
		return CompositeParser.parseYaml(new InputWrapper(data));
	}
	
	static public CompositeStruct parseYaml(InputStream data) {
		CompositeStruct ret = null;
		
		try {
			ret = CompositeParser.parseYaml(new UnicodeReader(data));
		}
		catch (Exception x) {
			// TODO logging
			//System.out.println("err: " + x);
		}
		
		return ret;
	}
	
	static public CompositeStruct parseYaml(Reader rdr) throws IOException, BuilderStateException {
		ObjectBuilder eb = new ObjectBuilder();
		
		Yaml y = new Yaml();
		boolean docaslist = false;
		
		for (Event event : y.parse(rdr)) {
			//System.out.println("Event: " + event);
			
			if (event instanceof DocumentStartEvent) {
				if (((DocumentStartEvent)event).getExplicit()) {
					if (eb.getState() == BuilderState.Ready) {
						docaslist = true;
						eb.startList();
					}
				}
			}
			else if (event instanceof DocumentEndEvent) {
			}
			else if (event instanceof SequenceStartEvent) 
				eb.startList();
			else if (event instanceof SequenceEndEvent) 
				eb.endList();
			else if (event instanceof MappingStartEvent) 
				eb.startRecord();
			else if (event instanceof MappingEndEvent) 
				eb.endRecord();
			else if (event instanceof ScalarEvent) {
				ScalarEvent sev  = (ScalarEvent)event;
				
				if (eb.getState() == BuilderState.InRecord)
					eb.field(sev.getValue());
				else {
					//System.out.println(sev.getValue() + " - " + sev.getTag());
					String tag = sev.getTag();
					String val = sev.getValue();
					
					if ("tag:yaml.org,2002:float".equals(tag))
						eb.scalar(new BigDecimal(val));
					else if ("tag:yaml.org,2002:int".equals(tag))
						eb.scalar(new Long(val));
					else if ("tag:yaml.org,2002:bool".equals(tag))
						eb.scalar(new Boolean(val));
					else if ("tag:yaml.org,2002:null".equals(tag))
						eb.scalar(null);
					else if ("tag:yaml.org,2002:str".equals(tag))
						eb.scalar(val);
					else if ("tag:yaml.org,2002:timestamp".equals(tag))
						eb.scalar(TimeUtil.parseDateTime(val));
					else {			// TODO add support for dcSchema types
						eb.scalar(val);
					}
				}
			}
		}
		
		if (docaslist)
			eb.endList();
		
		return eb.getRoot();
	}
	
	/*
	 * XML
	 * 
	 * <Record Type="nnn">
	 * 		<Field Name="Age" Type="nnn">
	 * 			<Scalar>9</Scalar>
	 * 		</Field>
	 * 		<Field Name="NickName" Value="Freckles" Type="nnn" />
	 * 		<Field Name="Friends">
	 * 			<List Type="nnn">
	 * 				<Scalar>Stacy</Scalar>
	 * 				<Scalar>Lenny</Scalar>
	 * 			</List>
	 * 		</Field>
	 * </Record>
	 * 
	 */
}
