/*
 *  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.wsdl.teststeps;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.ImageIcon;

import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.AMFRequestTestStepConfig;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.WsdlRequest;
import com.eviware.soapui.impl.wsdl.panels.teststeps.amf.AMFRequest;
import com.eviware.soapui.impl.wsdl.panels.teststeps.amf.AMFResponse;
import com.eviware.soapui.impl.wsdl.panels.teststeps.amf.AMFSubmit;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.attachments.WsdlSinglePartHttpResponse;
import com.eviware.soapui.impl.wsdl.support.AMFMessageExchange;
import com.eviware.soapui.impl.wsdl.support.XmlBeansPropertiesTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.support.assertions.AssertableConfig;
import com.eviware.soapui.impl.wsdl.support.assertions.AssertedXPathsContainer;
import com.eviware.soapui.impl.wsdl.support.assertions.AssertionsSupport;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
import com.eviware.soapui.impl.wsdl.teststeps.assertions.TestAssertionRegistry.AssertableType;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.iface.Request.SubmitException;
import com.eviware.soapui.model.iface.Submit;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.TestStepBeanProperty;
import com.eviware.soapui.model.testsuite.Assertable;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.model.testsuite.AssertionsListener;
import com.eviware.soapui.model.testsuite.SamplerTestStep;
import com.eviware.soapui.model.testsuite.TestAssertion;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestPropertyListener;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.model.testsuite.TestStepResult.TestStepStatus;
import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
import com.eviware.soapui.support.scripting.SoapUIScriptEngineRegistry;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.support.types.StringToStringsMap;

/**
 * 
 * @author nebojsa.tasic
 */

