/*
 *  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.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.LoadTestConfig;
import com.eviware.soapui.config.RunTestCaseRunModeTypeConfig;
import com.eviware.soapui.config.RunTestCaseRunModeTypeConfig.Enum;
import com.eviware.soapui.config.RunTestCaseStepConfig;
import com.eviware.soapui.config.TestCaseConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.wsdl.WsdlTestSuite;
import com.eviware.soapui.impl.wsdl.support.XmlBeansPropertiesTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.support.XmlBeansPropertiesTestPropertyHolder.PropertiesStepProperty;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.support.ModelSupport;
import com.eviware.soapui.model.support.TestPropertyListenerAdapter;
import com.eviware.soapui.model.support.TestRunListenerAdapter;
import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
import com.eviware.soapui.model.testsuite.MessageExchangeTestStepResult;
import com.eviware.soapui.model.testsuite.TestCase;
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.TestRunContext;
import com.eviware.soapui.model.testsuite.TestRunListener;
import com.eviware.soapui.model.testsuite.TestRunner;
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.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.resolver.ChooseAnotherTestCase;
import com.eviware.soapui.support.resolver.CreateNewEmptyTestCase;
import com.eviware.soapui.support.resolver.ResolveContext;
import com.eviware.soapui.support.resolver.RunTestCaseRemoveResolver;
import com.eviware.soapui.support.types.StringList;
import com.eviware.soapui.support.types.StringToObjectMap;

public class WsdlRunTestCaseTestStep extends WsdlTestStep
{
	public static final String TARGET_TESTCASE = WsdlRunTestCaseTestStep.class.getName() + "@target_testcase";

	private RunTestCaseStepConfig stepConfig;
	private WsdlTestCaseRunner testCaseRunner;
	private XmlBeansPropertiesTestPropertyHolder propertyHolderSupport;
	private String currentLabel;
	private WsdlTestCase targetTestCase;
	private InternalTestSuiteListener testSuiteListener = new InternalTestSuiteListener();
	private InternalTestRunListener testRunListener = new InternalTestRunListener();
	private InternalTestPropertyListener testPropertyListener = new InternalTestPropertyListener();
	private Set<TestRunListener> testRunListeners = new HashSet<TestRunListener>();
	private WsdlTestCase runningTestCase;

	public WsdlRunTestCaseTestStep( WsdlTestCase testCase, TestStepConfig config, boolean forLoadTest )
	{
		super( testCase, config, true, forLoadTest );

		if( config.getConfig() == null )
		{
			stepConfig = ( RunTestCaseStepConfig )config.addNewConfig().changeType( RunTestCaseStepConfig.type );
			stepConfig.addNewProperties();
			stepConfig.addNewReturnProperties();
		}
		else
		{
			stepConfig = ( RunTestCaseStepConfig )config.getConfig().changeType( RunTestCaseStepConfig.type );
		}

		if( stepConfig.getRunMode() == null )
		{
			stepConfig.setRunMode( RunTestCaseRunModeTypeConfig.PARALLELL );
		}

		setIcon( UISupport.createImageIcon( "/run_testcase_step.gif" ) );

		propertyHolderSupport = new XmlBeansPropertiesTestPropertyHolder( this, stepConfig.getProperties() );
	}

	/**
	 * We need to check that we are not pointing at testcase in original
	 * testsuite
	 */

	public void afterCopy( WsdlTestSuite oldTestSuite, WsdlTestCase oldTestCase )
	{
		super.afterCopy( oldTestSuite, oldTestCase );

		if( targetTestCase != null && oldTestSuite == targetTestCase.getTestSuite() )
		{
			setTargetTestCase( getTestCase().getTestSuite().getTestCaseByName( targetTestCase.getName() ) );
		}
	}

	@Override
	public void afterLoad()
	{
		setTargetTestCase( findTargetTestCase() );

		super.afterLoad();
	}

	private void syncProperties()
	{
		for( String name : propertyHolderSupport.getPropertyNames() )
		{
			if( !targetTestCase.hasProperty( name ) )
				propertyHolderSupport.removeProperty( name );
		}

		for( String name : targetTestCase.getPropertyNames() )
		{
			if( !propertyHolderSupport.hasProperty( name ) )
				propertyHolderSupport.addProperty( name );
		}
	}

	private WsdlTestCase findTargetTestCase()
	{
		return ModelSupport.findModelItemById( getTestCaseId(), getTestCase().getTestSuite().getProject() );
	}

	public StringList getReturnProperties()
	{
		return new StringList( stepConfig.getReturnProperties().getEntryList() );
	}

	public void setReturnProperties( StringList returnProperties )
	{
		stepConfig.getReturnProperties().setEntryArray( returnProperties.toStringArray() );
	}

	public TestStepResult run( TestCaseRunner testRunner, TestCaseRunContext testRunContext )
	{
		WsdlMessageExchangeTestStepResult result = new WsdlMessageExchangeTestStepResult( this );

		testCaseRunner = null;

		if( targetTestCase != null )
		{
			Enum runMode = getRunMode();

			if( runMode == RunTestCaseRunModeTypeConfig.PARALLELL )
			{
				runningTestCase = createTestCase( targetTestCase );
			}
			else
			{
				runningTestCase = targetTestCase;

				TestCaseRunner targetTestRunner = SoapUI.getTestMonitor().getTestRunner( targetTestCase );
				if( targetTestRunner != null && targetTestRunner.getStatus() == TestRunner.Status.RUNNING )
				{
					if( runMode == RunTestCaseRunModeTypeConfig.SINGLETON_AND_FAIL )
					{
						result.setStatus( TestStepStatus.FAILED );
						result.addMessage( "Target TestCase is already running" );
						result.stopTimer();
						runningTestCase = null;
					}
					else
					{
						targetTestRunner.waitUntilFinished();
					}
				}
			}

			if( runningTestCase != null )
			{
				synchronized( runningTestCase )
				{
					for( TestRunListener listener : testRunListeners )
						runningTestCase.addTestRunListener( listener );

					StringList returnProperties = getReturnProperties();
					Map<String, TestProperty> props = getProperties();
					for( String key : props.keySet() )
					{
						if( runningTestCase.hasProperty( key ) && !returnProperties.contains( key ) )
						{
							String value = PropertyExpander.expandProperties( testRunContext, props.get( key ).getValue() );
							if( StringUtils.hasContent( value ) || !isIgnoreEmptyProperties() )
								runningTestCase.setPropertyValue( key, value );
						}
					}

					currentLabel = getLabel();
					runningTestCase.addTestRunListener( testRunListener );

					// StringToObjectMap properties = new StringToObjectMap();
					// for( String name : testRunContext.getPropertyNames() )
					// properties.put( name, testRunContext.getProperty( name ));

					result.startTimer();
					StringToObjectMap properties = new StringToObjectMap();

					if( isCopyLoadTestProperties() && properties.containsKey( TestRunContext.LOAD_TEST_CONTEXT ) )
					{
						properties
								.put( TestRunContext.THREAD_INDEX, testRunContext.getProperty( TestRunContext.THREAD_INDEX ) );
						properties.put( TestRunContext.TOTAL_RUN_COUNT,
								testRunContext.getProperty( TestRunContext.TOTAL_RUN_COUNT ) );
						properties.put( TestRunContext.LOAD_TEST_CONTEXT,
								testRunContext.getProperty( TestRunContext.LOAD_TEST_CONTEXT ) );
						properties.put( TestRunContext.LOAD_TEST_RUNNER,
								testRunContext.getProperty( TestRunContext.LOAD_TEST_RUNNER ) );
						properties.put( TestRunContext.RUN_COUNT, testRunContext.getProperty( TestRunContext.RUN_COUNT ) );
					}

					if( isCopyHttpSession() && testRunContext.hasProperty( TestRunContext.HTTP_STATE_PROPERTY ) )
					{
						properties.put( TestRunContext.HTTP_STATE_PROPERTY,
								testRunContext.getProperty( TestRunContext.HTTP_STATE_PROPERTY ) );
					}

					properties.put( TestRunContext.INTERACTIVE, testRunContext.getProperty( TestRunContext.INTERACTIVE ) );

					properties.put( "#CallingRunTestCaseStep#", this );
					properties.put( "#CallingTestCaseRunner#", testRunner );
					properties.put( "#CallingTestRunContext#", testRunContext );

					testCaseRunner = runningTestCase.run( properties, true );
					testCaseRunner.waitUntilFinished();
					result.stopTimer();

					for( String key : returnProperties )
					{
						if( runningTestCase.hasProperty( key ) )
							setPropertyValue( key, runningTestCase.getPropertyValue( key ) );
					}

					// aggregate results
					for( TestStepResult testStepResult : testCaseRunner.getResults() )
					{
						result.addMessage( testStepResult.getTestStep().getName() + " - " + testStepResult.getStatus()
								+ " - " + testStepResult.getTimeTaken() );
						for( String msg : testStepResult.getMessages() )
						{
							result.addMessage( "- " + msg );
						}

						if( testStepResult instanceof MessageExchangeTestStepResult )
						{
							result.addMessages( ( ( MessageExchangeTestStepResult )testStepResult ).getMessageExchanges() );
						}
					}

					switch( testCaseRunner.getStatus() )
					{
					case CANCELED :
						result.setStatus( TestStepStatus.CANCELED );
						break;
					case FAILED :
						result.setStatus( TestStepStatus.FAILED );
						break;
					case FINISHED :
						result.setStatus( TestStepStatus.OK );
						break;
					default :
						result.setStatus( TestStepStatus.UNKNOWN );
						break;
					}

					for( TestRunListener listener : testRunListeners )
						runningTestCase.removeTestRunListener( listener );

					if( runMode == RunTestCaseRunModeTypeConfig.PARALLELL )
						runningTestCase.release();

					runningTestCase.removeTestRunListener( testRunListener );

					runningTestCase = null;
					testCaseRunner = null;
				}
			}
		}
		else
		{
			result.setStatus( TestStepStatus.FAILED );
			result.addMessage( "Missing testCase in project" );
			result.stopTimer();
		}

		return result;
	}

	@Override
	public String getLabel()
	{
		String name = getName();

		if( testCaseRunner != null )
		{
			name += " - [" + testCaseRunner.getStatus() + "]";
		}

		if( isDisabled() )
			return name + " (disabled)";
		else
			return name;
	}

	@Override
	public boolean cancel()
	{
		if( testCaseRunner != null )
		{
			testCaseRunner.cancel( "Canceled by calling TestCase" );
		}

		return true;
	}

	private String getTestCaseId()
	{
		return stepConfig.getTargetTestCase();
	}

	public void setTargetTestCase( WsdlTestCase testCase )
	{
		if( targetTestCase != null )
		{
			targetTestCase.getTestSuite().removeTestSuiteListener( testSuiteListener );
			targetTestCase.removeTestPropertyListener( testPropertyListener );
		}

		WsdlTestCase oldTestCase = this.targetTestCase;
		this.targetTestCase = testCase;

		if( testCase != null )
		{
			stepConfig.setTargetTestCase( testCase.getId() );

			targetTestCase.getTestSuite().addTestSuiteListener( testSuiteListener );
			targetTestCase.addTestPropertyListener( testPropertyListener );

			syncProperties();
		}

		notifyPropertyChanged( TARGET_TESTCASE, oldTestCase, testCase );
	}

	public boolean isCopyHttpSession()
	{
		return stepConfig.getCopyHttpSession();
	}

	public boolean isCopyLoadTestProperties()
	{
		return stepConfig.getCopyLoadTestProperties();
	}

	public boolean isIgnoreEmptyProperties()
	{
		return stepConfig.getIgnoreEmptyProperties();
	}

	public void setCopyHttpSession( boolean arg0 )
	{
		if( arg0 == isCopyHttpSession() )
			return;

		stepConfig.setCopyHttpSession( arg0 );
		notifyPropertyChanged( "copyHttpSession", !arg0, arg0 );
	}

	public void setCopyLoadTestProperties( boolean arg0 )
	{
		if( arg0 == isCopyLoadTestProperties() )
			return;

		stepConfig.setCopyLoadTestProperties( arg0 );
		notifyPropertyChanged( "copyLoadTestProperties", !arg0, arg0 );
	}

	public void setIgnoreEmptyProperties( boolean arg0 )
	{
		if( arg0 == isIgnoreEmptyProperties() )
			return;

		stepConfig.setIgnoreEmptyProperties( arg0 );
		notifyPropertyChanged( "ignoreEmptyProperties", !arg0, arg0 );
	}

	/**
	 * Creates a copy of the underlying WsdlTestCase with all LoadTests removed
	 * and configured for LoadTesting
	 */

	private WsdlTestCase createTestCase( WsdlTestCase testCase )
	{
		// clone config and remove and loadtests
		testCase.beforeSave();

		try
		{
			TestCaseConfig config = TestCaseConfig.Factory.parse( testCase.getConfig().xmlText() );
			config.setLoadTestArray( new LoadTestConfig[0] );

			// clone entire testCase
			WsdlTestCase wsdlTestCase = testCase.getTestSuite().buildTestCase( config, true );
			wsdlTestCase.afterLoad();
			return wsdlTestCase;
		}
		catch( Throwable e )
		{
			SoapUI.logError( e );
		}

		return null;
	}

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

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

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

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

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

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

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

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

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

	private void updateLabelDuringRun()
	{
		notifyPropertyChanged( WsdlTestStep.LABEL_PROPERTY, currentLabel, getLabel() );
		currentLabel = getLabel();
	}

	private final class InternalTestPropertyListener extends TestPropertyListenerAdapter
	{
		@Override
		public void propertyAdded( String name )
		{
			propertyHolderSupport.addProperty( name );
		}

		@Override
		public void propertyRemoved( String name )
		{
			propertyHolderSupport.removeProperty( name );
		}

		@Override
		public void propertyRenamed( String oldName, String newName )
		{
			propertyHolderSupport.renameProperty( oldName, newName );
		}

		@Override
		public void propertyMoved( String name, int oldIndex, int newIndex )
		{
			propertyHolderSupport.moveProperty( name, newIndex );
		}
	}

	private final class InternalTestRunListener extends TestRunListenerAdapter
	{
		@Override
		public void beforeRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
		{
			updateLabelDuringRun();
		}

		@Override
		public void afterRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
		{
			updateLabelDuringRun();
		}

		@Override
		public void afterStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result )
		{
			updateLabelDuringRun();
		}

		@Override
		public void beforeStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep testStep )
		{
			updateLabelDuringRun();
		}
	}

	@Override
	public void resetConfigOnMove( TestStepConfig config )
	{
		super.resetConfigOnMove( config );

		stepConfig = ( RunTestCaseStepConfig )config.getConfig().changeType( RunTestCaseStepConfig.type );
		propertyHolderSupport.resetPropertiesConfig( stepConfig.getProperties() );
	}

	@Override
	public void release()
	{
		if( targetTestCase != null )
		{
			targetTestCase.getTestSuite().removeTestSuiteListener( testSuiteListener );
			targetTestCase.removeTestPropertyListener( testPropertyListener );
		}

		super.release();
	}

	private final class InternalTestSuiteListener extends TestSuiteListenerAdapter
	{
		@Override
		public void testCaseRemoved( TestCase testCase )
		{
			setTargetTestCase( findTargetTestCase() );
		}
	}

	public WsdlTestCase getTargetTestCase()
	{
		return targetTestCase;
	}

	public void addTestRunListener( TestRunListener listener )
	{
		testRunListeners.add( listener );
	}

	public void removeTestRunListener( TestRunListener listener )
	{
		testRunListeners.remove( listener );
	}

	public WsdlTestCase getRunningTestCase()
	{
		return runningTestCase;
	}

	public WsdlTestCaseRunner getTestCaseRunner()
	{
		return testCaseRunner;
	}

	public RunTestCaseRunModeTypeConfig.Enum getRunMode()
	{
		return stepConfig.getRunMode();
	}

	public void setRunMode( RunTestCaseRunModeTypeConfig.Enum runMode )
	{
		stepConfig.setRunMode( runMode );
	}

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

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

	@Override
	public void resolve( ResolveContext<?> context )
	{
		super.resolve( context );

		if( targetTestCase == null )
		{
			if( context.hasThisModelItem( this, "Missing Test Case",
					getTestStepTitle() + "/" + stepConfig.getTargetTestCase() ) )
				return;
			context
					.addPathToResolve( this, "Missing Test Case", getTestStepTitle() + "/" + stepConfig.getTargetTestCase() )
					.addResolvers( new RunTestCaseRemoveResolver( this ), new ChooseAnotherTestCase( this ),
							new CreateNewEmptyTestCase( this ) );
		}
		else
		{
			targetTestCase.resolve( context );
			if( context.hasThisModelItem( this, "Missing Test Case",
					getTestStepTitle() + "/" + stepConfig.getTargetTestCase() ) )
			{
				context.getPath( this, "Missing Test Case", getTestStepTitle() + "/" + stepConfig.getTargetTestCase() )
						.setSolved( true );
			}
		}
	}
}
