/*
 *  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.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;

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

import com.eviware.soapui.config.GotoConditionConfig;
import com.eviware.soapui.config.GotoConditionTypeConfig;
import com.eviware.soapui.config.GotoStepConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.support.http.HttpRequestTestStep;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContainer;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.XPathReference;
import com.eviware.soapui.model.support.XPathReferenceContainer;
import com.eviware.soapui.model.support.XPathReferenceImpl;
import com.eviware.soapui.model.testsuite.SamplerTestStep;
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.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.xml.XmlUtils;

/**
 * TestStep that moves execution to another step based on the contents of a XML
 * Property
 * 
 * @author ole.matzura
 */

public class WsdlGotoTestStep extends WsdlTestStepWithProperties implements XPathReferenceContainer,
		PropertyExpansionContainer
{
	private GotoStepConfig gotoStepConfig;
	private List<GotoCondition> conditions = new ArrayList<GotoCondition>();
	private boolean canceled;

	private final static Logger log = Logger.getLogger( WsdlGotoTestStep.class );

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

		if( !forLoadTest )
		{
			setIcon( UISupport.createImageIcon( "/goto.gif" ) );
		}
	}

	@Override
	public void afterLoad()
	{
		TestStepConfig config = getConfig();

		if( config.getConfig() == null )
		{
			gotoStepConfig = ( GotoStepConfig )config.addNewConfig().changeType( GotoStepConfig.type );
		}
		else
		{
			gotoStepConfig = ( GotoStepConfig )config.getConfig().changeType( GotoStepConfig.type );
			for( int c = 0; c < gotoStepConfig.sizeOfConditionArray(); c++ )
			{
				conditions.add( new GotoCondition( gotoStepConfig.getConditionArray( c ) ) );
			}
		}

		super.afterLoad();
	}

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

		gotoStepConfig = ( GotoStepConfig )config.getConfig().changeType( GotoStepConfig.type );
		for( int c = 0; c < gotoStepConfig.sizeOfConditionArray(); c++ )
		{
			conditions.get( c ).setConfig( gotoStepConfig.getConditionArray( c ) );
		}
	}

	public TestStepResult run( TestCaseRunner runner, TestCaseRunContext context )
	{
		WsdlTestStepResult result = new WsdlTestStepResult( this );
		canceled = false;

		result.startTimer();

		SamplerTestStep previousStep = getTestCase().findPreviousStepOfType( this, SamplerTestStep.class );

		if( previousStep == null )
		{
			result.stopTimer();
			result.addMessage( "Failed to find previous request step from [" + getName() + "]" );
			result.setStatus( TestStepStatus.FAILED );
			return result;
		}

		GotoCondition target = runConditions( previousStep, context );
		if( target == null )
		{
			result.addMessage( "Missing matching condition, moving on." );
		}
		else
		{
			String targetStepName = target.getTargetStep().trim();
			result.addMessage( "Matched condition [" + targetStepName + "], transferring to [" + targetStepName + "]" );
			runner.gotoStep( runner.getTestCase().getTestStepIndexByName( targetStepName ) );
		}

		result.stopTimer();
		result.setStatus( TestStepStatus.OK );
		return result;
	}

	public GotoCondition runConditions( SamplerTestStep previousStep, TestCaseRunContext context )
	{
		for( GotoCondition condition : conditions )
		{
			if( canceled )
				break;

			try
			{
				if( condition.evaluate( previousStep, context ) )
				{
					return condition;
				}
			}
			catch( Exception e )
			{
				log.error( "Error making condition " + condition.getName() + "; " + e );
			}
		}

		return null;
	}

	public boolean cancel()
	{
		canceled = true;
		return canceled;
	}

	public int getConditionCount()
	{
		return conditions.size();
	}

	public GotoCondition getConditionAt( int index )
	{
		return conditions.get( index );
	}

	public GotoCondition addCondition( String name )
	{
		GotoCondition condition = new GotoCondition( gotoStepConfig.addNewCondition() );
		condition.setName( name );
		condition.setType( GotoConditionTypeConfig.XPATH.toString() );
		conditions.add( condition );
		return condition;
	}

	public void removeConditionAt( int index )
	{
		conditions.remove( index );
		gotoStepConfig.removeCondition( index );
	}

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

		for( GotoCondition condition : conditions )
		{
			condition.release();
		}
	}

	public class GotoCondition implements PropertyChangeListener
	{
		public final static String TARGET_STEP_PROPERTY = "target_step";

		private GotoConditionConfig conditionConfig;
		private TestStep currentStep;
		private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport( this );

		public GotoCondition( GotoConditionConfig conditionConfig )
		{
			this.conditionConfig = conditionConfig;
			initListeners();
		}

		public void addPropertyChangeListener( String propertyName, PropertyChangeListener listener )
		{
			propertyChangeSupport.addPropertyChangeListener( propertyName, listener );
		}

		public void addPropertyChangeListener( PropertyChangeListener listener )
		{
			propertyChangeSupport.addPropertyChangeListener( listener );
		}

		public void removePropertyChangeListener( String propertyName, PropertyChangeListener listener )
		{
			propertyChangeSupport.removePropertyChangeListener( propertyName, listener );
		}

		public void removePropertyChangeListener( PropertyChangeListener listener )
		{
			propertyChangeSupport.removePropertyChangeListener( listener );
		}

		private void initListeners()
		{
			release();

			if( getTargetStep() != null )
			{
				int index = getTestCase().getTestStepIndexByName( getTargetStep() );
				if( index != -1 )
				{
					currentStep = getTestCase().getTestStepAt( index );
					currentStep.addPropertyChangeListener( TestStep.NAME_PROPERTY, this );
				}
			}
		}

		public void release()
		{
			if( currentStep != null )
				currentStep.removePropertyChangeListener( this );
		}

		public boolean evaluate( SamplerTestStep previousStep, TestCaseRunContext context ) throws Exception
		{
			if( getExpression() == null || getExpression().trim().length() == 0 )
				throw new Exception( "Missing expression in condition [" + getName() + "]" );

			if( getTargetStep() == null || getTargetStep().trim().length() == 0 )
				throw new Exception( "Missing target step in condition [" + getName() + "]" );

			if( getType().equals( GotoConditionTypeConfig.XPATH.toString() ) )
			{
				TestRequest testRequest = previousStep.getTestRequest();
				// XmlObject xmlObject = XmlObject.Factory.parse(
				// testRequest.getResponse().getContentAsXml() );
				XmlObject xmlObject = XmlUtils.createXmlObject( testRequest.getResponse().getContentAsXml() );

				String expression = PropertyExpander.expandProperties( context, getExpression() );
				XmlObject[] selectPath = xmlObject.selectPath( expression );
				if( selectPath.length == 1 && selectPath[0] instanceof XmlBoolean )
				{
					if( ( ( XmlBoolean )selectPath[0] ).getBooleanValue() )
					{
						return true;
					}
				}
			}
			else
			{
				log.error( "Unkown condition type: " + getType() );
			}

			return false;
		}

		protected void setConfig( GotoConditionConfig conditionConfig )
		{
			this.conditionConfig = conditionConfig;
		}

		public String getType()
		{
			return conditionConfig.getType();
		}

		public String getName()
		{
			return conditionConfig.getName();
		}

		public String getExpression()
		{
			return conditionConfig.getExpression();
		}

		public String getTargetStep()
		{
			return conditionConfig.getTargetStep();
		}

		public void setType( String type )
		{
			conditionConfig.setType( type );
		}

		public void setName( String name )
		{
			conditionConfig.setName( name );
		}

		public void setExpression( String expression )
		{
			conditionConfig.setExpression( expression );
		}

		public void setTargetStep( String targetStep )
		{
			String oldStep = getTargetStep();
			conditionConfig.setTargetStep( targetStep );
			initListeners();
			propertyChangeSupport.firePropertyChange( TARGET_STEP_PROPERTY, oldStep, targetStep );
		}

		public void propertyChange( PropertyChangeEvent evt )
		{
			conditionConfig.setTargetStep( evt.getNewValue().toString() );
			propertyChangeSupport.firePropertyChange( TARGET_STEP_PROPERTY, evt.getOldValue(), evt.getNewValue() );
		}

		public TestProperty getSourceProperty()
		{
			HttpRequestTestStep previousStep = ( HttpRequestTestStep )getTestCase().findPreviousStepOfType(
					WsdlGotoTestStep.this, HttpRequestTestStep.class );
			return previousStep == null ? null : previousStep.getProperty( "Response" );
		}
	}

	public boolean hasProperties()
	{
		return false;
	}

	public PropertyExpansion[] getPropertyExpansions()
	{
		List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();

		for( GotoCondition condition : conditions )
		{
			result.addAll( PropertyExpansionUtils.extractPropertyExpansions( this, condition, "expression" ) );
		}

		return result.toArray( new PropertyExpansion[result.size()] );
	}

	public XPathReference[] getXPathReferences()
	{
		List<XPathReference> result = new ArrayList<XPathReference>();

		for( GotoCondition condition : conditions )
		{
			if( StringUtils.hasContent( condition.getExpression() ) )
				result.add( new XPathReferenceImpl( "Condition for " + condition.getName() + " GotoCondition in "
						+ getName(), condition.getSourceProperty(), condition, "expression" ) );
		}

		return result.toArray( new XPathReference[result.size()] );
	}
}
