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

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.swing.ImageIcon;

import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.MockOperationConfig;
import com.eviware.soapui.config.MockOperationDispatchStyleConfig;
import com.eviware.soapui.config.MockResponseConfig;
import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
import com.eviware.soapui.impl.wsdl.WsdlInterface;
import com.eviware.soapui.impl.wsdl.WsdlOperation;
import com.eviware.soapui.impl.wsdl.mock.dispatch.MockOperationDispatchRegistry;
import com.eviware.soapui.impl.wsdl.mock.dispatch.MockOperationDispatcher;
import com.eviware.soapui.impl.wsdl.support.CompressedStringSupport;
import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.iface.Operation;
import com.eviware.soapui.model.mock.MockOperation;
import com.eviware.soapui.model.mock.MockResponse;
import com.eviware.soapui.model.support.InterfaceListenerAdapter;
import com.eviware.soapui.model.support.ProjectListenerAdapter;
import com.eviware.soapui.settings.WsdlSettings;
import com.eviware.soapui.support.UISupport;

/**
 * A WsdlMockOperation in a WsdlMockService
 * 
 * @author ole.matzura
 */

public class WsdlMockOperation extends AbstractWsdlModelItem<MockOperationConfig> implements MockOperation,
		PropertyChangeListener
{
	@SuppressWarnings( "unused" )
	private final static Logger log = Logger.getLogger( WsdlMockOperation.class );

	public final static String DISPATCH_STYLE_PROPERTY = WsdlMockOperation.class.getName() + "@dispatchstyle";
	public final static String DEFAULT_RESPONSE_PROPERTY = WsdlMockOperation.class.getName() + "@defaultresponse";
	public final static String DISPATCH_PATH_PROPERTY = WsdlMockOperation.class.getName() + "@dispatchpath";
	public final static String OPERATION_PROPERTY = WsdlMockOperation.class.getName() + "@operation";

	private WsdlOperation operation;
	private MockOperationDispatcher dispatcher;
	private List<WsdlMockResponse> responses = new ArrayList<WsdlMockResponse>();
	private InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
	private InternalProjectListener projectListener = new InternalProjectListener();
	private ImageIcon oneWayIcon;
	private ImageIcon notificationIcon;
	private ImageIcon solicitResponseIcon;

	public WsdlMockOperation( WsdlMockService mockService, MockOperationConfig config )
	{
		super( config, mockService, "/mockOperation.gif" );

		Interface iface = mockService.getProject().getInterfaceByName( config.getInterface() );
		if( iface == null )
		{
			SoapUI.log.warn( "Missing interface [" + config.getInterface() + "] for MockOperation in project" );
		}
		else
		{
			operation = ( WsdlOperation )iface.getOperationByName( config.getOperation() );
		}

		List<MockResponseConfig> responseConfigs = config.getResponseList();
		for( MockResponseConfig responseConfig : responseConfigs )
		{
			WsdlMockResponse wsdlMockResponse = new WsdlMockResponse( this, responseConfig );
			wsdlMockResponse.addPropertyChangeListener( this );
			responses.add( wsdlMockResponse );
		}

		initData( config );
	}

	private void initData( MockOperationConfig config )
	{
		if( !config.isSetName() )
			config.setName( operation == null ? "<missing operation>" : operation.getName() );

		if( !config.isSetDefaultResponse() && responses.size() > 0 )
			setDefaultResponse( responses.get( 0 ).getName() );

		if( !config.isSetDispatchStyle() )
			config.setDispatchStyle( MockOperationDispatchStyleConfig.SEQUENCE );

		if( !getConfig().isSetDispatchConfig() )
			getConfig().addNewDispatchConfig();

		dispatcher = MockOperationDispatchRegistry.buildDispatcher( config.getDispatchStyle().toString(), this );

		if( operation != null )
		{
			operation.getInterface().getProject().addProjectListener( projectListener );
			operation.getInterface().addInterfaceListener( interfaceListener );
			operation.getInterface().addPropertyChangeListener( WsdlInterface.NAME_PROPERTY, this );
		}

		oneWayIcon = UISupport.createImageIcon( "/onewaymockoperation.gif" );
		notificationIcon = UISupport.createImageIcon( "/mocknotificationoperation.gif" );
		solicitResponseIcon = UISupport.createImageIcon( "/mocksolicitresponseoperation.gif" );
	}

	public WsdlMockOperation( WsdlMockService mockService, MockOperationConfig config, WsdlOperation operation )
	{
		super( config, mockService, "/mockOperation.gif" );
		this.operation = operation;

		config.setInterface( operation.getInterface().getName() );
		config.setOperation( operation.getName() );

		initData( config );
		interfaceListener = new InternalInterfaceListener();
	}

	@Override
	public ImageIcon getIcon()
	{
		if( operation != null )
		{
			if( isOneWay() )
			{
				return oneWayIcon;
			}
			else if( isNotification() )
			{
				return notificationIcon;
			}
			else if( isSolicitResponse() )
			{
				return solicitResponseIcon;
			}
		}

		return super.getIcon();
	}

	public WsdlMockService getMockService()
	{
		return ( WsdlMockService )getParent();
	}

	public WsdlMockResponse getMockResponseAt( int index )
	{
		return responses.get( index );
	}

	public WsdlOperation getOperation()
	{
		return operation;
	}

	public WsdlMockResponse getMockResponseByName( String name )
	{
		return ( WsdlMockResponse )getWsdlModelItemByName( responses, name );
	}

	public int getMockResponseCount()
	{
		return responses.size();
	}

	public WsdlMockResponse addNewMockResponse( MockResponseConfig responseConfig )
	{
		WsdlMockResponse mockResponse = new WsdlMockResponse( this, responseConfig );

		responses.add( mockResponse );
		if( responses.size() == 1 )
			setDefaultResponse( mockResponse.getName() );

		// add ws-a action
		WsdlUtils.setDefaultWsaAction( mockResponse.getWsaConfig(), true );

		( getMockService() ).fireMockResponseAdded( mockResponse );
		notifyPropertyChanged( "mockResponses", null, mockResponse );

		return mockResponse;
	}

	public WsdlMockResponse addNewMockResponse( String name, boolean createResponse )
	{
		MockResponseConfig responseConfig = getConfig().addNewResponse();
		responseConfig.setName( name );
		responseConfig.addNewResponseContent();

		if( createResponse && getOperation() != null && getOperation().isBidirectional() )
		{
			boolean createOptional = SoapUI.getSettings().getBoolean(
					WsdlSettings.XML_GENERATION_ALWAYS_INCLUDE_OPTIONAL_ELEMENTS );
			CompressedStringSupport.setString( responseConfig.getResponseContent(),
					getOperation().createResponse( createOptional ) );
		}

		return addNewMockResponse( responseConfig );
	}

	public void removeMockResponse( WsdlMockResponse mockResponse )
	{
		int ix = responses.indexOf( mockResponse );
		responses.remove( ix );
		mockResponse.removePropertyChangeListener( this );

		try
		{
			( getMockService() ).fireMockResponseRemoved( mockResponse );
		}
		finally
		{
			mockResponse.release();
			getConfig().removeResponse( ix );
		}
	}

	public WsdlMockResult dispatchRequest( WsdlMockRequest request ) throws DispatchException
	{
		try
		{
			request.setOperation( getOperation() );
			WsdlMockResult result = new WsdlMockResult( request );

			if( getMockResponseCount() == 0 )
				throw new DispatchException( "Missing MockResponse(s) in MockOperation [" + getName() + "]" );

			result.setMockOperation( this );
			WsdlMockResponse response = dispatcher.selectMockResponse( request, result );
			if( response == null )
			{
				response = getMockResponseByName( getDefaultResponse() );
			}

			if( response == null )
			{
				throw new DispatchException( "Failed to find MockResponse" );
			}

			result.setMockResponse( response );
			response.execute( request, result );

			return result;
		}
		catch( Throwable e )
		{
			if( e instanceof DispatchException )
				throw ( DispatchException )e;
			else
				throw new DispatchException( e );
		}
	}

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

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

		for( WsdlMockResponse response : responses )
		{
			response.removePropertyChangeListener( this );
			response.release();
		}

		if( operation != null )
		{
			operation.getInterface().getProject().removeProjectListener( projectListener );
			operation.getInterface().removeInterfaceListener( interfaceListener );
			operation.getInterface().removePropertyChangeListener( WsdlInterface.NAME_PROPERTY, this );
		}
	}

	public String getDispatchStyle()
	{
		return String.valueOf( getConfig().isSetDispatchStyle() ? getConfig().getDispatchStyle()
				: MockOperationDispatchStyleConfig.SEQUENCE );
	}

	public MockOperationDispatcher setDispatchStyle( String dispatchStyle )
	{
		String old = getDispatchStyle();
		if( dispatcher != null && dispatchStyle.equals( old ) )
			return dispatcher;

		getConfig().setDispatchStyle( MockOperationDispatchStyleConfig.Enum.forString( dispatchStyle ) );

		if( dispatcher != null )
		{
			dispatcher.release();
		}

		if( !getConfig().isSetDispatchConfig() )
			getConfig().addNewDispatchConfig();

		dispatcher = MockOperationDispatchRegistry.buildDispatcher( dispatchStyle, this );

		notifyPropertyChanged( DISPATCH_STYLE_PROPERTY, old, dispatchStyle );

		return dispatcher;
	}

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

	public void setDispatchPath( String dispatchPath )
	{
		String old = getDispatchPath();
		getConfig().setDispatchPath( dispatchPath );
		notifyPropertyChanged( DISPATCH_PATH_PROPERTY, old, dispatchPath );
	}

	public String getWsdlOperationName()
	{
		return operation == null ? null : operation.getName();
	}

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

	public void setDefaultResponse( String defaultResponse )
	{
		String old = getDefaultResponse();
		getConfig().setDefaultResponse( defaultResponse );
		notifyPropertyChanged( DEFAULT_RESPONSE_PROPERTY, old, defaultResponse );
	}

	public List<MockResponse> getMockResponses()
	{
		return new ArrayList<MockResponse>( responses );
	}

	public void propertyChange( PropertyChangeEvent arg0 )
	{
		if( arg0.getPropertyName().equals( WsdlMockResponse.NAME_PROPERTY ) )
		{
			if( arg0.getOldValue().equals( getDefaultResponse() ) )
				setDefaultResponse( arg0.getNewValue().toString() );
		}
		else if( arg0.getPropertyName().equals( WsdlInterface.NAME_PROPERTY ) )
		{
			getConfig().setInterface( arg0.getNewValue().toString() );
		}
	}

	public WsdlMockResult getLastMockResult()
	{
		WsdlMockResult result = null;

		for( WsdlMockResponse response : responses )
		{
			WsdlMockResult mockResult = response.getMockResult();
			if( mockResult != null )
			{
				if( result == null || result.getTimestamp() > mockResult.getTimestamp() )
					result = mockResult;
			}
		}

		return result;
	}

	public void setOperation( WsdlOperation operation )
	{
		WsdlOperation oldOperation = getOperation();

		if( operation == null )
		{
			getConfig().unsetInterface();
			getConfig().unsetOperation();
		}
		else
		{
			getConfig().setInterface( operation.getInterface().getName() );
			getConfig().setOperation( operation.getName() );
		}

		this.operation = operation;

		notifyPropertyChanged( OPERATION_PROPERTY, oldOperation, operation );
	}

	public MockOperationDispatcher getMockOperationDispatcher()
	{
		return dispatcher;
	}

	private class InternalInterfaceListener extends InterfaceListenerAdapter
	{
		@Override
		public void operationUpdated( Operation operation )
		{
			if( operation == WsdlMockOperation.this.operation )
				getConfig().setOperation( operation.getName() );
		}

		@Override
		public void operationRemoved( Operation operation )
		{
			if( operation == WsdlMockOperation.this.operation )
				getMockService().removeMockOperation( WsdlMockOperation.this );
		}
	}

	private class InternalProjectListener extends ProjectListenerAdapter
	{
		@Override
		public void interfaceRemoved( Interface iface )
		{
			if( operation.getInterface() == iface )
				getMockService().removeMockOperation( WsdlMockOperation.this );
		}

		@Override
		public void interfaceUpdated( Interface iface )
		{
			if( operation.getInterface() == iface )
				getConfig().setInterface( iface.getName() );
		}
	}

	public boolean isOneWay()
	{
		return operation == null ? false : operation.isOneWay();
	}

	public boolean isNotification()
	{
		return operation == null ? false : operation.isNotification();
	}

	public boolean isSolicitResponse()
	{
		return operation == null ? false : operation.isSolicitResponse();
	}

	public boolean isUnidirectional()
	{
		return operation == null ? false : operation.isUnidirectional();
	}

	public boolean isBidirectional()
	{
		return !isUnidirectional();
	}

	public List<? extends ModelItem> getChildren()
	{
		return responses;
	}

	public void exportMockOperation( File file )
	{
		try
		{
			this.getConfig().newCursor().save( file );
		}
		catch( IOException e )
		{
			e.printStackTrace();
		}
	}

}
