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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.AbstractHttpRequest;
import com.eviware.soapui.impl.support.AbstractHttpRequestInterface;
import com.eviware.soapui.impl.wsdl.MutableTestPropertyHolder;
import com.eviware.soapui.impl.wsdl.WsdlInterface;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.WsdlTestSuite;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockOperation;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockResponse;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockService;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStep;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.TestPropertyHolder;
import com.eviware.soapui.model.mock.MockOperation;
import com.eviware.soapui.model.mock.MockResponse;
import com.eviware.soapui.model.mock.MockService;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.support.SettingsTestPropertyHolder;
import com.eviware.soapui.model.testsuite.RenameableTestProperty;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestSuite;
import com.eviware.soapui.security.SecurityTest;
import com.eviware.soapui.settings.GlobalPropertySettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.types.StringToObjectMap;

public class PropertyExpansionUtils
{
	public final static Logger log = Logger.getLogger( PropertyExpansionUtils.class );

	private static SettingsTestPropertyHolder globalTestPropertyHolder;

	public static String getGlobalProperty( String propertyName )
	{
		if( globalTestPropertyHolder == null )
		{
			initGlobalProperties();
		}

		return globalTestPropertyHolder.getPropertyValue( propertyName );
	}

	private synchronized static void initGlobalProperties()
	{
		globalTestPropertyHolder = new SettingsTestPropertyHolder( SoapUI.getSettings(), null,
				GlobalPropertySettings.PROPERTIES );

		String propFile = System.getProperty( "soapui.properties" );
		if( StringUtils.hasContent( propFile ) )
			globalTestPropertyHolder.addPropertiesFromFile( propFile );
	}

	public static void saveGlobalProperties()
	{
		if( globalTestPropertyHolder != null )
		{
			globalTestPropertyHolder.saveTo( SoapUI.getSettings() );
		}
	}

	public static String shortenXPathForPropertyExpansion( String xpath, String value )
	{
		if( xpath.length() > 0 )
		{
			StringBuffer buf = new StringBuffer();

			for( int c = 0; c < xpath.length(); c++ )
			{
				char ch = xpath.charAt( c );
				switch( ch )
				{
				case '\n' :
					buf.append( ' ' );
					break;
				default :
					buf.append( ch );
				}
			}

			xpath = buf.toString();
		}

		return xpath;
	}

	/**
	 * @deprecated Use {@link PropertyExpander#expandProperties(String)} instead
	 */
	public static String expandProperties( String content )
	{
		return PropertyExpander.expandProperties( content );
	}

	/**
	 * @deprecated Use
	 *             {@link PropertyExpander#expandProperties(PropertyExpansionContext,String)}
	 *             instead
	 */
	public static String expandProperties( PropertyExpansionContext context, String content )
	{
		return PropertyExpander.expandProperties( context, content );
	}

	/**
	 * @deprecated Use
	 *             {@link PropertyExpander#expandProperties(PropertyExpansionContext,String,boolean)}
	 *             instead
	 */

	public static String expandProperties( PropertyExpansionContext context, String content, boolean entitize )
	{
		return PropertyExpander.expandProperties( context, content, entitize );
	}

	/**
	 * Checks if a property can be transferred to another specified property via
	 * a property-transfer
	 */

	public static boolean canTransferToProperty( TestProperty source, TestProperty target )
	{
		return false;
	}

	/**
	 * Checks if a modelItem can acces a specified property via
	 * property-expansion
	 */

