/*
 *  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.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.AttachmentConfig;
import com.eviware.soapui.config.CompressedStringConfig;
import com.eviware.soapui.config.CredentialsConfig;
import com.eviware.soapui.config.HttpRequestConfig;
import com.eviware.soapui.config.OldRestRequestConfig;
import com.eviware.soapui.config.RestParameterConfig;
import com.eviware.soapui.config.RestParametersConfig;
import com.eviware.soapui.config.RestRequestConfig;
import com.eviware.soapui.config.RestRequestStepConfig;
import com.eviware.soapui.config.RestResourceRepresentationConfig;
import com.eviware.soapui.config.SettingsConfig;
import com.eviware.soapui.config.StringToStringMapConfig;
import com.eviware.soapui.config.StringToStringMapConfig.Entry;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.impl.rest.RestMethod;
import com.eviware.soapui.impl.rest.RestRepresentation;
import com.eviware.soapui.impl.rest.RestRequest;
import com.eviware.soapui.impl.rest.RestRequestInterface.RequestMethod;
import com.eviware.soapui.impl.rest.RestResource;
import com.eviware.soapui.impl.rest.RestService;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.teststeps.RestTestRequestStep;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;

/**
 * Convert old-style REST Requests to new ones, creating REST Methods for them
 * or placing them into existing Methods.
 * 
 * @author Dain Nilsson
 */
public class RestRequestConverter
{

	private static Map<Project, Boolean> autoConvert = new HashMap<Project, Boolean>();
	private final static Logger log = Logger.getLogger( RestRequestConverter.class );

	public static void convert( RestResource resource, OldRestRequestConfig oldConfig )
	{
		convert( resource, oldConfig, getMethod( resource, oldConfig.getMethod(), oldConfig.getName() ) );
	}

	public static RestMethod getMethod( RestResource resource, String methodType, String requestName )
	{
		WsdlProject project = resource.getService().getProject();
		if( !autoConvert.containsKey( project ) )
		{
			autoConvert
					.put( project,
							UISupport
									.confirm(
											"The model for REST requests has changed slightly,\r\n"
													+ "introducing a new REST Method item in-between each REST Resource and Request.\r\n"
													+ "Any existing REST Request must now be placed under either an existing Method or a new one, "
													+ "either automatically or manually.\r\n\r\nWould You like soapUI to do this automatically using the default values?",
											"Update REST model for project: " + project.getName() ) );
		}
		RestMethod method = null;
		List<String> options = new ArrayList<String>();
		for( int c = 0; c < resource.getRestMethodCount(); c++ )
		{
			RestMethod restMethod = resource.getRestMethodAt( c );
			if( restMethod.getMethod().toString().equals( methodType ) )
				options.add( restMethod.getName() );
		}
		if( autoConvert.get( project ) )
		{
			if( options.size() > 0 )
			{
				method = resource.getRestMethodByName( options.get( 0 ) );
				log.info( "Placed request '" + requestName + "' under method '" + method.getName() + "' in Resource '"
						+ resource.getName() + "'." );
			}
			else
			{
				method = resource.addNewMethod( methodType + " Method" );
				method.setMethod( RequestMethod.valueOf( methodType ) );
				log.info( "Created new Method for Resource '" + resource.getName() + "'." );
			}
		}
		else
		{
			options.add( "[Create new REST Method]" );
			if( requestName == null )
				requestName = "REST Request";
			String message = "Select REST Method to place \"" + resource.getName() + " > " + requestName + "\" under.";
			Object op = UISupport.prompt( message, "Migrate REST Request", options.toArray() );
			if( op != null )
			{
				int ix = options.indexOf( op );
				if( ix != -1 && ix != options.size() - 1 )
				{
					method = resource.getRestMethodByName( ( String )op );
				}
			}
			else
				throw new RestConversionException( "Cannot get RestMethod selection!" );
			if( method == null )
			{
				String name = UISupport.prompt( "Name for REST " + methodType + " Method", "Create new REST Method",
						methodType + " Method" );
				if( name == null )
					throw new RestConversionException( "Cannot get name for RestMethod!" );
				method = resource.addNewMethod( name );
				method.setMethod( RequestMethod.valueOf( methodType ) );
			}
		}
		return method;
	}

