/*
 *  soapUI, copyright (C) 2004-2012 smartbear.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *  See the GNU Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wadl.inference.support;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.xmlbeans.XmlAnySimpleType;
import org.apache.xmlbeans.XmlBase64Binary;
import org.apache.xmlbeans.XmlBoolean;
import org.apache.xmlbeans.XmlDate;
import org.apache.xmlbeans.XmlDateTime;
import org.apache.xmlbeans.XmlDecimal;
import org.apache.xmlbeans.XmlGDay;
import org.apache.xmlbeans.XmlGMonth;
import org.apache.xmlbeans.XmlGYear;
import org.apache.xmlbeans.XmlGYearMonth;
import org.apache.xmlbeans.XmlHexBinary;
import org.apache.xmlbeans.XmlInteger;
import org.apache.xmlbeans.XmlNegativeInteger;
import org.apache.xmlbeans.XmlNonNegativeInteger;
import org.apache.xmlbeans.XmlNonPositiveInteger;
import org.apache.xmlbeans.XmlPositiveInteger;
import org.apache.xmlbeans.XmlString;
import org.apache.xmlbeans.XmlTime;

/**
 * XML Schema inferred from gathered XML data.
 * 
 * @author Dain Nilsson
 */
public class TypeInferrer
{
	private static TypeInferrer ref;
	private TypeTree types;
	private Map<XmlAnySimpleType, TypeTree> typeTable;

	/**
	 * Get the instance of the XmlAnySimpleType with the type xs:<typeName>.
	 * 
	 * @param typeName
	 * @return Returns the XmlAnySimpleType, if available. Otherwise returns
	 *         null.
	 */
	public static XmlAnySimpleType getType( String typeName )
	{
		for( XmlAnySimpleType item : getRef().typeTable.keySet() )
			if( item.schemaType().getName().getLocalPart().equals( typeName ) )
				return item;
		return null;
	}

	/**
	 * Get the XmlAnySimpleType that describes a simple value that is empty.
	 * 
	 * @return Returns the xs:string XmlAnySimpleType.
	 */
	public static XmlAnySimpleType getBlankType()
	{
		return getRef().types.type;
	}

	/**
	 * Given a value and a type, a new type will be returned that validates
	 * values for both the given type, and the new value.
	 * 
	 * @param value
	 *           The new value to expand the type for.
	 * @param baseType
	 *           The type to be expanded.
	 * @return The new expanded type.
	 */
	public static XmlAnySimpleType expandTypeForValue( String value, XmlAnySimpleType baseType )
	{
		return getRef().expandTypeForValueInternal( baseType, value );
	}

	/**
	 * Given a simple value, infers the type of the value.
	 * 
	 * @param value
	 *           The value to assign a type to.
	 * @return Returns the inferred type for the given value.
	 */
	public static XmlAnySimpleType inferSimpleType( String value )
	{
		return getRef().inferSimpleTypeRec( value, getRef().types );
	}

	/**
	 * Validates a string against an XmlAnySimpleType.
	 * 
	 * @param value
	 *           The value to validate.
	 * @param type
	 *           The XmlAnySimpleType to validate against.
	 * @return True if the value validates, false if not.
	 */
	public static boolean validateSimpleType( String value, XmlAnySimpleType type )
	{
		try
		{
			type.setStringValue( value );
			return type.validate();
		}
		catch( Exception e )
		{
			return false;
		}
	}

	private static TypeInferrer getRef()
	{
		if( ref == null )
			ref = new TypeInferrer();
		return ref;
	}