	public static boolean canExpandProperty( ModelItem contextModelItem, TestProperty property )
	{
		ModelItem propertyModelItem = property.getModelItem();

		// global / anonymous reference?
		if( propertyModelItem == null || propertyModelItem instanceof Project )
			return true;

		if( contextModelItem instanceof TestSuite )
		{
			return propertyModelItem == contextModelItem;
		}

		if( contextModelItem instanceof TestCase )
		{
			return propertyModelItem == contextModelItem
					|| ( propertyModelItem instanceof TestSuite && ( ( TestCase )contextModelItem ).getTestSuite() == propertyModelItem );
		}

		if( contextModelItem instanceof TestStep )
		{
			TestStep testStep = ( ( TestStep )contextModelItem );

			return propertyModelItem == contextModelItem
					|| ( propertyModelItem instanceof TestSuite && testStep.getTestCase().getTestSuite() == propertyModelItem )
					|| ( propertyModelItem instanceof TestCase && testStep.getTestCase() == propertyModelItem )
					|| ( propertyModelItem instanceof TestStep && testStep.getTestCase() == ( ( TestStep )propertyModelItem )
							.getTestCase() );
		}

		if( contextModelItem instanceof MockService )
		{
			return propertyModelItem == contextModelItem;
		}

		if( contextModelItem instanceof MockOperation )
		{
			return propertyModelItem == contextModelItem
					|| ( propertyModelItem instanceof MockService && ( ( MockOperation )contextModelItem ).getMockService() == propertyModelItem );
		}

		if( contextModelItem instanceof MockResponse )
		{
			MockResponse testStep = ( ( MockResponse )contextModelItem );

			return propertyModelItem == contextModelItem
					|| ( propertyModelItem instanceof MockService && testStep.getMockOperation().getMockService() == propertyModelItem )
					|| ( propertyModelItem instanceof MockOperation && testStep.getMockOperation() == propertyModelItem )
					|| ( propertyModelItem instanceof MockResponse && testStep.getMockOperation() == ( ( MockResponse )propertyModelItem )
							.getMockOperation() );
		}

		if( contextModelItem instanceof SecurityTest )
		{
			SecurityTest securityTest = ( ( SecurityTest )contextModelItem );

			return propertyModelItem == contextModelItem
					|| ( propertyModelItem instanceof TestSuite && securityTest.getTestCase().getTestSuite() == propertyModelItem )
					|| ( propertyModelItem instanceof TestCase && securityTest.getTestCase() == propertyModelItem )
					|| ( propertyModelItem instanceof SecurityTest && securityTest.getTestCase() == ( ( SecurityTest )propertyModelItem )
							.getTestCase() );
		}

		System.out
				.println( "property " + property.getName() + " can not be transferred to " + contextModelItem.getName() );
		return false;
	}

	public static MutableTestPropertyHolder getGlobalProperties()
	{
		if( globalTestPropertyHolder == null )
		{
			initGlobalProperties();
		}

		return globalTestPropertyHolder;
	}

	public static MutablePropertyExpansion[] renameProperty( RenameableTestProperty property, String newName,
			ModelItem root )
	{
		UISupport.setHourglassCursor();

		try
		{
			List<MutablePropertyExpansion> result = new ArrayList<MutablePropertyExpansion>();
			List<MutablePropertyExpansion> properties = new ArrayList<MutablePropertyExpansion>();

			PropertyExpansion[] propertyExpansions = getPropertyExpansions( root, true, true );
			for( PropertyExpansion pe : propertyExpansions )
			{
				MutablePropertyExpansion mpe = ( MutablePropertyExpansion )pe;
				if( mpe.getProperty().equals( property ) )
				{
					mpe.setProperty( property );
					properties.add( mpe );
				}
			}

			property.setName( newName );

			for( MutablePropertyExpansion mpe : properties )
			{
				try
				{
					mpe.update();
					result.add( mpe );
				}
				catch( Exception e )
				{
					e.printStackTrace();
				}
			}

			return result.toArray( new MutablePropertyExpansion[result.size()] );
		}
		finally
		{
			UISupport.resetCursor();
		}
	}