	public static RestResource resolveResource( RestTestRequestStep requestStep )
	{
		Map<String, RestResource> options = new LinkedHashMap<String, RestResource>();

		WsdlProject project = requestStep.getTestCase().getTestSuite().getProject();
		String serviceName = requestStep.getRequestStepConfig().getService();
		RestService service = ( RestService )project.getInterfaceByName( serviceName );
		if( service != null )
		{
			addResources( service, options );
		}
		else
		{
			for( Interface iface : project.getInterfaceList() )
			{
				if( iface instanceof RestService )
				{
					addResources( ( RestService )iface, options );
				}
			}
		}
		options.put( "<Delete TestRequest>", null );

		String message = "Select a new REST Resource to place TestRequest \"" + requestStep.getName() + "\" under.";
		Object op = UISupport.prompt( message, "Missing REST Resource for TestRequest", options.keySet().toArray() );
		RestResource resource = options.get( op );

		return resource;
	}

	private static void addResources( RestService service, Map<String, RestResource> list )
	{
		for( RestResource resource : service.getResources().values() )
		{
			list.put( service.getName() + " > " + resource.getName(), resource );
		}
	}

	@SuppressWarnings( "deprecation" )
	private static void convert( RestResource resource, OldRestRequestConfig oldConfig, RestMethod method )
	{

		RestRequest request = method.addNewRequest( oldConfig.getName() );

		XmlBeansRestParamsTestPropertyHolder params = new XmlBeansRestParamsTestPropertyHolder( null,
				oldConfig.getParameters() );
		RestParamsPropertyHolder parentParams = method.getOverlayParams();

		for( TestProperty prop : params.values() )
		{
			if( !parentParams.containsKey( prop.getName() ) )
			{
				method.getParams().addParameter( ( RestParamProperty )prop );
			}
			request.setPropertyValue( prop.getName(), prop.getValue() );
		}
		params.release();

		boolean exists;
		for( RestResourceRepresentationConfig rep : oldConfig.getRepresentationList() )
		{
			exists = false;
			for( RestRepresentation existing : method.getRepresentations(
					RestRepresentation.Type.valueOf( rep.getType().toString() ), rep.getMediaType() ) )
			{
				if( existing.getElement() == null && rep.getElement() == null
						|| existing.getElement().equals( rep.getElement() ) )
				{
					exists = true;
					break;
				}
			}
			if( !exists )
			{
				RestRepresentation repr = method.addNewRepresentation( RestRepresentation.Type.valueOf( rep.getType()
						.toString() ) );
				repr.setConfig( ( RestResourceRepresentationConfig )rep.copy() );
			}
		}

		RestRequestConfig newConfig = request.getConfig();

		newConfig.setRequest( oldConfig.getRequest() );

		for( AttachmentConfig ac : oldConfig.getAttachmentList() )
			try
			{
				if( ac.isSetData() )
				{
					File temp = File.createTempFile( "pattern", ".suffix" );
					temp.deleteOnExit();
					FileOutputStream out = new FileOutputStream( temp );
					out.write( ac.getData() );
					request.attachFile( temp, true );
				}
				else
				{
					request.attachFile( new File( ac.getUrl() ), false );
				}
			}
			catch( IOException e )
			{
				e.printStackTrace();
			}
		newConfig.setAttachmentArray( oldConfig.getAttachmentArray() );

		if( oldConfig.isSetFullPath() )
			newConfig.setFullPath( oldConfig.getFullPath() );
		if( oldConfig.isSetMediaType() )
			newConfig.setMediaType( oldConfig.getMediaType() );
		if( oldConfig.isSetPostQueryString() )
			newConfig.setPostQueryString( oldConfig.getPostQueryString() );
		if( oldConfig.isSetAccept() )
			newConfig.setAccept( oldConfig.getAccept() );
		if( oldConfig.isSetDescription() )
			newConfig.setDescription( oldConfig.getDescription() );
		if( oldConfig.isSetId() )
			newConfig.setId( oldConfig.getId() );
		if( oldConfig.isSetSettings() )
			newConfig.setSettings( ( SettingsConfig )oldConfig.getSettings().copy() );
		if( oldConfig.isSetSslKeystore() )
			newConfig.setSslKeystore( oldConfig.getSslKeystore() );
		if( oldConfig.isSetTimestamp() )
			newConfig.setTimestamp( oldConfig.getTimestamp() );
		if( oldConfig.isSetWadlId() )
			newConfig.setWadlId( oldConfig.getWadlId() );

		request.updateConfig( newConfig );

	}

