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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.list.TreeList;
import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.LoadStrategyConfig;
import com.eviware.soapui.config.LoadTestAssertionConfig;
import com.eviware.soapui.config.LoadTestConfig;
import com.eviware.soapui.config.LoadTestLimitTypesConfig;
import com.eviware.soapui.config.LoadTestLimitTypesConfig.Enum;
import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
import com.eviware.soapui.impl.wsdl.loadtest.assertions.AbstractLoadTestAssertion;
import com.eviware.soapui.impl.wsdl.loadtest.assertions.LoadTestAssertionRegistry;
import com.eviware.soapui.impl.wsdl.loadtest.data.LoadTestStatistics;
import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLog;
import com.eviware.soapui.impl.wsdl.loadtest.log.LoadTestLogErrorEntry;
import com.eviware.soapui.impl.wsdl.loadtest.strategy.BurstLoadStrategy;
import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategy;
import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyFactory;
import com.eviware.soapui.impl.wsdl.loadtest.strategy.LoadStrategyRegistry;
import com.eviware.soapui.impl.wsdl.loadtest.strategy.SimpleLoadStrategy;
import com.eviware.soapui.impl.wsdl.support.Configurable;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCaseRunner;
import com.eviware.soapui.impl.wsdl.teststeps.SimplePathPropertySupport;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.model.support.LoadTestRunListenerAdapter;
import com.eviware.soapui.model.testsuite.LoadTest;
import com.eviware.soapui.model.testsuite.LoadTestRunContext;
import com.eviware.soapui.model.testsuite.LoadTestRunListener;
import com.eviware.soapui.model.testsuite.LoadTestRunner;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestRunnable;
import com.eviware.soapui.model.testsuite.TestRunner;
import com.eviware.soapui.model.testsuite.TestRunner.Status;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.settings.HttpSettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
import com.eviware.soapui.support.scripting.SoapUIScriptEngineRegistry;
import com.eviware.soapui.support.types.StringList;
import com.eviware.soapui.support.types.StringToObjectMap;

/**
 * TestCase implementation for LoadTests
 * 
 * @author Ole.Matzura
 * @todo add assertionFailed event to LoadTestListener
 * @todo create and return LoadTestAssertionResult from load-test assertions
 */

@SuppressWarnings( "unchecked" )
public class WsdlLoadTest extends AbstractWsdlModelItem<LoadTestConfig> implements LoadTest, TestRunnable
{
	public final static String THREADCOUNT_PROPERTY = WsdlLoadTest.class.getName() + "@threadcount";
	public final static String STARTDELAY_PROPERTY = WsdlLoadTest.class.getName() + "@startdelay";
	public final static String TESTLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@testlimit";
	public final static String HISTORYLIMIT_PROPERTY = WsdlLoadTest.class.getName() + "@historylimit";
	public final static String LIMITTYPE_PROPERRY = WsdlLoadTest.class.getName() + "@limittype";
	public final static String SAMPLEINTERVAL_PROPERRY = WsdlLoadTest.class.getName() + "@sample-interval";
	public static final String MAXASSERTIONERRORS_PROPERTY = WsdlLoadTest.class.getName() + "@max-assertion-errors";
	public final static String SETUP_SCRIPT_PROPERTY = WsdlTestCase.class.getName() + "@setupScript";
	public final static String TEARDOWN_SCRIPT_PROPERTY = WsdlTestCase.class.getName() + "@tearDownScript";

	private final static Logger logger = Logger.getLogger( WsdlLoadTest.class );
	public static final int DEFAULT_STRATEGY_INTERVAL = 500;

	private InternalTestRunListener internalTestRunListener = new InternalTestRunListener();

	private WsdlTestCase testCase;
	private LoadTestStatistics statisticsModel;
	private LoadStrategy loadStrategy = new BurstLoadStrategy( this );
	private LoadTestLog loadTestLog;

