package railo.runtime.tag;

import java.io.IOException;

import javax.xml.parsers.FactoryConfigurationError;

import railo.runtime.converter.ConverterException;
import railo.runtime.converter.JSConverter;
import railo.runtime.converter.WDDXConverter;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.TagImpl;
import railo.runtime.op.Caster;

/**
* Serializes and de-serializes CFML data structures to the XML-based WDDX format. 
*   Generates JavaScript statements to instantiate JavaScript objects equivalent to the contents of a 
*   WDDX packet or some CFML data structures.
*
*
*
**/
public final class Wddx extends TagImpl {

	/** The value to be processed. */
	private Object input;

	/** Specifies the action taken by the cfwddx tag. */
	private String action;

	/** The name of the variable to hold the output of the operation. This attribute is required for 
	** 		action = 'WDDX2CFML'. For all other actions, if this attribute is not provided, the result of the 
	** 		WDDX processing is outputted in the HTML stream. */
	private String output;

	private boolean validate;

	/** The name of the top-level JavaScript object created by the deserialization process. The object 
	** 		created is an instance of the WddxRecordset object, explained in WddxRecordset Object. */
	private String toplevelvariable;

	/** Indicates whether to output time-zone information when serializing CFML to WDDX. If time-zone 
	** 		information is taken into account, the hour-minute offset, as represented in the ISO8601 format, is 
	** 		calculated in the date-time output. If time-zone information is not taken into account, the local 
	** 		time is output. The default is Yes. */
	private boolean usetimezoneinfo;

	private boolean xmlConform;

	@Override
	public void release()	{
		super.release();
		input=null;
		action=null;
		output=null;
		validate=false;
		toplevelvariable=null;
		usetimezoneinfo=false;
		xmlConform=false;
	}
	

	/** set the value input
	*  The value to be processed.
	* @param input value to set
	**/
	public void setInput(Object input)	{
		this.input=input;
	}

	/** set the value action
	*  Specifies the action taken by the cfwddx tag.
	* @param action value to set
	**/
	public void setAction(String action)	{
		this.action=action.toLowerCase();
	}

	/** set the value output
	*  The name of the variable to hold the output of the operation. This attribute is required for 
	* 		action = 'WDDX2CFML'. For all other actions, if this attribute is not provided, the result of the 
	* 		WDDX processing is outputted in the HTML stream.
	* @param output value to set
	**/
	public void setOutput(String output)	{
		this.output=output;
	}

	/** set the value validate
	*  
	* @param validate value to set
	**/
	public void setValidate(boolean validate)	{
		this.validate=validate;
	}

	/** set the value toplevelvariable
	*  The name of the top-level JavaScript object created by the deserialization process. The object 
	* 		created is an instance of the WddxRecordset object, explained in WddxRecordset Object.
	* @param toplevelvariable value to set
	**/
	public void setToplevelvariable(String toplevelvariable)	{
		this.toplevelvariable=toplevelvariable;
	}

	/** set the value usetimezoneinfo
	*  Indicates whether to output time-zone information when serializing CFML to WDDX. If time-zone 
	* 		information is taken into account, the hour-minute offset, as represented in the ISO8601 format, is 
	* 		calculated in the date-time output. If time-zone information is not taken into account, the local 
	* 		time is output. The default is Yes.
	* @param usetimezoneinfo value to set
	**/
	public void setUsetimezoneinfo(boolean usetimezoneinfo)	{
		this.usetimezoneinfo=usetimezoneinfo;
	}
	
	/**
	 * sets if generated code is xml or wddx conform
	 * @param xmlConform
	 */
	public void setXmlconform(boolean xmlConform)	{
		this.xmlConform=xmlConform;
	}


	@Override
	public int doStartTag() throws PageException	{
		try {
			doIt();
			
		} catch (Exception e) {
			throw Caster.toPageException(e);
		}
		return SKIP_BODY;
	}
	private void doIt() throws ExpressionException, PageException, ConverterException, IOException, FactoryConfigurationError	{
	// cfml > wddx
		if(action.equals("cfml2wddx")) {
			if(output!=null) pageContext.setVariable(output,cfml2wddx(input));
			else pageContext.forceWrite(cfml2wddx(input));
		}
		
	// wddx > cfml
		else if(action.equals("wddx2cfml")) {
			if(output==null) throw new ApplicationException("at tag cfwddx the attribute output is required if you set action==wddx2cfml");
			pageContext.setVariable(output,wddx2cfml(Caster.toString(input)));
		}
		
	// cfml > js
		else if(action.equals("cfml2js")) {
			if(output!=null) pageContext.setVariable(output,cfml2js(input));
			else pageContext.forceWrite(cfml2js(input));
		}
		
	// wddx > js
		else if(action.equals("wddx2js")) {
			if(output!=null) pageContext.setVariable(output,wddx2js(Caster.toString(input)));
			else pageContext.forceWrite(wddx2js(Caster.toString(input)));
		}
		
		
		else throw new ExpressionException("invalid attribute action for tag cfwddx, attributes are [cfml2wddx, wddx2cfml,cfml2js, wddx2js].");

	}
	
	private String cfml2wddx(Object input) throws ConverterException {
		WDDXConverter converter =new WDDXConverter(pageContext.getTimeZone(),xmlConform,true);
		if(!usetimezoneinfo)converter.setTimeZone(null);
		return converter.serialize(input);
	}
	private Object wddx2cfml(String input) throws ConverterException, IOException, FactoryConfigurationError {
		WDDXConverter converter =new WDDXConverter(pageContext.getTimeZone(),xmlConform,true);
		converter.setTimeZone(pageContext.getTimeZone());
		return converter.deserialize(input,validate);
	}
	private String cfml2js(Object input) throws ConverterException {
		if(toplevelvariable==null)missingTopLevelVariable();
		JSConverter converter =new JSConverter();
		return converter.serialize(input,toplevelvariable);
	}
	private String wddx2js(String input) throws ConverterException, IOException, FactoryConfigurationError {
		if(toplevelvariable==null)missingTopLevelVariable();
		JSConverter converter =new JSConverter();
		return converter.serialize(wddx2cfml(input),toplevelvariable);
	}
	
	
	private ApplicationException missingTopLevelVariable() {
		return new ApplicationException("at tag cfwddx the attribute topLevelVariable is required if you set action equal wddx2js or cfml2js");
	}


	@Override
	public int doEndTag()	{
		return EVAL_PAGE;
	}
}