	public static HttpRequestConfig convert( OldRestRequestConfig old )
	{
		HttpRequestConfig config = HttpRequestConfig.Factory.newInstance();
		config.setAssertionArray( old.getAssertionList().toArray( new TestAssertionConfig[old.sizeOfAssertionArray()] ) );
		config.setAttachmentArray( old.getAttachmentList().toArray( new AttachmentConfig[old.sizeOfAttachmentArray()] ) );
		XmlObject obj = old.getCredentials();
		if( obj != null )
			config.setCredentials( ( CredentialsConfig )obj.copy() );
		obj = old.getParameters();
		if( obj != null )
			config.setParameters( ( RestParametersConfig )obj.copy() );

		obj = old.getRequest();
		if( obj != null )
			config.setRequest( ( CompressedStringConfig )obj.copy() );
		obj = old.getSettings();
		if( obj != null )
			config.setSettings( ( SettingsConfig )obj.copy() );
		if( old.isSetDescription() )
			config.setDescription( old.getDescription() );
		config.setEncoding( old.getEncoding() );
		config.setEndpoint( old.getEndpoint() );
		config.setSslKeystore( old.getSslKeystore() );
		if( old.isSetMediaType() )
			config.setMediaType( old.getMediaType() );
		if( old.isSetMethod() )
			config.setMethod( old.getMethod() );
		if( old.isSetName() )
			config.setName( old.getName() );
		if( old.isSetPostQueryString() )
			config.setPostQueryString( old.getPostQueryString() );
		return config;
	}

	public static HttpRequestConfig updateIfNeeded( XmlObject config )
	{
		try
		{
			if( config instanceof RestRequestStepConfig )
			{
				return convert( OldRestRequestConfig.Factory.parse( config.selectChildren(
						"http://eviware.com/soapui/config", "restRequest" )[0].toString() ) );
			}
			else
			{
				return ( HttpRequestConfig )config.changeType( HttpRequestConfig.type );
			}
		}
		catch( XmlException e )
		{
			return HttpRequestConfig.Factory.newInstance();
		}
	}

	public static class RestConversionException extends RuntimeException
	{
		public RestConversionException( String message )
		{
			super( message );
		}
	}

	public static void updateRestTestRequest( RestTestRequestStep restTestRequestStep )
	{
		try
		{
			RestRequestStepConfig restRequestStepConfig = ( RestRequestStepConfig )restTestRequestStep.getConfig()
					.getConfig();
			RestRequestConfig restRequestConfig = restRequestStepConfig.getRestRequest();
			OldRestRequestConfig oldConfig = OldRestRequestConfig.Factory.parse( restRequestConfig.toString() );

			RestParametersConfig oldParams = oldConfig.getParameters();
			if( oldParams != null )
			{
				StringToStringMapConfig newParams = restRequestConfig.addNewParameters();

				for( RestParameterConfig oldParam : oldParams.getParameterList() )
				{
					if( StringUtils.hasContent( oldParam.getValue() ) )
					{
						Entry entry = newParams.addNewEntry();
						entry.setKey( oldParam.getName() );
						entry.setValue( oldParam.getValue() );
					}
				}

				restRequestConfig.getParameters().getDomNode().getParentNode()
						.removeChild( restRequestConfig.getParameters().getDomNode() );
			}
		}
		catch( XmlException e )
		{
			SoapUI.logError( e );
		}
	}
}