	private LoadStrategyConfigurationChangeListener loadStrategyListener = new LoadStrategyConfigurationChangeListener();
	private List<LoadTestAssertion> assertions = new ArrayList<LoadTestAssertion>();
	private ConfigurationChangePropertyListener configurationChangeListener = new ConfigurationChangePropertyListener();
	private Set<LoadTestListener> loadTestListeners = new HashSet<LoadTestListener>();
	private Set<LoadTestRunListener> loadTestRunListeners = new HashSet<LoadTestRunListener>();
	private List<LoadTestLogErrorEntry> assertionErrors = new TreeList();
	private WsdlLoadTestRunner runner;
	private StatisticsLogger statisticsLogger = new StatisticsLogger();
	private SoapUIScriptEngine setupScriptEngine;
	private SoapUIScriptEngine tearDownScriptEngine;
	@SuppressWarnings( "unused" )
	private SimplePathPropertySupport logFolder;
	private LoadTestRunListener[] loadTestRunListenersArray;

	public WsdlLoadTest( WsdlTestCase testCase, LoadTestConfig config )
	{
		super( config, testCase, "/loadTest.gif" );

		this.testCase = testCase;

		if( getConfig().getThreadCount() < 1 )
			getConfig().setThreadCount( 5 );

		if( getConfig().getLimitType() == null )
		{
			getConfig().setLimitType( LoadTestLimitTypesConfig.TIME );
			getConfig().setTestLimit( 60 );
		}

		if( !getConfig().isSetHistoryLimit() )
		{
			getConfig().setHistoryLimit( -1 );
		}

		addLoadTestRunListener( internalTestRunListener );

		LoadStrategyConfig ls = getConfig().getLoadStrategy();
		if( ls == null )
		{
			ls = getConfig().addNewLoadStrategy();
			ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
		}

		LoadStrategyFactory factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
		if( factory == null )
		{
			ls.setType( SimpleLoadStrategy.STRATEGY_TYPE );
			factory = LoadStrategyRegistry.getInstance().getFactory( ls.getType() );
		}

		loadStrategy = factory.build( ls.getConfig(), this );
		loadStrategy.addConfigurationChangeListener( loadStrategyListener );

		addLoadTestRunListener( loadStrategy );

		statisticsModel = new LoadTestStatistics( this );

		if( getConfig().xgetSampleInterval() == null )
			setSampleInterval( LoadTestStatistics.DEFAULT_SAMPLE_INTERVAL );

		statisticsModel.setUpdateFrequency( getSampleInterval() );

		List<LoadTestAssertionConfig> assertionList = getConfig().getAssertionList();
		for( LoadTestAssertionConfig assertionConfig : assertionList )
		{
			AbstractLoadTestAssertion assertion = LoadTestAssertionRegistry.buildAssertion( assertionConfig, this );
			if( assertion != null )
			{
				assertions.add( assertion );
				assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
			}
			else
			{
				logger.warn( "Failed to build LoadTestAssertion from getConfig() [" + assertionConfig + "]" );
			}
		}

		if( getConfig().xgetResetStatisticsOnThreadCountChange() == null )
			getConfig().setResetStatisticsOnThreadCountChange( true );

		if( getConfig().xgetCalculateTPSOnTimePassed() == null )
			getConfig().setCalculateTPSOnTimePassed( true );

		if( !getConfig().isSetMaxAssertionErrors() )
			getConfig().setMaxAssertionErrors( 100 );

		if( getConfig().xgetCancelExcessiveThreads() == null )
			getConfig().setCancelExcessiveThreads( true );

		if( getConfig().xgetStrategyInterval() == null )
			getConfig().setStrategyInterval( DEFAULT_STRATEGY_INTERVAL );

		loadTestLog = new LoadTestLog( this );

		for( LoadTestRunListener listener : SoapUI.getListenerRegistry().getListeners( LoadTestRunListener.class ) )
		{
			addLoadTestRunListener( listener );
		}

		// set close-connections to same as global so override works ok
		if( !getSettings().isSet( HttpSettings.CLOSE_CONNECTIONS ) )
			getSettings().setBoolean( HttpSettings.CLOSE_CONNECTIONS,
					SoapUI.getSettings().getBoolean( HttpSettings.CLOSE_CONNECTIONS ) );
	}