	private TypeInferrer()
	{
		typeTable = new HashMap<XmlAnySimpleType, TypeTree>();
		TypeTree xmlbool = new TypeTree( XmlBoolean.Factory.newInstance() );
		typeTable.put( xmlbool.type, xmlbool );
		TypeTree xmlbool2 = new TypeTree( XmlBoolean.Factory.newInstance() );
		typeTable.put( xmlbool2.type, xmlbool2 );
		TypeTree xmlnegint = new TypeTree( XmlNegativeInteger.Factory.newInstance() );
		typeTable.put( xmlnegint.type, xmlnegint );
		TypeTree xmlposint = new TypeTree( XmlPositiveInteger.Factory.newInstance() );
		typeTable.put( xmlposint.type, xmlposint );
		TypeTree xmlnonnegint = new TypeTree( XmlNonNegativeInteger.Factory.newInstance() );
		typeTable.put( xmlnonnegint.type, xmlnonnegint );
		xmlnonnegint.addChild( xmlposint );
		xmlnonnegint.addChild( xmlbool );
		TypeTree xmlnonposint = new TypeTree( XmlNonPositiveInteger.Factory.newInstance() );
		typeTable.put( xmlnonposint.type, xmlnonposint );
		xmlnonposint.addChild( xmlnegint );
		TypeTree xmlint = new TypeTree( XmlInteger.Factory.newInstance() );
		typeTable.put( xmlint.type, xmlint );
		xmlint.addChild( xmlnonnegint );
		xmlint.addChild( xmlnonposint );
		TypeTree xmldec = new TypeTree( XmlDecimal.Factory.newInstance() );
		typeTable.put( xmldec.type, xmldec );
		xmldec.addChild( xmlint );
		TypeTree xmldate = new TypeTree( XmlDate.Factory.newInstance() );
		typeTable.put( xmldate.type, xmldate );
		TypeTree xmltime = new TypeTree( XmlTime.Factory.newInstance() );
		typeTable.put( xmltime.type, xmltime );
		TypeTree xmldatetime = new TypeTree( XmlDateTime.Factory.newInstance() );
		typeTable.put( xmldatetime.type, xmldatetime );
		TypeTree xmlhexbin = new TypeTree( XmlHexBinary.Factory.newInstance() );
		typeTable.put( xmlhexbin.type, xmlhexbin );
		TypeTree xmlb64bin = new TypeTree( XmlBase64Binary.Factory.newInstance() );
		typeTable.put( xmlb64bin.type, xmlb64bin );
		TypeTree xmlgyearmonth = new TypeTree( XmlGYearMonth.Factory.newInstance() );
		typeTable.put( xmlgyearmonth.type, xmlgyearmonth );
		TypeTree xmlgyear = new TypeTree( XmlGYear.Factory.newInstance() );
		typeTable.put( xmlgyear.type, xmlgyear );
		TypeTree xmlgmonth = new TypeTree( XmlGMonth.Factory.newInstance() );
		typeTable.put( xmlgmonth.type, xmlgmonth );
		TypeTree xmlgday = new TypeTree( XmlGDay.Factory.newInstance() );
		typeTable.put( xmlgday.type, xmlgday );
		TypeTree xmlstring = new TypeTree( XmlString.Factory.newInstance() );
		typeTable.put( xmlstring.type, xmlstring );
		xmlstring.addChild( xmldec );
		xmlstring.addChild( xmldate );
		xmlstring.addChild( xmltime );
		xmlstring.addChild( xmldatetime );
		xmlstring.addChild( xmlbool2 );
		xmlstring.addChild( xmlgyearmonth );
		xmlstring.addChild( xmlgyear );
		xmlstring.addChild( xmlgmonth );
		xmlstring.addChild( xmlgday );
		xmlstring.addChild( xmlhexbin );
		// xmlstring.addChild(xmlb64bin);
		types = xmlstring;
	}

	private XmlAnySimpleType expandTypeForValueInternal( XmlAnySimpleType type, String value )
	{
		TypeTree p = typeTable.get( type );
		while( !validateSimpleType( value, p.type ) )
		{
			p = p.parent;
		}
		return p.type;
	}

	private XmlAnySimpleType inferSimpleTypeRec( String value, TypeTree p )
	{
		for( TypeTree item : p.children )
			if( validateSimpleType( value, item.type ) )
				return inferSimpleTypeRec( value, item );
		return p.type;
	}

	private class TypeTree
	{
		public XmlAnySimpleType type;
		public TypeTree parent;
		public List<TypeTree> children;

		public TypeTree( XmlAnySimpleType type )
		{
			this.type = type;
			children = new ArrayList<TypeTree>();
		}

		public void addChild( TypeTree type )
		{
			children.add( type );
			type.parent = this;
		}
	}

}