public class AMFRequestTestStep extends WsdlTestStepWithProperties implements Assertable, MutableTestPropertyHolder,
		PropertyChangeListener, SamplerTestStep
{
	@SuppressWarnings( "unused" )
	private final static Logger log = Logger.getLogger( WsdlTestRequestStep.class );
	protected AMFRequestTestStepConfig amfRequestTestStepConfig;
	public final static String amfREQUEST = AMFRequestTestStep.class.getName() + "@amfrequest";
	public static final String STATUS_PROPERTY = WsdlTestRequest.class.getName() + "@status";
	public static final String RESPONSE_PROPERTY = "response";
	public static final String REQUEST_PROPERTY = "request";
	public static final String HTTP_HEADERS_PROPERTY = AMFRequest.class.getName() + "@request-headers";
	public static final String AMF_HEADERS_PROPERTY = AMFRequest.class.getName() + "@amfrequest-amfheaders";
	private AMFSubmit submit;

	private SoapUIScriptEngine scriptEngine;
	private AssertionsSupport assertionsSupport;
	private PropertyChangeNotifier notifier;
	private XmlBeansPropertiesTestPropertyHolder propertyHolderSupport;

	private AMFRequest amfRequest;

	public AMFRequestTestStep( WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest )
	{

		super( testCase, config, true, forLoadTest );

		if( getConfig().getConfig() != null )
		{
			amfRequestTestStepConfig = ( AMFRequestTestStepConfig )getConfig().getConfig().changeType(
					AMFRequestTestStepConfig.type );

		}
		else
		{
			amfRequestTestStepConfig = ( AMFRequestTestStepConfig )getConfig().addNewConfig().changeType(
					AMFRequestTestStepConfig.type );
		}

		if( amfRequestTestStepConfig.getProperties() == null )
			amfRequestTestStepConfig.addNewProperties();

		amfRequest = new AMFRequest( this, forLoadTest );

		propertyHolderSupport = new XmlBeansPropertiesTestPropertyHolder( this, amfRequestTestStepConfig.getProperties() );
		addResponseAsXmlVirtualProperty();

		initAssertions();

		scriptEngine = SoapUIScriptEngineRegistry.create( this );
		scriptEngine.setScript( getScript() );
		if( forLoadTest && !isDisabled() )
			try
			{
				scriptEngine.compile();
			}
			catch( Exception e )
			{
				SoapUI.logError( e );
			}
	}

	private void addResponseAsXmlVirtualProperty()
	{
		TestStepBeanProperty responseProperty = new TestStepBeanProperty( WsdlTestStepWithProperties.RESPONSE_AS_XML,
				false, amfRequest, "responseContent", this )
		{
			@Override
			public String getDefaultValue()
			{
				return "";
			}

		};

		propertyHolderSupport.addVirtualProperty( WsdlTestStepWithProperties.RESPONSE_AS_XML, responseProperty );
	}

	public AMFRequestTestStepConfig getAMFRequestTestStepConfig()
	{
		return amfRequestTestStepConfig;
	}

	@Override
	public WsdlTestStep clone( WsdlTestCase targetTestCase, String name )
	{
		beforeSave();

		TestStepConfig config = ( TestStepConfig )getConfig().copy();
		AMFRequestTestStep result = ( AMFRequestTestStep )targetTestCase.addTestStep( config );

		return result;
	}

	@Override
	public void release()
	{
		super.release();
	}

	public TestStepResult run( TestCaseRunner runner, TestCaseRunContext runContext )
	{
		AMFTestStepResult testStepResult = new AMFTestStepResult( this );
		testStepResult.startTimer();
		runContext.setProperty( AssertedXPathsContainer.ASSERTEDXPATHSCONTAINER_PROPERTY, testStepResult );

		try
		{
			if( !initAmfRequest( runContext ) )
			{
				throw new SubmitException( "AMF request is not initialised properly !" );
			}
			submit = amfRequest.submit( runContext, false );
			AMFResponse response = submit.getResponse();

			if( submit.getStatus() != Submit.Status.CANCELED )
			{
				if( submit.getStatus() == Submit.Status.ERROR )
				{
					testStepResult.setStatus( TestStepStatus.FAILED );
					testStepResult.addMessage( submit.getError().toString() );

					amfRequest.setResponse( null );
				}
				else if( response == null )
				{
					testStepResult.setStatus( TestStepStatus.FAILED );
					testStepResult.addMessage( "Request is missing response" );

					amfRequest.setResponse( null );
				}
				else
				{
					runContext.setProperty( AssertedXPathsContainer.ASSERTEDXPATHSCONTAINER_PROPERTY, testStepResult );
					amfRequest.setResponse( response );

					testStepResult.setTimeTaken( response.getTimeTaken() );
					testStepResult.setSize( response.getContentLength() );

					switch( amfRequest.getAssertionStatus() )
					{
					case FAILED :
						testStepResult.setStatus( TestStepStatus.FAILED );
						break;
					case VALID :
						testStepResult.setStatus( TestStepStatus.OK );
						break;
					case UNKNOWN :
						testStepResult.setStatus( TestStepStatus.UNKNOWN );
						break;
					}

					testStepResult.setResponse( response, testStepResult.getStatus() != TestStepStatus.FAILED );
				}
			}
			else
			{
				testStepResult.setStatus( TestStepStatus.CANCELED );
				testStepResult.addMessage( "Request was canceled" );
			}

			if( response != null )
				testStepResult.setRequestContent( response.getRequestContent() );
			else
				testStepResult.setRequestContent( amfRequest.getRequestContent() );

			testStepResult.stopTimer();
		}
		catch( SubmitException e )
		{
			testStepResult.setStatus( TestStepStatus.FAILED );
			testStepResult.addMessage( "SubmitException: " + e );
			testStepResult.stopTimer();
		}
		finally
		{
			submit = null;
		}

		if( testStepResult.getStatus() != TestStepStatus.CANCELED )
		{
			assertResponse( runContext );

			AssertionStatus assertionStatus = amfRequest.getAssertionStatus();
			switch( assertionStatus )
			{
			case FAILED :
			{
				testStepResult.setStatus( TestStepStatus.FAILED );
				if( getAssertionCount() == 0 )
				{
					testStepResult.addMessage( "Invalid/empty response" );
				}
				else
					for( int c = 0; c < getAssertionCount(); c++ )
					{
						TestAssertion assertion = getAssertionAt( c );
						AssertionError[] errors = assertion.getErrors();
						if( errors != null )
						{
							for( AssertionError error : errors )
							{
								testStepResult.addMessage( "[" + assertion.getName() + "] " + error.getMessage() );
							}
						}
					}

				break;
			}
			// default : testStepResult.setStatus( TestStepStatus.OK ); break;
			}
		}

		if( isDiscardResponse() && !SoapUI.getDesktop().hasDesktopPanel( this ) )
			amfRequest.setResponse( null );

		// FIXME This should not be hard coded
		// FIXME This has to be tested before release
		firePropertyValueChanged( "ResponseAsXml", null, testStepResult.getResponseContentAsXml() );
		return testStepResult;
	}

	@Override
	public boolean cancel()
	{
		if( submit == null )
			return false;

		submit.cancel();

		return true;
	}

	public String getDefaultSourcePropertyName()
	{
		return "Response";
	}

	private void initAssertions()
	{
		assertionsSupport = new AssertionsSupport( this, new AssertableConfig()
		{

			public TestAssertionConfig addNewAssertion()
			{
				return getAMFRequestTestStepConfig().addNewAssertion();
			}

			public List<TestAssertionConfig> getAssertionList()
			{
				return getAMFRequestTestStepConfig().getAssertionList();
			}

			public void removeAssertion( int ix )
			{
				getAMFRequestTestStepConfig().removeAssertion( ix );
			}

			public TestAssertionConfig insertAssertion( TestAssertionConfig source, int ix )
			{
				TestAssertionConfig conf = getAMFRequestTestStepConfig().insertNewAssertion( ix );
				conf.set( source );
				return conf;
			}
		} );
	}

	private class PropertyChangeNotifier
	{
		private AssertionStatus oldStatus;
		private ImageIcon oldIcon;

		public PropertyChangeNotifier()
		{
			oldStatus = getAssertionStatus();
			oldIcon = getIcon();
		}

		public void notifyChange()
		{
			AssertionStatus newStatus = getAssertionStatus();
			ImageIcon newIcon = getIcon();

			if( oldStatus != newStatus )
				notifyPropertyChanged( STATUS_PROPERTY, oldStatus, newStatus );

			if( oldIcon != newIcon )
				notifyPropertyChanged( ICON_PROPERTY, oldIcon, getIcon() );

			oldStatus = newStatus;
			oldIcon = newIcon;
		}
	}

	public TestAssertion addAssertion( String assertionLabel )
	{
		PropertyChangeNotifier notifier = new PropertyChangeNotifier();

		try
		{
			WsdlMessageAssertion assertion = assertionsSupport.addWsdlAssertion( assertionLabel );
			if( assertion == null )
				return null;

			if( getAMFRequest().getResponse() != null )
			{
				assertion.assertResponse( new AMFMessageExchange( this, getAMFRequest().getResponse() ),
						new WsdlTestRunContext( this ) );
				notifier.notifyChange();
			}

			return assertion;
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
			return null;
		}
	}

	public void addAssertionsListener( AssertionsListener listener )
	{
		assertionsSupport.addAssertionsListener( listener );
	}

	public TestAssertion cloneAssertion( TestAssertion source, String name )
	{
		return assertionsSupport.cloneAssertion( source, name );
	}

	public String getAssertableContent()
	{
		return getAMFRequest().getResponse() == null ? null : getAMFRequest().getResponse().getContentAsString();
	}

	public WsdlMessageAssertion importAssertion( WsdlMessageAssertion source, boolean overwrite, boolean createCopy,
			String newName )
	{
		return assertionsSupport.importAssertion( source, overwrite, createCopy, newName );
	}

	public AssertableType getAssertableType()
	{
		return AssertableType.RESPONSE;
	}

	public TestAssertion getAssertionAt( int c )
	{
		return assertionsSupport.getAssertionAt( c );
	}

	public TestAssertion getAssertionByName( String name )
	{
		return assertionsSupport.getAssertionByName( name );
	}

	public int getAssertionCount()
	{
		return assertionsSupport.getAssertionCount();
	}

	public List<TestAssertion> getAssertionList()
	{
		return new ArrayList<TestAssertion>( assertionsSupport.getAssertionList() );
	}

	public void propertyChange( PropertyChangeEvent arg0 )
	{
		if( arg0.getPropertyName().equals( TestAssertion.CONFIGURATION_PROPERTY )
				|| arg0.getPropertyName().equals( TestAssertion.DISABLED_PROPERTY ) )
		{
			if( getAMFRequest().getResponse() != null )
			{
				assertResponse( new WsdlTestRunContext( this ) );
			}
		}
	}

	public Map<String, TestAssertion> getAssertions()
	{
		return assertionsSupport.getAssertions();
	}

	public String getDefaultAssertableContent()
	{
		return null;
	}

	public AssertionStatus getAssertionStatus()
	{
		return amfRequest.getAssertionStatus();
	}

	public ImageIcon getIcon()
	{
		return amfRequest.getIcon();
	}

	public Interface getInterface()
	{
		return null;
	}

	public TestAssertion moveAssertion( int ix, int offset )
	{
		PropertyChangeNotifier notifier = new PropertyChangeNotifier();
		TestAssertion assertion = getAssertionAt( ix );
		try
		{
			return assertionsSupport.moveAssertion( ix, offset );
		}
		finally
		{
			( ( WsdlMessageAssertion )assertion ).release();
			notifier.notifyChange();
		}
	}

	public void removeAssertion( TestAssertion assertion )
	{
		PropertyChangeNotifier notifier = new PropertyChangeNotifier();

		try
		{
			assertionsSupport.removeAssertion( ( WsdlMessageAssertion )assertion );

		}
		finally
		{
			( ( WsdlMessageAssertion )assertion ).release();
			notifier.notifyChange();
		}
	}

	public void removeAssertionsListener( AssertionsListener listener )
	{
		assertionsSupport.removeAssertionsListener( listener );
	}

	public void assertResponse( SubmitContext context )
	{
		try
		{
			if( notifier == null )
				notifier = new PropertyChangeNotifier();

			AMFMessageExchange messageExchange = new AMFMessageExchange( this, getAMFRequest().getResponse() );

			if( this != null )
			{
				// assert!
				for( WsdlMessageAssertion assertion : assertionsSupport.getAssertionList() )
				{
					assertion.assertResponse( messageExchange, context );
				}
			}

			notifier.notifyChange();
		}
		catch( Exception e )
		{
			e.printStackTrace();
		}
	}

	public TestProperty addProperty( String name )
	{
		return propertyHolderSupport.addProperty( name );
	}

	public TestProperty removeProperty( String propertyName )
	{
		return propertyHolderSupport.removeProperty( propertyName );
	}

	public boolean renameProperty( String name, String newName )
	{
		return PropertyExpansionUtils.renameProperty( propertyHolderSupport.getProperty( name ), newName, getTestCase() ) != null;
	}

	// FIXME Remove the overridden methods in TestPropertyHolder

	//	public void addTestPropertyListener( TestPropertyListener listener )
	//	{
	//		propertyHolderSupport.addTestPropertyListener( listener );
	//	}

	public Map<String, TestProperty> getProperties()
	{
		return propertyHolderSupport.getProperties();
	}

	public TestProperty getProperty( String name )
	{
		return propertyHolderSupport.getProperty( name );
	}

	public TestProperty getPropertyAt( int index )
	{
		return propertyHolderSupport.getPropertyAt( index );
	}

	public int getPropertyCount()
	{
		return propertyHolderSupport.getPropertyCount();
	}

	public List<TestProperty> getPropertyList()
	{
		return propertyHolderSupport.getPropertyList();
	}

	public String[] getPropertyNames()
	{
		return propertyHolderSupport.getPropertyNames();
	}

	public String getPropertyValue( String name )
	{
		return propertyHolderSupport.getPropertyValue( name );
	}

	public void removeTestPropertyListener( TestPropertyListener listener )
	{
		propertyHolderSupport.removeTestPropertyListener( listener );
	}

	public boolean hasProperty( String name )
	{
		return propertyHolderSupport.hasProperty( name );
	}

	public void setPropertyValue( String name, String value )
	{
		propertyHolderSupport.setPropertyValue( name, value );
	}

	public void setPropertyValue( String name, Object value )
	{
		setPropertyValue( name, String.valueOf( value ) );
	}

	public void moveProperty( String propertyName, int targetIndex )
	{
		propertyHolderSupport.moveProperty( propertyName, targetIndex );
	}

	public AMFRequest getAMFRequest()
	{
		return amfRequest;
	}

	public void setResponse( AMFResponse response, SubmitContext context )
	{
		AMFResponse oldResponse = amfRequest.getResponse();
		amfRequest.setResponse( response );

		notifyPropertyChanged( RESPONSE_PROPERTY, oldResponse, response );
		assertResponse( context );
	}

	public String getScript()
	{
		return amfRequestTestStepConfig.getScript() != null ? amfRequestTestStepConfig.getScript().getStringValue() : "";
	}

	public void setScript( String script )
	{
		String old = getScript();
		scriptEngine.setScript( script );
		if( amfRequestTestStepConfig.getScript() == null )
		{
			amfRequestTestStepConfig.addNewScript();
		}

		amfRequestTestStepConfig.getScript().setStringValue( script );
		notifyPropertyChanged( "script", old, script );
	}

	public String getAmfCall()
	{
		return amfRequestTestStepConfig.getAmfCall();
	}

	public void setAmfCall( String amfCall )
	{
		String old = getAmfCall();
		amfRequestTestStepConfig.setAmfCall( amfCall );
		notifyPropertyChanged( "amfCall", old, amfCall );
	}

	public String getEndpoint()
	{
		return amfRequestTestStepConfig.getEndpoint();
	}

	public void setEndpoint( String endpoint )
	{
		String old = getEndpoint();
		amfRequestTestStepConfig.setEndpoint( endpoint );
		notifyPropertyChanged( "endpoint", old, endpoint );
	}

	public boolean initAmfRequest( SubmitContext submitContext )
	{
		amfRequest.setScriptEngine( scriptEngine );
		amfRequest.setAmfCall( PropertyExpander.expandProperties( submitContext, getAmfCall() ) );
		amfRequest.setEndpoint( PropertyExpander.expandProperties( submitContext, getEndpoint() ) );
		amfRequest.setScript( getScript() );
		amfRequest.setPropertyNames( getPropertyNames() );
		amfRequest.setPropertyMap( ( HashMap<String, TestProperty> )getProperties() );
		amfRequest.setHttpHeaders( getHttpHeaders() );
		amfRequest.setAmfHeadersString( getAmfHeaders() );

		return amfRequest.executeAmfScript( submitContext );
	}

	public void setHttpHeaders( StringToStringsMap httpHeaders )
	{
		StringToStringsMap old = getHttpHeaders();
		getSettings().setString( HTTP_HEADERS_PROPERTY, httpHeaders.toXml() );
		notifyPropertyChanged( HTTP_HEADERS_PROPERTY, old, httpHeaders );
	}

	public StringToStringsMap getHttpHeaders()
	{
		return StringToStringsMap.fromXml( getSettings().getString( HTTP_HEADERS_PROPERTY, null ) );
	}

	public void setAmfHeaders( StringToStringMap amfHeaders )
	{
		StringToStringMap old = getAmfHeaders();
		getSettings().setString( AMF_HEADERS_PROPERTY, amfHeaders.toXml() );
		notifyPropertyChanged( AMF_HEADERS_PROPERTY, old, amfHeaders );
	}

	public StringToStringMap getAmfHeaders()
	{
		return StringToStringMap.fromXml( getSettings().getString( AMF_HEADERS_PROPERTY, null ) );
	}

	public void resetConfigOnMove( TestStepConfig config )
	{
		super.resetConfigOnMove( config );
		amfRequestTestStepConfig = ( AMFRequestTestStepConfig )config.getConfig().changeType(
				AMFRequestTestStepConfig.type );
		propertyHolderSupport.resetPropertiesConfig( amfRequestTestStepConfig.getProperties() );
		// addResponseAsXmlVirtualProperty();
		assertionsSupport.refresh();
	}

	public XmlBeansPropertiesTestPropertyHolder getPropertyHolderSupport()
	{
		return propertyHolderSupport;
	}

	public TestStep getTestStep()
	{
		return this;
	}

	public boolean isDiscardResponse()
	{
		return amfRequest.isDiscardResponse();
	}

	public void setDiscardResponse( boolean discardResponse )
	{
		amfRequest.setDiscardResponse( discardResponse );
	}

	public TestRequest getTestRequest()
	{
		return amfRequest;
	}
}