	public LoadTestStatistics getStatisticsModel()
	{
		return statisticsModel;
	}

	public StatisticsLogger getStatisticsLogger()
	{
		return statisticsLogger;
	}

	public long getThreadCount()
	{
		return getConfig().getThreadCount();
	}

	public void setThreadCount( long threadCount )
	{
		long oldCount = getThreadCount();
		if( threadCount == oldCount )
			return;

		if( getLogStatisticsOnThreadChange() && isRunning() )
			statisticsLogger.logStatistics( "ThreadCount change from " + oldCount + " to " + threadCount );

		getConfig().setThreadCount( ( int )threadCount );
		notifyPropertyChanged( THREADCOUNT_PROPERTY, oldCount, threadCount );
	}

	public boolean getResetStatisticsOnThreadCountChange()
	{
		return getConfig().getResetStatisticsOnThreadCountChange();
	}

	public void setResetStatisticsOnThreadCountChange( boolean value )
	{
		getConfig().setResetStatisticsOnThreadCountChange( value );
	}

	public boolean getCancelOnReachedLimit()
	{
		return getConfig().getCancelOnReachedLimit();
	}

	public void setCancelOnReachedLimit( boolean value )
	{
		getConfig().setCancelOnReachedLimit( value );
	}

	public boolean getCancelExcessiveThreads()
	{
		return getConfig().getCancelExcessiveThreads();
	}

	public void setCancelExcessiveThreads( boolean value )
	{
		getConfig().setCancelExcessiveThreads( value );
	}

	public boolean getLogStatisticsOnThreadChange()
	{
		return getConfig().getLogStatisticsOnThreadChange();
	}

	public void setLogStatisticsOnThreadChange( boolean value )
	{
		getConfig().setLogStatisticsOnThreadChange( value );
	}

	public String getStatisticsLogFolder()
	{
		return getConfig().getStatisticsLogFolder();
	}

	public void setStatisticsLogFolder( String value )
	{
		getConfig().setStatisticsLogFolder( value );
	}

	public boolean getCalculateTPSOnTimePassed()
	{
		return getConfig().getCalculateTPSOnTimePassed();
	}

	public void setCalculateTPSOnTimePassed( boolean value )
	{
		getConfig().setCalculateTPSOnTimePassed( value );
	}

	public int getStartDelay()
	{
		return getConfig().getStartDelay();
	}

	public void setStartDelay( int startDelay )
	{
		if( startDelay < 0 )
			return;

		int oldDelay = getStartDelay();
		getConfig().setStartDelay( startDelay );
		notifyPropertyChanged( STARTDELAY_PROPERTY, oldDelay, startDelay );
	}

	public long getHistoryLimit()
	{
		return getConfig().getHistoryLimit();
	}

	public void setHistoryLimit( long historyLimit )
	{
		long oldLimit = getHistoryLimit();
		getConfig().setHistoryLimit( historyLimit );
		if( historyLimit == 0 )

			notifyPropertyChanged( HISTORYLIMIT_PROPERTY, oldLimit, historyLimit );
	}

	public long getTestLimit()
	{
		return getConfig().getTestLimit();
	}

	public void setTestLimit( long testLimit )
	{
		if( testLimit < 0 )
			return;

		long oldLimit = getTestLimit();
		getConfig().setTestLimit( testLimit );
		notifyPropertyChanged( TESTLIMIT_PROPERTY, oldLimit, testLimit );
	}

	public long getMaxAssertionErrors()
	{
		return getConfig().getMaxAssertionErrors();
	}

	public void setMaxAssertionErrors( long testLimit )
	{
		if( testLimit < 0 )
			return;

		long oldLimit = getMaxAssertionErrors();
		getConfig().setMaxAssertionErrors( testLimit );
		notifyPropertyChanged( MAXASSERTIONERRORS_PROPERTY, oldLimit, testLimit );
	}

