/*
 *  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.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

import javax.swing.ImageIcon;

import com.eviware.soapui.config.PropertiesStepConfig;
import com.eviware.soapui.config.TestStepConfig;
import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.support.ExternalDependency;
import com.eviware.soapui.impl.wsdl.support.PathPropertyExternalDependency;
import com.eviware.soapui.impl.wsdl.support.XmlBeansPropertiesTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
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.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.ResolveContext;
import com.eviware.soapui.support.types.StringList;

/**
 * TestStep that holds an arbitrary number of custom properties
 * 
 * @author ole.matzura
 */

public class WsdlPropertiesTestStep extends WsdlTestStep implements MutableTestPropertyHolder
{
	private PropertiesStepConfig propertiesStepConfig;
	private ImageIcon okIcon;
	private ImageIcon failedIcon;
	private XmlBeansPropertiesTestPropertyHolder propertyHolderSupport;
	private BeanPathPropertySupport sourceProperty;
	private BeanPathPropertySupport targetProperty;

	public static final String SOURCE_PROPERTY = WsdlPropertiesTestStep.class.getName() + "@source";
	public static final String TARGET_PROPERTY = WsdlPropertiesTestStep.class.getName() + "@target";

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

		if( !forLoadTest )
		{
			okIcon = UISupport.createImageIcon( "/properties_step.gif" );
			failedIcon = UISupport.createImageIcon( "/properties_step_failed.gif" );

			setIcon( okIcon );
		}

		if( config.getConfig() == null )
		{
			propertiesStepConfig = ( PropertiesStepConfig )config.addNewConfig().changeType( PropertiesStepConfig.type );
			propertiesStepConfig.addNewProperties();
			propertiesStepConfig.setCreateMissingOnLoad( true );
		}
		else
		{
			propertiesStepConfig = ( PropertiesStepConfig )config.getConfig().changeType( PropertiesStepConfig.type );
			if( !propertiesStepConfig.isSetProperties() )
			{
				propertiesStepConfig.addNewProperties();
			}

			if( !propertiesStepConfig.isSetSaveFirst() )
				propertiesStepConfig.setSaveFirst( true );
		}

