/*
 *  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;

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

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.GrammarsDocument.Grammars;
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.Representation;
import net.java.dev.wadl.x2009.x02.RequestDocument.Request;
import net.java.dev.wadl.x2009.x02.ResourceDocument.Resource;
import net.java.dev.wadl.x2009.x02.ResourcesDocument.Resources;
import net.java.dev.wadl.x2009.x02.ResponseDocument.Response;

import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.NodeList;

import com.eviware.soapui.impl.rest.panels.request.inspectors.schema.InferredSchemaManager;
import com.eviware.soapui.impl.rest.support.RestParamProperty;
import com.eviware.soapui.impl.rest.support.RestParamsPropertyHolder;
import com.eviware.soapui.impl.wsdl.support.Constants;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.xml.XmlUtils;

public class WadlGenerator
{
	private RestService restService;

	private boolean isWADL11 = true;

	public WadlGenerator( RestService restService )
	{
		this.restService = restService;
	}

	public XmlObject generateWadl()
	{
		ApplicationDocument applicationDocument = ApplicationDocument.Factory.newInstance();
		Application application = applicationDocument.addNewApplication();

		createDoc( application.addNewDoc(), restService );

		Resources resources = application.addNewResources();

		// use first endpoint for now -> this should be configurable
		String basePath = restService.getBasePath();
		String[] endpoints = restService.getEndpoints();
		if( endpoints.length > 0 )
			basePath = endpoints[0] + basePath;

		resources.setBase( basePath );

		for( int c = 0; c < restService.getOperationCount(); c++ )
		{
			resources.addNewResource().set( generateWadlResource( restService.getOperationAt( c ) ) );
		}

		String[] namespaces = InferredSchemaManager.getInferredSchema( restService ).getNamespaces();
		if( namespaces.length > 0 )
		{
			Grammars grammars = application.addNewGrammars();
			for( String namespace : namespaces )
			{
				grammars.addNewInclude().setHref( InferredSchemaManager.filenameForNamespace( namespace ) );
			}
		}

		if( !isWADL11 )
		{
			XmlOptions options = new XmlOptions();
			StringToStringMap subst = new StringToStringMap();
			subst.put( Constants.WADL11_NS, Constants.WADL10_NS );
			options.setLoadSubstituteNamespaces( subst );
			try
			{
				// return XmlObject.Factory.parse( applicationDocument.xmlText(),
				// options );
				return XmlUtils.createXmlObject( applicationDocument.xmlText(), options );
			}
			catch( XmlException e )
			{
				e.printStackTrace();
			}
		}

		return applicationDocument;
	}

	private XmlObject generateWadlResource( RestResource resource )
	{
		Resource resourceConfig = Resource.Factory.newInstance();
		createDoc( resourceConfig.addNewDoc(), resource );
		String path = resource.getPath();
		if( path.startsWith( "/" ) )
			path = path.length() > 1 ? path.substring( 1 ) : "";

		resourceConfig.setPath( path );
		resourceConfig.setId( resource.getName() );

		RestParamsPropertyHolder params = resource.getParams();
		for( int c = 0; c < params.size(); c++ )
		{
			generateParam( resourceConfig.addNewParam(), params.getPropertyAt( c ) );
		}

		for( int c = 0; c < resource.getChildResourceCount(); c++ )
		{
			resourceConfig.addNewResource().set( generateWadlResource( resource.getChildResourceAt( c ) ) );
		}

		for( int c = 0; c < resource.getRestMethodCount(); c++ )
		{
			RestMethod restMethod = resource.getRestMethodAt( c );
			generateWadlMethod( resourceConfig, restMethod );
		}

		return resourceConfig;
	}

	private void generateParam( Param paramConfig, RestParamProperty param )
	{
		paramConfig.setName( param.getName() );

		if( StringUtils.hasContent( param.getDefaultValue() ) )
			paramConfig.setDefault( param.getDefaultValue() );

		paramConfig.setType( param.getType() );
		paramConfig.setRequired( param.getRequired() );
		paramConfig.setDefault( param.getDefaultValue() );

		if( StringUtils.hasContent( param.getDescription() ) )
			createDoc( paramConfig.addNewDoc(), param.getName() + " Parameter", param.getDescription() );

		String[] options = param.getOptions();
		for( String option : options )
			paramConfig.addNewOption().setValue( option );

		ParamStyle.Enum style = ParamStyle.QUERY;
		switch( param.getStyle() )
		{
		case HEADER :
			style = ParamStyle.HEADER;
			break;
		case MATRIX :
			style = ParamStyle.MATRIX;
			break;
		case PLAIN :
			style = ParamStyle.PLAIN;
			break;
		case TEMPLATE :
			style = ParamStyle.TEMPLATE;
			break;
		}

		paramConfig.setStyle( style );
	}

	private void createDoc( Doc docConfig, ModelItem modelItem )
	{
		createDoc( docConfig, modelItem.getName(), modelItem.getDescription() );
	}

	private void createDoc( Doc docConfig, String name, String description )
	{
		docConfig.setLang( "en" );
		docConfig.setTitle( name );
		docConfig.getDomNode().appendChild( docConfig.getDomNode().getOwnerDocument().createTextNode( description ) );
	}

	private void generateWadlMethod( Resource resourceConfig, RestMethod restMethod )
	{
		Method methodConfig = resourceConfig.addNewMethod();
		createDoc( methodConfig.addNewDoc(), restMethod );
		methodConfig.setName( restMethod.getMethod().toString() );
		methodConfig.setId( restMethod.getName() );
		Request requestConfig = methodConfig.addNewRequest();

		Map<String, RestParamProperty> defaultParams = new HashMap<String, RestParamProperty>();
		for( RestParamProperty defaultParam : restMethod.getResource().getDefaultParams() )
			defaultParams.put( defaultParam.getName(), defaultParam );

		RestParamsPropertyHolder params = restMethod.getParams();
		for( int c = 0; c < params.size(); c++ )
		{
			RestParamProperty param = params.getPropertyAt( c );
			if( !defaultParams.containsKey( param.getName() ) || !param.equals( defaultParams.get( param.getName() ) ) )
				generateParam( requestConfig.addNewParam(), param );
		}

		if( restMethod.hasRequestBody() )
		{
			for( RestRepresentation representation : restMethod.getRepresentations( RestRepresentation.Type.REQUEST, null ) )
			{
				generateRepresentation( requestConfig.addNewRepresentation(), representation );
			}
		}

		Map<String, Response> responses = new HashMap<String, Response>();
		if( !isWADL11 )
			responses.put( null, methodConfig.addNewResponse() );
		for( RestRepresentation representation : restMethod.getRepresentations() )
		{
			Response response;
			if( isWADL11 )
			{
				List<Comparable> status = new ArrayList<Comparable>( ( List<Comparable> )representation.getStatus() );
				Collections.sort( status );
				StringBuilder statusStrBuilder = new StringBuilder();
				for( Object o : status )
					statusStrBuilder.append( o ).append( " " );
				String statusStr = statusStrBuilder.toString();

				if( !responses.containsKey( statusStr ) )
				{
					response = methodConfig.addNewResponse();
					response.setStatus( status );
					responses.put( statusStr, response );
				}
				else
				{
					response = responses.get( statusStr );
				}
			}
			else
			{
				response = responses.get( null );
			}

			Representation representationConfig = response.addNewRepresentation();
			generateRepresentation( representationConfig, representation );

			if( !isWADL11 && representation.getType() == RestRepresentation.Type.FAULT )
			{
				Element resp = ( Element )response.getDomNode();
				Element rep = ( Element )representationConfig.getDomNode();
				Element fault = resp.getOwnerDocument().createElementNS( Constants.WADL11_NS, "fault" );

				NamedNodeMap attributes = rep.getAttributes();
				for( int i = 0; i < attributes.getLength(); i++ )
				{
					fault.setAttribute( attributes.item( i ).getNodeName(), attributes.item( i ).getNodeValue() );
				}
				NodeList children = rep.getChildNodes();
				for( int i = 0; i < children.getLength(); i++ )
				{
					fault.appendChild( children.item( i ) );
				}

				resp.appendChild( fault );
				rep.getParentNode().removeChild( rep );
			}
		}
	}

	private void generateRepresentation( Representation representationConfig, RestRepresentation representation )
	{
		representationConfig.setMediaType( representation.getMediaType() );

		if( StringUtils.hasContent( representation.getId() ) )
			representationConfig.setId( representation.getId() );

		if( !isWADL11 )
		{
			List<?> status = representation.getStatus();
			if( status != null && status.size() > 0 )
			{
				StringBuilder statusStr = new StringBuilder();
				for( Object s : status )
					statusStr.append( s ).append( " " );
				( ( Element )representationConfig.getDomNode() ).setAttribute( "status", statusStr.toString().trim() );
			}
		}

		if( representation.getElement() != null )
			representationConfig.setElement( representation.getElement() );
	}

}
