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

import javax.swing.ImageIcon;
import javax.xml.namespace.QName;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.JdbcRequestTestStepConfig;
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.panels.teststeps.JdbcRequest;
import com.eviware.soapui.impl.wsdl.panels.teststeps.JdbcResponse;
import com.eviware.soapui.impl.wsdl.panels.teststeps.JdbcSubmit;
import com.eviware.soapui.impl.wsdl.support.JdbcMessageExchange;
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.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.StringUtils;

/**
 * WsdlTestStep that executes a WsdlTestRequest
 * 
 * @author dragica.soldo
 */

public class JdbcRequestTestStep extends WsdlTestStepWithProperties implements Assertable, MutableTestPropertyHolder,
		PropertyChangeListener, SamplerTestStep
{
	@SuppressWarnings( "unused" )
	private final static Logger log = Logger.getLogger( WsdlTestRequestStep.class );

	public final static String JDBCREQUEST = JdbcRequestTestStep.class.getName() + "@jdbcrequest";
	public static final String STATUS_PROPERTY = WsdlTestRequest.class.getName() + "@status";
	public static final String RESPONSE_PROPERTY = "response";
	protected static final String DRIVER_FIELD = "Driver";
	protected static final String CONNSTR_FIELD = "Connection String";
	protected static final String PASS_FIELD = "Password";
	public static final String PASS_TEMPLATE = "PASS_VALUE";
	public static final String QUERY_FIELD = "SQL Query";
	protected static final String STOREDPROCEDURE_FIELD = "Stored Procedure";
	protected static final String DATA_CONNECTION_FIELD = "Connection";

	protected static final String QUERY_ELEMENT = "query";
	protected static final String STOREDPROCEDURE_ELEMENT = "stored-procedure";

	private AssertionsSupport assertionsSupport;
	private PropertyChangeNotifier notifier;
	private XmlBeansPropertiesTestPropertyHolder propertyHolderSupport;
	private JdbcRequestTestStepConfig jdbcRequestTestStepConfig;
	private JdbcRequest jdbcRequest;
	private JdbcSubmit submit;

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

		if( getConfig().getConfig() != null )
		{
			jdbcRequestTestStepConfig = ( JdbcRequestTestStepConfig )getConfig().getConfig().changeType(
					JdbcRequestTestStepConfig.type );
		}
		else
		{
			jdbcRequestTestStepConfig = ( JdbcRequestTestStepConfig )getConfig().addNewConfig().changeType(
					JdbcRequestTestStepConfig.type );
		}

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

		jdbcRequest = new JdbcRequest( this, forLoadTest );

		propertyHolderSupport = new XmlBeansPropertiesTestPropertyHolder( this, jdbcRequestTestStepConfig.getProperties() );

		addResponseAsXmlVirtualProperty();

		initAssertions();
	}

	private void addResponseAsXmlVirtualProperty()
	{
		TestStepBeanProperty responseProperty = new TestStepBeanProperty( WsdlTestStepWithProperties.RESPONSE_AS_XML,
				true, this, "responseContent", this )
		{
			@Override
			public String getDefaultValue()
			{
				return "</no-response>";
			}

			@Override
			public QName getType()
			{
				return getSchemaType().getName();
			}

			@Override
			public SchemaType getSchemaType()
			{
				return XmlAnyTypeImpl.type;
			}
		};

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

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

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

	public JdbcRequestTestStepConfig getJdbcRequestTestStepConfig()
	{
		return jdbcRequestTestStepConfig;
	}

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

		jdbcRequestTestStepConfig = ( JdbcRequestTestStepConfig )config.getConfig().changeType(
				JdbcRequestTestStepConfig.type );
		propertyHolderSupport.resetPropertiesConfig( jdbcRequestTestStepConfig.getProperties() );
		// addResponseAsXmlVirtualProperty();
		assertionsSupport.refresh();
	}

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

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

		return result;
	}

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

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

		try
		{
			submit = jdbcRequest.submit( runContext, false );
			JdbcResponse response = submit.getResponse();

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

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

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

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

					switch( jdbcRequest.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( jdbcRequest.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 = jdbcRequest.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 ) )
			jdbcRequest.setResponse( null );

		// FIXME This should not be hard coded
		// FIXME This should not fire if the response is the same. Could we implement a property changed event handler instead?
		firePropertyValueChanged( "ResponseAsXml", null, testStepResult.getResponseContentAsXml() );
		return testStepResult;
	}

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

		submit.cancel();

		return true;
	}

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

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

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

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

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

			public TestAssertionConfig insertAssertion( TestAssertionConfig source, int ix )
			{
				TestAssertionConfig conf = getJdbcRequestTestStepConfig().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( getJdbcRequest().getResponse() != null )
			{
				assertion.assertResponse( new JdbcMessageExchange( this, getJdbcRequest().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()
	{
		TestProperty property = getProperty( "ResponseAsXml" );
		String value = property.getValue();
		return StringUtils.hasContent( value ) ? value : property.getDefaultValue();
	}

	public String getResponseContent()
	{
		return getJdbcRequest().getResponse() == null ? "" : getJdbcRequest().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( getJdbcRequest().getResponse() != null )
			{
				assertResponse( new WsdlTestRunContext( this ) );
			}
		}
	}

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

	public String getDefaultAssertableContent()
	{
		return null;
	}

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

	@Override
	public ImageIcon getIcon()
	{
		return jdbcRequest.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();

			JdbcMessageExchange messageExchange = new JdbcMessageExchange( this, getJdbcRequest().getResponse() );

			if( getJdbcRequest().getResponse() != 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 void removeAllProperties()
	{
		for( String propertyName : propertyHolderSupport.getPropertyNames() )
		{
			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 );
	//	}

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

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

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

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

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

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

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

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

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

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

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

	public String getDriver()
	{
		return jdbcRequestTestStepConfig.getDriver();
	}

	public void setDriver( String d )
	{
		String old = getDriver();
		jdbcRequestTestStepConfig.setDriver( d );
		notifyPropertyChanged( "driver", old, d );
	}

	public String getConnectionString()
	{
		return jdbcRequestTestStepConfig.getConnectionString();
	}

	public void setConnectionString( String c )
	{
		String old = getConnectionString();
		jdbcRequestTestStepConfig.setConnectionString( c );
		notifyPropertyChanged( "connectionString", old, c );
	}

	public String getQuery()
	{
		return jdbcRequestTestStepConfig.getQuery();
	}

	public void setQuery( String q )
	{
		String old = getQuery();
		jdbcRequestTestStepConfig.setQuery( q );
		notifyPropertyChanged( "query", old, q );
	}

	public String getPassword()
	{
		return jdbcRequestTestStepConfig.getPassword();
	}

	public void setPassword( String p )
	{
		String old = getPassword();
		jdbcRequestTestStepConfig.setPassword( p );
		notifyPropertyChanged( "password", old, p );
	}

	public static boolean isNeededPassword( String connStr )
	{
		return !StringUtils.isNullOrEmpty( connStr ) ? connStr.contains( PASS_TEMPLATE ) : false;
	}

	public boolean isStoredProcedure()
	{
		return jdbcRequestTestStepConfig.getStoredProcedure();
	}

	public void setStoredProcedure( boolean sp )
	{
		String old = getPassword();
		jdbcRequestTestStepConfig.setStoredProcedure( sp );
		notifyPropertyChanged( "password", old, sp );
	}

	public JdbcRequest getJdbcRequest()
	{
		return jdbcRequest;
	}

	public String getQueryTimeout()
	{
		return jdbcRequestTestStepConfig.getQueryTimeout();
	}

	public String getMaxRows()
	{
		return jdbcRequestTestStepConfig.getMaxRows();
	}

	public String getFetchSize()
	{
		return jdbcRequestTestStepConfig.getFetchSize();
	}

	public void setQueryTimeout( String queryTimeout )
	{
		String old = getQueryTimeout();
		jdbcRequestTestStepConfig.setQueryTimeout( queryTimeout );
		notifyPropertyChanged( "queryTimeout", old, queryTimeout );
	}

	public void setMaxRows( String maxRows )
	{
		String old = getMaxRows();
		jdbcRequestTestStepConfig.setMaxRows( maxRows );
		notifyPropertyChanged( "maxRows", old, maxRows );
	}

	public void setFetchSize( String fetchSize )
	{
		String old = getFetchSize();
		jdbcRequestTestStepConfig.setFetchSize( fetchSize );
		notifyPropertyChanged( "fetchSize", old, fetchSize );
	}

	public void setResponse( JdbcResponse response, SubmitContext context )
	{
		JdbcResponse oldResponse = jdbcRequest.getResponse();
		jdbcRequest.setResponse( response );

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

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

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

	public TestRequest getTestRequest()
	{
		return jdbcRequest;
	}

	public TestStep getTestStep()
	{
		return this;
	}

	@Override
	public void prepare( TestCaseRunner testRunner, TestCaseRunContext testRunContext ) throws Exception
	{
		super.prepare( testRunner, testRunContext );

		setResponse( null, testRunContext );

		for( TestAssertion assertion : jdbcRequest.getAssertionList() )
		{
			assertion.prepare( testRunner, testRunContext );
		}
	}
}