		propertyHolderSupport = new XmlBeansPropertiesTestPropertyHolder( this, propertiesStepConfig.getProperties() );
		sourceProperty = new BeanPathPropertySupport( this, propertiesStepConfig, "source" );
		targetProperty = new BeanPathPropertySupport( this, propertiesStepConfig, "target" );
	}

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

		if( okIcon != null )
			setIcon( okIcon );

		result.setStatus( TestStepStatus.OK );
		result.startTimer();

		if( isSaveFirst() )
			saveDuringRun( result, testRunContext );

		String source = sourceProperty.expand( testRunContext );
		if( StringUtils.hasContent( source ) )
		{
			try
			{
				int cnt = loadProperties( source, isCreateMissingOnLoad() );

				result.setStatus( TestStepStatus.OK );
				result.addMessage( "Loaded " + cnt + " properties from [" + source + "]" );
			}
			catch( IOException e )
			{
				result.stopTimer();
				result.addMessage( "Failed to load properties from [" + source + "]" );
				result.setStatus( TestStepStatus.FAILED );
				result.setError( e );

				if( failedIcon != null )
					setIcon( failedIcon );
			}
		}

		if( !isSaveFirst() )
			saveDuringRun( result, testRunContext );

		result.stopTimer();

		return result;
	}

	private boolean saveDuringRun( WsdlTestStepResult result, TestCaseRunContext context )
	{
		String target = targetProperty.expand( context );
		if( StringUtils.hasContent( target ) )
		{
			try
			{
				int cnt = saveProperties( target );

				result.setStatus( TestStepStatus.OK );
				result.addMessage( "Saved " + cnt + " properties to [" + target + "]" );
			}
			catch( IOException e )
			{
				result.stopTimer();
				result.addMessage( "Failed to save properties to [" + target + "]" );
				result.setStatus( TestStepStatus.FAILED );
				result.setError( e );

				if( failedIcon != null )
					setIcon( failedIcon );

				return false;
			}
		}

		return true;
	}

	private int saveProperties( String target ) throws IOException
	{
		return propertyHolderSupport.saveTo( System.getProperty( target, target ) );
	}

	private int loadProperties( String source, boolean createMissing ) throws IOException
	{
		// override methods so propertynames are returned in readorder
		java.util.Properties props = new java.util.Properties()
		{
			public StringList names = new StringList();

			@Override
			public synchronized Object put( Object key, Object value )
			{
				names.add( key.toString() );
				return super.put( key, value );
			}

			@Override
			public Enumeration<?> propertyNames()
			{
				return Collections.enumeration( names );
			}
		};

		InputStream in = getPropertiesInputStream( source );
		props.load( in );
		in.close();

		int cnt = 0;
		Enumeration<?> names = props.propertyNames();
		while( names.hasMoreElements() )
		{
			String name = names.nextElement().toString();
			TestProperty property = getProperty( name );
			if( property != null )
			{
				property.setValue( props.get( name ).toString() );
				cnt++ ;
			}
			else if( createMissing )
			{
				addProperty( name ).setValue( props.get( name ).toString() );
				cnt++ ;
			}
		}

		return cnt;
	}

	private InputStream getPropertiesInputStream( String source ) throws IOException
	{
		String fileProperty = System.getProperty( source );
		if( fileProperty != null )
			source = fileProperty;

		URL url;

		try
		{
			url = new URL( source );
		}
		catch( MalformedURLException e )
		{
			url = new URL( "file:" + source );
		}

		return url.openStream();
	}

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

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

	public String getSource()
	{
		return sourceProperty.get();
	}

	public void setSource( String source )
	{
		sourceProperty.set( source, true );
	}

	public String getTarget()
	{
		return targetProperty.get();
	}

	public String getLabel()
	{
		String str = super.getName() + " (" + getPropertyCount() + ")";

		if( isDisabled() )
			str += " (disabled)";

		return str;
	}

	public void setTarget( String target )
	{
		targetProperty.set( target, true );
	}

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

		propertiesStepConfig = ( PropertiesStepConfig )config.getConfig().changeType( PropertiesStepConfig.type );
		propertyHolderSupport.resetPropertiesConfig( propertiesStepConfig.getProperties() );
		sourceProperty.setConfig( propertiesStepConfig );
		targetProperty.setConfig( propertiesStepConfig );
	}

	public int loadProperties( boolean createMissing ) throws IOException
	{
		return loadProperties( sourceProperty.expand(), createMissing );
	}

	public int saveProperties() throws IOException
	{
		String target = PropertyExpander.expandProperties( this, targetProperty.expand() );
		return saveProperties( target );
	}

	public boolean isCreateMissingOnLoad()
	{
		return propertiesStepConfig.getCreateMissingOnLoad();
	}

	public void setCreateMissingOnLoad( boolean b )
	{
		propertiesStepConfig.setCreateMissingOnLoad( b );
	}

	public boolean isSaveFirst()
	{
		return propertiesStepConfig.getSaveFirst();
	}

	public void setSaveFirst( boolean b )
	{
		propertiesStepConfig.setSaveFirst( b );
	}

	public boolean isDiscardValuesOnSave()
	{
		return propertiesStepConfig.getDiscardValuesOnSave();
	}

	public void setDiscardValuesOnSave( boolean b )
	{
		propertiesStepConfig.setDiscardValuesOnSave( b );
	}

	public void setPropertyValue( String name, String value )
	{
		if( isCreateMissingOnLoad() && getProperty( name ) == null )
			addProperty( name );

		propertyHolderSupport.setPropertyValue( name, value );
	}

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

		if( isDiscardValuesOnSave() )
		{
			clearPropertyValues();
		}
	}

	public void clearPropertyValues()
	{
		for( TestProperty property : propertyHolderSupport.getProperties().values() )
			property.setValue( null );
	}

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

	public TestProperty addProperty( String name )
	{
		String oldLabel = getLabel();

		TestProperty property = propertyHolderSupport.addProperty( name );
		notifyPropertyChanged( WsdlTestStep.LABEL_PROPERTY, oldLabel, getLabel() );

		return property;
	}

	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 List<TestProperty> getPropertyList()
	{
		return propertyHolderSupport.getPropertyList();
	}

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

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

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

	public TestProperty removeProperty( String propertyName )
	{
		String oldLabel = getLabel();

		TestProperty result = propertyHolderSupport.removeProperty( propertyName );
		notifyPropertyChanged( WsdlTestStep.LABEL_PROPERTY, oldLabel, getLabel() );
		return result;
	}

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

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

	public void addExternalDependency( List<ExternalDependency> dependencies )
	{
		super.addExternalDependencies( dependencies );
		dependencies.add( new PathPropertyExternalDependency( targetProperty ) );
		dependencies.add( new PathPropertyExternalDependency( sourceProperty ) );
	}

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

		targetProperty.resolveFile( context, "Missing target property file", "properties",
				"Properties Files (*.properties)", true );
		sourceProperty.resolveFile( context, "Missing source property file", "properties",
				"Properties Files (*.properties)", true );
	}

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

	public String getSource( boolean expand )
	{
		return expand ? sourceProperty.expand() : getSource();
	}

	public String getTarget( boolean expand )
	{
		return expand ? targetProperty.expand() : getTarget();
	}
}
