/*
 *  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.rest.support;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import net.java.dev.wadl.x2009.x02.ApplicationDocument;
import net.java.dev.wadl.x2009.x02.ApplicationDocument.Application;
import net.java.dev.wadl.x2009.x02.DocDocument.Doc;
import net.java.dev.wadl.x2009.x02.MethodDocument.Method;
import net.java.dev.wadl.x2009.x02.ParamDocument.Param;
import net.java.dev.wadl.x2009.x02.ParamStyle;
import net.java.dev.wadl.x2009.x02.RepresentationDocument;
import net.java.dev.wadl.x2009.x02.RepresentationDocument.Representation;
import net.java.dev.wadl.x2009.x02.ResourceDocument.Resource;
import net.java.dev.wadl.x2009.x02.ResourceTypeDocument;
import net.java.dev.wadl.x2009.x02.ResourcesDocument.Resources;
import net.java.dev.wadl.x2009.x02.ResponseDocument.Response;

import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.eviware.soapui.impl.rest.RestMethod;
import com.eviware.soapui.impl.rest.RestRepresentation;
import com.eviware.soapui.impl.rest.RestRequestInterface;
import com.eviware.soapui.impl.rest.RestResource;
import com.eviware.soapui.impl.rest.RestService;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder.ParameterStyle;
import com.eviware.soapui.impl.support.definition.support.InvalidDefinitionException;
import com.eviware.soapui.impl.wsdl.support.Constants;
import com.eviware.soapui.impl.wsdl.support.UrlSchemaLoader;
import com.eviware.soapui.impl.wsdl.support.xsd.SchemaUtils;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.Tools;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.xml.XmlUtils;

public class WadlImporter
{
	private RestService service;
	private Application application;
	private List<Resources> resourcesList;
	private Map<String, ApplicationDocument> refCache = new HashMap<String, ApplicationDocument>();
	private boolean isWADL11 = true;

	public WadlImporter( RestService service )
	{
		this.service = service;
	}

	public void initFromWadl( String wadlUrl )
	{
		try
		{
			// XmlObject xmlObject = XmlObject.Factory.parse( new URL( wadlUrl ) );
			XmlObject xmlObject = XmlUtils.createXmlObject( new URL( wadlUrl ) );

			Element element = ( ( Document )xmlObject.getDomNode() ).getDocumentElement();

			// try to allow older namespaces
			if( element.getLocalName().equals( "application" )
					&& element.getNamespaceURI().startsWith( "http://research.sun.com/wadl" ) )
			{
				isWADL11 = false;
				String content = xmlObject.xmlText();
				content = content.replaceAll( "\"" + element.getNamespaceURI() + "\"", "\"" + Constants.WADL11_NS + "\"" );
				xmlObject = ApplicationDocument.Factory.parse( content );
			}
			else if( !element.getLocalName().equals( "application" )
					|| !element.getNamespaceURI().equals( Constants.WADL11_NS ) )
			{
				throw new Exception( "Document is not a WADL application with " + Constants.WADL11_NS + " namespace" );
			}

			ApplicationDocument applicationDocument = ( ApplicationDocument )xmlObject
					.changeType( ApplicationDocument.type );
			application = applicationDocument.getApplication();

			resourcesList = application.getResourcesList();

			service.setName( getFirstTitle( application.getDocList(), service.getName() ) );

			String base = resourcesList.size() == 1 ? resourcesList.get( 0 ).getBase() : "";

			try
			{
				URL baseUrl = new URL( base );
				service.setBasePath( baseUrl.getPath() );

				service.addEndpoint( Tools.getEndpointFromUrl( baseUrl ) );
			}
			catch( Exception e )
			{
				service.setBasePath( base );
			}

			service.setWadlUrl( wadlUrl );
			service.getConfig().setWadlVersion( isWADL11 ? Constants.WADL11_NS : Constants.WADL10_NS );

			for( Resources resources : resourcesList )
			{
				RestResource baseResource = null;
				if( resourcesList.size() > 1 )
				{
					String path = resources.getBase();
					baseResource = service.addNewResource( path, path );
				}
				for( Resource resource : resources.getResourceList() )
				{
					String name = getFirstTitle( resource.getDocList(), resource.getPath() );
					String path = resource.getPath();

					RestResource newResource = null;

					if( baseResource != null && path != null )
					{
						for( RestResource res : baseResource.getChildResourceList() )
						{
							if( path.equals( res.getPath() ) )
							{
								newResource = res;
								break;
							}
						}

						if( newResource == null )
						{
							newResource = baseResource.addNewChildResource( name, path );
						}
					}
					else if( path != null )
					{
						for( RestResource res : service.getResourceList() )
						{
							if( path.equals( res.getPath() ) )
							{
								newResource = res;
								break;
							}
						}

						if( newResource == null )
						{
							newResource = service.addNewResource( name, path );
						}
					}
					else
					{
						newResource = service.addNewResource( name, "" );
					}

					initResourceFromWadlResource( newResource, resource );
					addSubResources( newResource, resource );
				}
			}
		}
		catch( InvalidDefinitionException ex )
		{
			ex.show();
		}
		catch( Exception e )
		{
			UISupport.showErrorMessage( e );
		}
	}

	private void addSubResources( RestResource newResource, Resource resource )
	{
		for( Resource res : resource.getResourceList() )
		{
			String path = res.getPath();
			if( path == null )
				path = "";

			String name = getFirstTitle( res.getDocList(), path );

			RestResource newRes = null;

			for( RestResource child : newResource.getChildResourceList() )
			{
				if( path.equals( child.getPath() ) )
				{
					newRes = child;
					break;
				}
			}

			if( newRes == null )
				newRes = newResource.addNewChildResource( name, path );

			initResourceFromWadlResource( newRes, res );

			addSubResources( newRes, res );
		}
	}

	private String getFirstTitle( List<Doc> list, String defaultTitle )
	{
		for( Doc doc : list )
		{
			if( StringUtils.hasContent( doc.getTitle() ) )
			{
				return doc.getTitle();
			}
		}
		return defaultTitle;
	}

	private void initResourceFromWadlResource( RestResource newResource, Resource resource )
	{
		for( Param param : resource.getParamList() )
		{
			param = resolveParameter( param );
			if( param != null )
			{
				String nm = param.getName();
				RestParamProperty prop = newResource.hasProperty( nm ) ? newResource.getProperty( nm ) : newResource
						.addProperty( nm );

				initParam( param, prop );
			}
		}

		for( Method method : resource.getMethodList() )
		{
			method = resolveMethod( method );
			initMethod( newResource, method );
		}

		List<?> types = resource.getType();
		if( types != null && types.size() > 0 )
		{
			for( Object obj : types )
			{
				ResourceTypeDocument.ResourceType type = resolveResource( obj.toString() );
				if( type != null )
				{
					for( Method method : type.getMethodList() )
					{
						method = resolveMethod( method );
						RestMethod restMethod = initMethod( newResource, method );

						for( Param param : type.getParamList() )
						{
							param = resolveParameter( param );
							if( param != null )
							{
								String nm = param.getName();
								RestParamProperty prop = restMethod.hasProperty( nm ) ? restMethod.getProperty( nm )
										: restMethod.addProperty( nm );

								initParam( param, prop );
							}
						}
					}
				}
			}
		}
	}

	private RestMethod initMethod( RestResource newResource, Method method )
	{
		// build name
		String name = getFirstTitle( method.getDocList(), method.getName() );
		String id = method.getId();
		if( StringUtils.hasContent( id ) && !id.trim().equals( name.trim() ) )
		{
			name += " - " + method.getId();
		}

		// ensure unique name
		if( newResource.getRestMethodByName( name ) != null )
		{
			int cnt = 0;
			String orgName = name;

			while( newResource.getRestMethodByName( name ) != null )
			{
				cnt++ ;
				name = orgName + "-" + cnt;
			}
		}

		// add to resource
		RestMethod restMethod = newResource.addNewMethod( name );
		restMethod.setMethod( RestRequestInterface.RequestMethod.valueOf( method.getName() ) );

		if( method.getRequest() != null )
		{
			for( Param param : method.getRequest().getParamList() )
			{
				param = resolveParameter( param );
				if( param != null )
				{
					RestParamProperty p = restMethod.addProperty( param.getName() );
					initParam( param, p );
				}
			}

			for( Representation representation : method.getRequest().getRepresentationList() )
			{
				representation = resolveRepresentation( representation );
				addRepresentationFromConfig( restMethod, representation, RestRepresentation.Type.REQUEST, null );
			}
		}

		for( Response response : method.getResponseList() )
		{
			for( Representation representation : response.getRepresentationList() )
			{
				addRepresentation( response, restMethod, representation );
			}
			if( !isWADL11 )
			{
				NodeList children = response.getDomNode().getChildNodes();
				for( int i = 0; i < children.getLength(); i++ )
				{
					Node n = children.item( i );
					if( "fault".equals( n.getNodeName() ) )
					{
						String content = XmlUtils.serialize( n, false );
						try
						{
							Map<Object, Object> map = new HashMap<Object, Object>();
							XmlCursor cursor = response.newCursor();
							cursor.getAllNamespaces( map );
							cursor.dispose();
							XmlOptions options = new XmlOptions();
							options.setLoadAdditionalNamespaces( map );
							// XmlObject obj = XmlObject.Factory.parse(
							// content.replaceFirst( "<(([a-z]+:)?)fault ",
							// "<$1representation " ), options );
							XmlObject obj = XmlUtils.createXmlObject(
									content.replaceFirst( "<(([a-z]+:)?)fault ", "<$1representation " ), options );
							RepresentationDocument representation = ( RepresentationDocument )obj
									.changeType( RepresentationDocument.type );
							addRepresentation( response, restMethod, representation.getRepresentation() );
						}
						catch( XmlException e )
						{
						}
					}
				}
			}
		}

		restMethod.addNewRequest( "Request 1" );
		return restMethod;
	}

	private void addRepresentation( Response response, RestMethod restMethod, Representation representation )
	{
		representation = resolveRepresentation( representation );
		List<Long> status = null;
		if( isWADL11 )
			status = response.getStatus();
		else
		{
			Node n = representation.getDomNode().getAttributes().getNamedItem( "status" );
			if( n != null )
			{
				status = new ArrayList<Long>();
				for( String s : n.getNodeValue().split( " " ) )
				{
					status.add( Long.parseLong( s ) );
				}
			}
		}
		boolean fault = false;
		if( status != null && status.size() > 0 )
		{
			fault = true;
			for( Long s : status )
			{
				if( s < 400 )
				{
					fault = false;
					break;
				}
			}
		}
		RestRepresentation.Type type = fault ? RestRepresentation.Type.FAULT : RestRepresentation.Type.RESPONSE;
		addRepresentationFromConfig( restMethod, representation, type, status );
	}

	private void addRepresentationFromConfig( RestMethod restMethod, Representation representation,
			RestRepresentation.Type type, List<?> status )
	{
		RestRepresentation restRepresentation = restMethod.addNewRepresentation( type );
		restRepresentation.setMediaType( representation.getMediaType() );
		restRepresentation.setElement( representation.getElement() );
		if( status != null )
			restRepresentation.setStatus( status );
		restRepresentation.setId( representation.getId() );
		restRepresentation.setDescription( getFirstTitle( representation.getDocList(), null ) );
	}

	private void initParam( Param param, RestParamProperty prop )
	{
		prop.setDefaultValue( param.getDefault() );
		prop.setValue( param.getDefault() );
		ParamStyle.Enum paramStyle = param.getStyle();
		if( paramStyle == null )
			paramStyle = ParamStyle.QUERY;

		prop.setStyle( ParameterStyle.valueOf( paramStyle.toString().toUpperCase() ) );
		prop.setRequired( param.getRequired() );
		QName paramType = param.getType();
		prop.setType( paramType );

		String[] options = new String[param.sizeOfOptionArray()];
		for( int c = 0; c < options.length; c++ )
			options[c] = param.getOptionArray( c ).getValue();

		if( options.length > 0 )
			prop.setOptions( options );
	}

	private Method resolveMethod( Method method )
	{
		String href = method.getHref();
		if( !StringUtils.hasContent( href ) )
			return method;

		for( Method m : application.getMethodList() )
		{
			if( m.getId().equals( href.substring( 1 ) ) )
				return m;
		}

		try
		{
			ApplicationDocument applicationDocument = loadReferencedWadl( href );
			if( applicationDocument != null )
			{
				int ix = href.lastIndexOf( '#' );
				if( ix > 0 )
					href = href.substring( ix + 1 );

				for( Method m : application.getMethodList() )
				{
					if( m.getId().equals( href ) )
						return m;
				}
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}

		return method;
	}

	private Representation resolveRepresentation( Representation representation )
	{
		String href = representation.getHref();
		if( !StringUtils.hasContent( href ) )
			return representation;

		try
		{
			Application app = application;

			if( !href.startsWith( "#" ) )
			{
				ApplicationDocument applicationDocument = loadReferencedWadl( href );
				app = applicationDocument.getApplication();
			}

			if( app != null )
			{
				int ix = href.lastIndexOf( '#' );
				if( ix >= 0 )
					href = href.substring( ix + 1 );

				for( Representation m : application.getRepresentationList() )
				{
					if( m.getId().equals( href ) )
						return m;
				}
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}

		return representation;
	}

	private Param resolveParameter( Param param )
	{
		String href = param.getHref();
		if( !StringUtils.hasContent( href ) )
			return param;

		try
		{
			Application app = application;

			if( !href.startsWith( "#" ) )
			{
				ApplicationDocument applicationDocument = loadReferencedWadl( href );
				app = applicationDocument.getApplication();
			}

			if( app != null )
			{
				int ix = href.lastIndexOf( '#' );
				if( ix >= 0 )
					href = href.substring( ix + 1 );

				for( Param p : application.getParamList() )
				{
					if( p.getId().equals( href ) )
						return p;
				}
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}

		return null;
	}

	/*
	 * private Representation resolveFault( Representation representation ) {
	 * String href = representation.getHref(); if( !StringUtils.hasContent( href
	 * ) ) return representation;
	 * 
	 * try { ApplicationDocument applicationDocument = loadReferencedWadl( href
	 * ); if( applicationDocument != null ) { int ix = href.lastIndexOf( '#' );
	 * if( ix > 0 ) href = href.substring( ix + 1 );
	 * 
	 * for( Representation m : application.getFaultList() ) { if(
	 * m.getId().equals( href ) ) return m; } } } catch( Exception e ) {
	 * e.printStackTrace(); }
	 * 
	 * return representation; }
	 */

	private ResourceTypeDocument.ResourceType resolveResource( String id )
	{
		for( ResourceTypeDocument.ResourceType resourceType : application.getResourceTypeList() )
		{
			if( resourceType.getId().equals( id ) )
				return resourceType;
		}

		try
		{
			ApplicationDocument applicationDocument = loadReferencedWadl( id );
			if( applicationDocument != null )
			{
				int ix = id.lastIndexOf( '#' );
				if( ix > 0 )
					id = id.substring( ix + 1 );

				for( ResourceTypeDocument.ResourceType resourceType : applicationDocument.getApplication()
						.getResourceTypeList() )
				{
					if( resourceType.getId().equals( id ) )
						return resourceType;
				}
			}
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}

		return null;
	}

	private ApplicationDocument loadReferencedWadl( String id ) throws URISyntaxException, XmlException, IOException
	{
		int ix = id.indexOf( '#' );
		if( ix != -1 )
			id = id.substring( 0, ix );
		ApplicationDocument applicationDocument = refCache.get( id );

		if( applicationDocument == null )
		{
			URI uri = new URI( id );
			applicationDocument = ApplicationDocument.Factory.parse( uri.toURL() );
			refCache.put( id, applicationDocument );
		}

		return applicationDocument;
	}

	public static Map<String, XmlObject> getDefinitionParts( String wadlUrl )
	{
		Map<String, XmlObject> result = new HashMap<String, XmlObject>();

		try
		{
			return SchemaUtils.getSchemas( wadlUrl, new UrlSchemaLoader( wadlUrl ) );

			// URL url = new URL(wadlUrl);
			// ApplicationDocument applicationDocument =
			// ApplicationDocument.Factory.parse(url);
			// result.put(url.getPath(), applicationDocument);
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}

		return result;
	}

	public static String extractParams( URL param, RestParamsPropertyHolder params )
	{
		String path = param.getPath();
		String[] items = path.split( "/" );

		int templateParamCount = 0;
		StringBuffer resultPath = new StringBuffer();

		for( int i = 0; i < items.length; i++ )
		{
			String item = items[i];
			try
			{
				String[] matrixParams = item.split( ";" );
				if( matrixParams.length > 0 )
				{
					item = matrixParams[0];
					for( int c = 1; c < matrixParams.length; c++ )
					{
						String matrixParam = matrixParams[c];

						int ix = matrixParam.indexOf( '=' );
						if( ix == -1 )
						{
							params.addProperty( URLDecoder.decode( matrixParam, "Utf-8" ) ).setStyle( ParameterStyle.MATRIX );
						}
						else
						{
							String name = matrixParam.substring( 0, ix );
							RestParamProperty property = params.addProperty( URLDecoder.decode( name, "Utf-8" ) );
							property.setStyle( ParameterStyle.MATRIX );
							property.setValue( URLDecoder.decode( matrixParam.substring( ix + 1 ), "Utf-8" ) );
						}
					}
				}

				Integer.parseInt( item );
				RestParamProperty prop = params.addProperty( "param" + templateParamCount++ );
				prop.setStyle( ParameterStyle.TEMPLATE );
				prop.setValue( item );

				item = "{" + prop.getName() + "}";
			}
			catch( Exception e )
			{
			}

			if( StringUtils.hasContent( item ) )
				resultPath.append( '/' ).append( item );
		}

		String query = ( ( URL )param ).getQuery();
		if( StringUtils.hasContent( query ) )
		{
			items = query.split( "&" );
			for( String item : items )
			{
				try
				{
					int ix = item.indexOf( '=' );
					if( ix == -1 )
					{
						params.addProperty( URLDecoder.decode( item, "Utf-8" ) ).setStyle( ParameterStyle.QUERY );
					}
					else
					{
						String name = item.substring( 0, ix );
						RestParamProperty property = params.addProperty( URLDecoder.decode( name, "Utf-8" ) );
						property.setStyle( ParameterStyle.QUERY );
						property.setValue( URLDecoder.decode( item.substring( ix + 1 ), "Utf-8" ) );
					}
				}
				catch( UnsupportedEncodingException e )
				{
					e.printStackTrace();
				}
			}
		}

		return resultPath.toString();
	}
}