	public static PropertyExpansion[] getPropertyExpansions( ModelItem modelItem, boolean mutableOnly, boolean deep )
	{
		List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();

		if( modelItem instanceof PropertyExpansionContainer )
		{
			PropertyExpansion[] pes = ( ( PropertyExpansionContainer )modelItem ).getPropertyExpansions();
			if( pes != null && pes.length > 0 )
			{
				for( PropertyExpansion pe : pes )
				{
					if( mutableOnly && !( pe instanceof MutablePropertyExpansion ) )
						continue;

					result.add( pe );
				}
			}
		}

		if( deep )
		{
			List<? extends ModelItem> children = modelItem.getChildren();
			if( children != null && children.size() > 0 )
			{
				for( ModelItem child : children )
				{
					result.addAll( Arrays.asList( getPropertyExpansions( child, mutableOnly, deep ) ) );
				}
			}
		}

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

	public static Collection<? extends PropertyExpansion> extractPropertyExpansions( ModelItem modelItem, Object target,
			String propertyName )
	{
		List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();
		Set<String> expansions = new HashSet<String>();

		try
		{
			Object property = PropertyUtils.getProperty( target, propertyName );
			if( property instanceof String && PropertyUtils.isWriteable( target, propertyName ) )
			{
				String str = property.toString();

				if( !StringUtils.isNullOrEmpty( str ) )
				{
					int ix = str.indexOf( "${" );
					while( ix != -1 )
					{
						// TODO handle nested property-expansions..
						int ix2 = str.indexOf( '}', ix + 2 );
						if( ix2 == -1 )
							break;

						String expansion = str.substring( ix + 2, ix2 );
						if( !expansions.contains( expansion ) )
						{
							MutablePropertyExpansion tp = createMutablePropertyExpansion( expansion, modelItem, target,
									propertyName );
							if( tp != null )
							{
								result.add( tp );
								expansions.add( expansion );
							}
						}

						str = str.substring( ix2 );
						ix = str.indexOf( "${" );
					}
				}
			}
		}
		catch( Exception e )
		{
			SoapUI.logError( e );
		}

		return result;
	}

	public static MutablePropertyExpansionImpl createMutablePropertyExpansion( String pe, ModelItem modelItem,
			Object target, String propertyName )
	{
		WsdlTestStep testStep = null;
		WsdlTestCase testCase = null;
		WsdlTestSuite testSuite = null;
		WsdlProject project = null;
		WsdlMockService mockService = null;
		WsdlMockResponse mockResponse = null;
		TestPropertyHolder holder = null;
		SecurityTest securityTest = null;

		if( modelItem instanceof WsdlTestStep )
		{
			testStep = ( WsdlTestStep )modelItem;
			testCase = testStep.getTestCase();
			testSuite = testCase.getTestSuite();
			project = testSuite.getProject();
		}
		else if( modelItem instanceof WsdlTestCase )
		{
			testCase = ( WsdlTestCase )modelItem;
			testSuite = testCase.getTestSuite();
			project = testSuite.getProject();
		}
		else if( modelItem instanceof WsdlTestSuite )
		{
			testSuite = ( WsdlTestSuite )modelItem;
			project = testSuite.getProject();
		}
		else if( modelItem instanceof WsdlInterface )
		{
			project = ( ( WsdlInterface )modelItem ).getProject();
		}
		else if( modelItem instanceof WsdlProject )
		{
			project = ( WsdlProject )modelItem;
		}
		else if( modelItem instanceof WsdlMockService )
		{
			mockService = ( WsdlMockService )modelItem;
			project = mockService.getProject();
		}
		else if( modelItem instanceof AbstractHttpRequestInterface<?> )
		{
			project = ( ( AbstractHttpRequest<?> )modelItem ).getOperation().getInterface().getProject();
		}
		else if( modelItem instanceof WsdlMockOperation )
		{
			mockService = ( ( WsdlMockOperation )modelItem ).getMockService();
			project = mockService.getProject();
		}
		else if( modelItem instanceof WsdlMockResponse )
		{
			mockResponse = ( WsdlMockResponse )modelItem;
			mockService = mockResponse.getMockOperation().getMockService();
			project = mockService.getProject();
		}
		else if( modelItem instanceof SecurityTest )
		{
			securityTest = ( SecurityTest )modelItem;
			testCase = securityTest.getTestCase();
			testSuite = testCase.getTestSuite();
			project = testSuite.getProject();
		}

		// explicit item reference?
		if( pe.startsWith( PropertyExpansion.PROJECT_REFERENCE ) )
		{
			holder = project;
			pe = pe.substring( PropertyExpansion.PROJECT_REFERENCE.length() );
		}
		else if( pe.startsWith( PropertyExpansion.TESTSUITE_REFERENCE ) )
		{
			holder = testSuite;
			pe = pe.substring( PropertyExpansion.TESTSUITE_REFERENCE.length() );
		}
		else if( pe.startsWith( PropertyExpansion.TESTCASE_REFERENCE ) )
		{
			holder = testCase;
			pe = pe.substring( PropertyExpansion.TESTCASE_REFERENCE.length() );
		}
		else if( pe.startsWith( PropertyExpansion.SECURITYTEST_REFERENCE ) )
		{
			holder = testCase;
			pe = pe.substring( PropertyExpansion.SECURITYTEST_REFERENCE.length() );
		}
		else if( pe.startsWith( PropertyExpansion.MOCKSERVICE_REFERENCE ) )
		{
			holder = mockService;
			pe = pe.substring( PropertyExpansion.MOCKSERVICE_REFERENCE.length() );
		}
		else if( pe.startsWith( PropertyExpansion.MOCKRESPONSE_REFERENCE ) )
		{
			holder = mockResponse;
			pe = pe.substring( PropertyExpansion.MOCKRESPONSE_REFERENCE.length() );
		}
		else if( testCase != null )
		{
			int sepIx = pe.indexOf( PropertyExpansion.PROPERTY_SEPARATOR );
			if( sepIx > 0 )
			{
				holder = testCase.getTestStepByName( pe.substring( 0, sepIx ) );
				if( holder != null )
				{
					pe = pe.substring( sepIx + 1 );
				}
			}
		}

		int sepIx = pe.indexOf( PropertyExpansion.XPATH_SEPARATOR );
		String xpath = null;

		if( sepIx > 0 )
		{
			xpath = pe.substring( sepIx + 1 );
			pe = pe.substring( 0, sepIx );
		}

		if( holder == null )
			holder = getGlobalProperties();

		TestProperty tp = holder.getProperty( pe );
		return tp == null ? null : new MutablePropertyExpansionImpl( tp, xpath, target, propertyName );
	}

	/**
	 * @deprecated Use
	 *             {@link PropertyExpander#expandProperties(ModelItem,String)}
	 *             instead
	 */
	public static String expandProperties( ModelItem contextModelItem, String content )
	{
		return PropertyExpander.expandProperties( contextModelItem, content );
	}

	public static class GlobalPropertyExpansionContext implements PropertyExpansionContext
	{
		public Object getProperty( String name )
		{
			return getGlobalProperties().getProperty( name );
		}

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

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

		public Object removeProperty( String name )
		{
			return getGlobalProperties().removeProperty( name );
		}

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

		public ModelItem getModelItem()
		{
			return null;
		}

		public String expand( String content )
		{
			return PropertyExpander.expandProperties( this, content );
		}

		public StringToObjectMap getProperties()
		{
			StringToObjectMap result = new StringToObjectMap();
			Map<String, TestProperty> props = getGlobalProperties().getProperties();
			for( String key : props.keySet() )
			{
				result.put( key, props.get( key ) );
			}

			return result;
		}
	}

	public static boolean containsPropertyExpansion( String str )
	{
		return str != null && str.indexOf( "${" ) >= 0 && str.indexOf( '}' ) > 2;
	}
}