	public long getStatisticsLogInterval()
	{
		return getConfig().getStatisticsLogInterval();
	}

	public void setStatisticsLogInterval( int sampleInterval )
	{
		if( sampleInterval < 0 )
			return;

		long oldInterval = getStatisticsLogInterval();
		getConfig().setStatisticsLogInterval( sampleInterval );

		notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );

		if( oldInterval == 0 && sampleInterval > 0 && isRunning() )
			statisticsLogger.start();
	}

	public long getSampleInterval()
	{
		return getConfig().getSampleInterval();
	}

	public void setSampleInterval( int sampleInterval )
	{
		if( sampleInterval < 0 )
			return;

		long oldInterval = getSampleInterval();
		getConfig().setSampleInterval( sampleInterval );

		statisticsModel.setUpdateFrequency( sampleInterval );
		notifyPropertyChanged( SAMPLEINTERVAL_PROPERRY, oldInterval, sampleInterval );
	}

	public Enum getLimitType()
	{
		return getConfig().getLimitType();
	}

	public void setLimitType( Enum limitType )
	{
		if( limitType == null )
			return;

		Enum oldType = getLimitType();
		getConfig().setLimitType( limitType );
		notifyPropertyChanged( LIMITTYPE_PROPERRY, oldType, limitType );
	}

	public WsdlTestCase getTestCase()
	{
		return testCase;
	}

	public synchronized WsdlLoadTestRunner run()
	{
		getStatisticsModel().reset();
		if( runner != null && runner.getStatus() == Status.RUNNING )
			return null;

		if( runner != null )
			runner.release();

		assertionErrors.clear();
		runner = new WsdlLoadTestRunner( this );
		runner.start();
		return runner;
	}

	private class InternalTestRunListener extends LoadTestRunListenerAdapter
	{
		@Override
		public void afterLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
		{
			statisticsLogger.finish();
		}

		@Override
		public void beforeLoadTest( LoadTestRunner loadTestRunner, LoadTestRunContext context )
		{
			statisticsLogger.init( context );

			if( getStatisticsLogInterval() > 0 )
				statisticsLogger.start();
		}

		@Override
		public void afterTestCase( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
				TestCaseRunContext runContext )
		{
			if( !assertions.isEmpty() )
			{
				for( LoadTestAssertion assertion : assertions )
				{
					String error = assertion.assertResults( loadTestRunner, context, testRunner, runContext );
					if( error != null )
					{
						int threadIndex = 0;

						try
						{
							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
						}
						catch( Throwable t )
						{
						}

						loadTestLog.addEntry( new LoadTestLogErrorEntry( assertion.getName(), error, assertion.getIcon(),
								threadIndex ) );
						statisticsModel.addError( LoadTestStatistics.TOTAL );
					}
				}
			}
		}

		@Override
		public void afterTestStep( LoadTestRunner loadTestRunner, LoadTestRunContext context, TestCaseRunner testRunner,
				TestCaseRunContext runContext, TestStepResult result )
		{
			boolean added = false;

			if( !assertions.isEmpty() )
			{
				for( LoadTestAssertion assertion : assertions )
				{
					String error = assertion.assertResult( loadTestRunner, context, result, testRunner, runContext );
					if( error != null )
					{
						int indexOfTestStep = testRunner.getTestCase().getIndexOfTestStep( result.getTestStep() );
						int threadIndex = 0;

						try
						{
							threadIndex = Integer.parseInt( runContext.getProperty( "ThreadIndex" ).toString() );
						}
						catch( Throwable t )
						{
						}

						LoadTestLogErrorEntry errorEntry = new LoadTestLogErrorEntry( assertion.getName(), error, result,
								assertion.getIcon(), threadIndex );

						loadTestLog.addEntry( errorEntry );
						statisticsModel.addError( indexOfTestStep );

						long maxAssertionErrors = getMaxAssertionErrors();
						if( maxAssertionErrors > 0 )
						{
							synchronized( assertionErrors )
							{
								assertionErrors.add( errorEntry );
								while( assertionErrors.size() > maxAssertionErrors )
								{
									assertionErrors.remove( 0 ).discard();
								}
							}
						}

						added = true;
					}
				}
			}

			// discard if set to discard and there were no errors
			if( !added )
			{
				if( getTestCase().getDiscardOkResults() || getTestCase().getMaxResults() == 0 )
				{
					result.discard();
				}
				else if( getTestCase().getMaxResults() > 0 && testRunner instanceof WsdlTestCaseRunner )
				{
					( ( WsdlTestCaseRunner )testRunner ).enforceMaxResults( getTestCase().getMaxResults() );
				}
			}
		}
	}

	public LoadStrategy getLoadStrategy()
	{
		return loadStrategy;
	}

	public void setLoadStrategy( LoadStrategy loadStrategy )
	{
		this.loadStrategy.removeConfigurationChangeListener( loadStrategyListener );
		removeLoadTestRunListener( this.loadStrategy );

		this.loadStrategy = loadStrategy;
		this.loadStrategy.addConfigurationChangeListener( loadStrategyListener );
		addLoadTestRunListener( this.loadStrategy );

		getConfig().getLoadStrategy().setType( loadStrategy.getType() );
		getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
	}

	private class LoadStrategyConfigurationChangeListener implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			getConfig().getLoadStrategy().setConfig( loadStrategy.getConfig() );
		}
	}

	public LoadTestAssertion addAssertion( String type, String targetStep, boolean showConfig )
	{
		LoadTestAssertion assertion = LoadTestAssertionRegistry.createAssertion( type, this );
		assertion.setTargetStep( targetStep );

		if( assertion instanceof Configurable && showConfig )
		{
			if( !( ( Configurable )assertion ).configure() )
				return null;
		}

		assertions.add( assertion );

		getConfig().addNewAssertion().set( assertion.getConfiguration() );
		assertion.addPropertyChangeListener( LoadTestAssertion.CONFIGURATION_PROPERTY, configurationChangeListener );
		fireAssertionAdded( assertion );

		return assertion;
	}

	public void removeAssertion( LoadTestAssertion assertion )
	{
		int ix = assertions.indexOf( assertion );
		if( ix >= 0 )
		{
			try
			{
				assertions.remove( ix );
				fireAssertionRemoved( assertion );
			}
			finally
			{
				assertion.removePropertyChangeListener( configurationChangeListener );
				assertion.release();
				getConfig().removeAssertion( ix );
			}
		}
	}

	private void fireAssertionRemoved( LoadTestAssertion assertion )
	{
		if( !loadTestListeners.isEmpty() )
		{
			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
			for( LoadTestListener listener : l )
			{
				listener.assertionRemoved( assertion );
			}
		}
	}

	private void fireAssertionAdded( LoadTestAssertion assertion )
	{
		if( !loadTestListeners.isEmpty() )
		{
			LoadTestListener[] l = loadTestListeners.toArray( new LoadTestListener[loadTestListeners.size()] );
			for( LoadTestListener listener : l )
			{
				listener.assertionAdded( assertion );
			}
		}
	}

	public int getAssertionCount()
	{
		return assertions.size();
	}

	public LoadTestAssertion getAssertionAt( int index )
	{
		return index < 0 || index >= assertions.size() ? null : assertions.get( index );
	}

	public LoadTestAssertion getAssertionByName( String name )
	{
		for( LoadTestAssertion assertion : assertions )
		{
			if( assertion.getName().equals( name ) )
			{
				return assertion;
			}
		}
		return null;
	}

	private class ConfigurationChangePropertyListener implements PropertyChangeListener
	{
		public void propertyChange( PropertyChangeEvent evt )
		{
			int ix = assertions.indexOf( evt.getSource() );
			if( ix >= 0 )
			{
				getConfig().getAssertionArray( ix ).set( assertions.get( ix ).getConfiguration() );
			}
		}
	}

	public LoadTestLog getLoadTestLog()
	{
		return loadTestLog;
	}

	public List<LoadTestAssertion> getAssertionList()
	{
		return assertions;
	}

	public void addLoadTestListener( LoadTestListener listener )
	{
		loadTestListeners.add( listener );
	}

	public void removeLoadTestListener( LoadTestListener listener )
	{
		loadTestListeners.remove( listener );
	}

	public void addLoadTestRunListener( LoadTestRunListener listener )
	{
		loadTestRunListeners.add( listener );
		loadTestRunListenersArray = null;
	}

	public void removeLoadTestRunListener( LoadTestRunListener listener )
	{
		loadTestRunListeners.remove( listener );
		loadTestRunListenersArray = null;
	}

	public LoadTestRunListener[] getLoadTestRunListeners()
	{
		if( loadTestRunListenersArray == null )
		{
			loadTestRunListenersArray = loadTestRunListeners
					.toArray( new LoadTestRunListener[loadTestRunListeners.size()] );
		}

		return loadTestRunListenersArray;
	}

	/**
	 * Release internal objects so they can remove listeners
	 */

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

		statisticsModel.release();
		loadTestLog.release();

		for( LoadTestAssertion assertion : assertions )
			assertion.release();

		loadTestRunListeners.clear();
		loadTestListeners.clear();
	}

	public boolean isRunning()
	{
		return runner != null && runner.getStatus() == LoadTestRunner.Status.RUNNING;
	}

	public WsdlLoadTestRunner getRunner()
	{
		return runner;
	}

	public void resetConfigOnMove( LoadTestConfig config )
	{
		setConfig( config );

		loadStrategy.updateConfig( config.getLoadStrategy().getConfig() );

		List<LoadTestAssertionConfig> assertionList = config.getAssertionList();
		for( int c = 0; c < assertionList.size(); c++ )
		{
			assertions.get( c ).updateConfiguration( assertionList.get( c ) );
		}
	}

	public class StatisticsLogger implements Runnable
	{
		private boolean stopped;
		private List<PrintWriter> writers = new ArrayList<PrintWriter>();
		private long startTime;

		public void run()
		{
			stopped = false;

			while( !stopped && getStatisticsLogInterval() > 0 )
			{
				try
				{
					long statisticsInterval = getStatisticsLogInterval();
					Thread.sleep( statisticsInterval );
					if( !stopped )
					{
						logStatistics( "Interval" );
					}
				}
				catch( InterruptedException e )
				{
					e.printStackTrace();
				}
			}
		}

		public void start()
		{
			new Thread( this, "Statistics Logger for LoadTest [" + getName() + "]" ).start();
		}

		public void init( LoadTestRunContext context )
		{
			writers.clear();

			String statisticsLogFolder = context.expand( getStatisticsLogFolder() );
			if( StringUtils.isNullOrEmpty( statisticsLogFolder ) )
				return;

			File folder = new File( statisticsLogFolder );
			if( !folder.exists() )
			{
				if( !folder.mkdirs() )
				{
					SoapUI.logError( new Exception( "Failed to create statistics log folder [" + statisticsLogFolder + "]" ) );
					return;
				}
			}

			for( int c = 0; c < testCase.getTestStepCount(); c++ )
			{
				try
				{
					WsdlTestStep testStep = testCase.getTestStepAt( c );
					String fileName = StringUtils.createFileName( testStep.getName(), '_' ) + ".log";
					PrintWriter writer = new PrintWriter( new File( folder, fileName ) );
					writers.add( writer );
					addHeaders( writer );
				}
				catch( FileNotFoundException e )
				{
					e.printStackTrace();
					writers.add( null );
				}
			}

			// and one writer for the testcase..
			try
			{
				String fileName = StringUtils.createFileName( testCase.getName(), '_' ) + ".log";
				writers.add( new PrintWriter( new File( folder, fileName ) ) );
			}
			catch( FileNotFoundException e )
			{
				e.printStackTrace();
			}

			startTime = System.nanoTime();
		}

		private void addHeaders( PrintWriter writer )
		{
			writer.print( "date,threads,elapsed,min,max,avg,last,cnt,tps,bytes,bps,err,reason\n" );
		}

		public void finish()
		{
			stopped = true;

			logStatistics( "Finished" );
			for( PrintWriter writer : writers )
			{
				if( writer != null )
					writer.close();
			}
		}

		private synchronized void logStatistics( String trigger )
		{
			if( writers.isEmpty() )
				return;

			long timestamp = System.nanoTime();
			String elapsedString = String.valueOf( ( timestamp - startTime ) / 1000000 );
			String dateString = new Date().toString();
			String threadCountString = String.valueOf( getThreadCount() );

			StringList[] snapshot = statisticsModel.getSnapshot();
			for( int c = 0; c < snapshot.length; c++ )
			{
				PrintWriter writer = writers.get( c );
				if( writer == null )
					continue;

				StringList values = snapshot[c];
				writer.append( dateString ).append( ',' );
				writer.append( threadCountString ).append( ',' );
				writer.append( elapsedString );

				for( String value : values )
				{
					writer.append( ',' ).append( value );
				}

				writer.append( ',' ).append( trigger ).append( '\n' );
				writer.flush();
			}
		}
	}

	public void setSetupScript( String script )
	{
		String oldScript = getSetupScript();

		if( !getConfig().isSetSetupScript() )
			getConfig().addNewSetupScript();

		getConfig().getSetupScript().setStringValue( script );
		if( setupScriptEngine != null )
			setupScriptEngine.setScript( script );

		notifyPropertyChanged( SETUP_SCRIPT_PROPERTY, oldScript, script );
	}

	public String getSetupScript()
	{
		return getConfig().isSetSetupScript() ? getConfig().getSetupScript().getStringValue() : null;
	}

	public void setTearDownScript( String script )
	{
		String oldScript = getTearDownScript();

		if( !getConfig().isSetTearDownScript() )
			getConfig().addNewTearDownScript();

		getConfig().getTearDownScript().setStringValue( script );
		if( tearDownScriptEngine != null )
			tearDownScriptEngine.setScript( script );

		notifyPropertyChanged( TEARDOWN_SCRIPT_PROPERTY, oldScript, script );
	}

	public String getTearDownScript()
	{
		return getConfig().isSetTearDownScript() ? getConfig().getTearDownScript().getStringValue() : null;
	}

	public Object runSetupScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
	{
		String script = getSetupScript();
		if( StringUtils.isNullOrEmpty( script ) )
			return null;

		if( setupScriptEngine == null )
		{
			setupScriptEngine = SoapUIScriptEngineRegistry.create( this );
			setupScriptEngine.setScript( script );
		}

		setupScriptEngine.setVariable( "context", runContext );
		setupScriptEngine.setVariable( "loadTestRunner", runner );
		setupScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
		return setupScriptEngine.run();
	}

	public Object runTearDownScript( LoadTestRunContext runContext, LoadTestRunner runner ) throws Exception
	{
		String script = getTearDownScript();
		if( StringUtils.isNullOrEmpty( script ) )
			return null;

		if( tearDownScriptEngine == null )
		{
			tearDownScriptEngine = SoapUIScriptEngineRegistry.create( this );
			tearDownScriptEngine.setScript( script );
		}

		tearDownScriptEngine.setVariable( "context", runContext );
		tearDownScriptEngine.setVariable( "loadTestRunner", runner );
		tearDownScriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
		return tearDownScriptEngine.run();
	}

	public int getStrategyInterval()
	{
		return getConfig().getStrategyInterval();
	}

	public void setStrategyInterval( int interval )
	{
		getConfig().setStrategyInterval( interval );
	}

	public boolean getUpdateStatisticsPerTestStep()
	{
		return getConfig().getUpdateStatisticsPerTestStep();
	}

	public void setUpdateStatisticsPerTestStep( boolean updateStatisticsPerTestStep )
	{
		getConfig().setUpdateStatisticsPerTestStep( updateStatisticsPerTestStep );
	}

	public TestRunner run( StringToObjectMap context, boolean async )
	{
		// TODO Auto-generated method stub
		return null;
	}
}
