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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;

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

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.impl.rest.RestRequestInterface;
import com.eviware.soapui.impl.support.AbstractHttpRequestInterface;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.panels.assertions.AssertionCategoryMapping;
import com.eviware.soapui.impl.wsdl.panels.assertions.AssertionListEntry;
import com.eviware.soapui.impl.wsdl.panels.mockoperation.WsdlMockResponseMessageExchange;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.AbstractGroovyEditorModel;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.GroovyEditor;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
import com.eviware.soapui.impl.wsdl.teststeps.HttpResponseMessageExchange;
import com.eviware.soapui.impl.wsdl.teststeps.HttpTestRequestStepInterface;
import com.eviware.soapui.impl.wsdl.teststeps.RestResponseMessageExchange;
import com.eviware.soapui.impl.wsdl.teststeps.RestTestRequestStepInterface;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlMockResponseTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlResponseMessageExchange;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep;
import com.eviware.soapui.impl.wsdl.teststeps.assertions.AbstractTestAssertionFactory;
import com.eviware.soapui.model.TestPropertyHolder;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.testsuite.Assertable;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.model.testsuite.AssertionException;
import com.eviware.soapui.model.testsuite.RequestAssertion;
import com.eviware.soapui.model.testsuite.ResponseAssertion;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.log.JLogList;
import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
import com.eviware.soapui.support.scripting.SoapUIScriptEngineRegistry;
import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
import com.jgoodies.forms.builder.ButtonBarBuilder;

/**
 * Assertion performed by a custom Grooy Script
 * 
 * @author ole.matzura
 */

public class GroovyScriptAssertion extends WsdlMessageAssertion implements RequestAssertion, ResponseAssertion
{
	public static final String ID = "GroovyScriptAssertion";
	public static final String LABEL = "Script Assertion";
	public static final String DESCRIPTION = "Runs a custom script to perform arbitrary validations. Applicable to any Property.";
	private String scriptText;
	private SoapUIScriptEngine scriptEngine;
	private JDialog dialog;
	private GroovyScriptAssertionPanel groovyScriptAssertionPanel;
	private String oldScriptText;

	public GroovyScriptAssertion( TestAssertionConfig assertionConfig, Assertable modelItem )
	{
		super( assertionConfig, modelItem, true, true, true, false );

		XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( getConfiguration() );
		scriptText = reader.readString( "scriptText", "" );

		scriptEngine = SoapUIScriptEngineRegistry.create( this );
		scriptEngine.setScript( scriptText );
	}

	@Override
	protected String internalAssertRequest( MessageExchange messageExchange, SubmitContext context )
			throws AssertionException
	{
		return assertScript( messageExchange, context, SoapUI.ensureGroovyLog() );
	}

	private String assertScript( MessageExchange messageExchange, SubmitContext context, Logger log )
			throws AssertionException
	{
		try
		{
			scriptEngine.setVariable( "context", context );
			scriptEngine.setVariable( "messageExchange", messageExchange );
			scriptEngine.setVariable( "log", log );
			scriptEngine.setVariable( "assertion", this );

			Object result = scriptEngine.run();
			return result == null ? null : result.toString();
		}
		catch( Throwable e )
		{
			throw new AssertionException( new AssertionError( e.getMessage() ) );
		}
		finally
		{
			scriptEngine.clearVariables();
		}
	}

	@Override
	protected String internalAssertResponse( MessageExchange messageExchange, SubmitContext context )
			throws AssertionException
	{
		return assertScript( messageExchange, context, SoapUI.ensureGroovyLog() );
	}

	@Override
	protected String internalAssertProperty( TestPropertyHolder source, String propertyName,
			MessageExchange messageExchange, SubmitContext context ) throws AssertionException
	{
		return null;
	}

	@Override
	public boolean configure()
	{
		if( dialog == null )
		{
			buildDialog();
		}

		oldScriptText = scriptText;
		UISupport.showDialog( dialog );
		return true;
	}

	protected void buildDialog()
	{
		dialog = new JDialog( UISupport.getMainFrame(), "Script Assertion", true );
		groovyScriptAssertionPanel = new GroovyScriptAssertionPanel();
		dialog.setContentPane( groovyScriptAssertionPanel );
		UISupport.initDialogActions( dialog, groovyScriptAssertionPanel.getShowOnlineHelpAction(),
				groovyScriptAssertionPanel.getDefaultButton() );
		dialog.setSize( 600, 500 );
		dialog.setModal( true );
		dialog.pack();
	}

	protected GroovyScriptAssertionPanel getScriptAssertionPanel()
	{
		return groovyScriptAssertionPanel;
	}

	protected XmlObject createConfiguration()
	{
		XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
		builder.add( "scriptText", scriptText );
		return builder.finish();
	}

	public String getScriptText()
	{
		return scriptText;
	}

	public void setScriptText( String scriptText )
	{
		this.scriptText = scriptText;
		scriptEngine.setScript( scriptText );
		setConfiguration( createConfiguration() );
	}

	protected class GroovyScriptAssertionPanel extends JPanel
	{
		private GroovyEditor editor;
		private JSplitPane mainSplit;
		private JLogList logArea;
		private RunAction runAction = new RunAction();
		private Logger logger;
		private JButton okButton;
		private ShowOnlineHelpAction showOnlineHelpAction;
		public String oldscriptText;

		public GroovyScriptAssertionPanel()
		{
			super( new BorderLayout() );

			buildUI();
			setPreferredSize( new Dimension( 600, 440 ) );

			logger = Logger.getLogger( "ScriptAssertion." + getName() );
			editor.requestFocusInWindow();
		}

		public GroovyEditor getGroovyEditor()
		{
			return editor;
		}

		public void release()
		{
			logArea.release();
			editor.release();
			logger = null;
		}

		private void buildUI()
		{
			editor = new GroovyEditor( new ScriptStepGroovyEditorModel() );

			logArea = new JLogList( "Groovy Test Log" );
			logArea.addLogger( "ScriptAssertion." + getName(), true );
			logArea.getLogList().addMouseListener( new MouseAdapter()
			{

				public void mouseClicked( MouseEvent e )
				{
					if( e.getClickCount() < 2 )
						return;

					String value = logArea.getLogList().getSelectedValue().toString();
					if( value == null )
						return;

					editor.selectError( value );
				}
			} );

			editor.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 0, 3, 0, 3 ),
					editor.getBorder() ) );

			mainSplit = UISupport.createVerticalSplit( editor, logArea );
			mainSplit.setDividerLocation( 280 );
			mainSplit.setResizeWeight( 0.8 );
			add( mainSplit, BorderLayout.CENTER );
			add( buildToolbar(), BorderLayout.NORTH );
			add( buildStatusBar(), BorderLayout.SOUTH );
		}

		public JButton getDefaultButton()
		{
			return okButton;
		}

		public ShowOnlineHelpAction getShowOnlineHelpAction()
		{
			return showOnlineHelpAction;
		}

		private Component buildStatusBar()
		{
			ButtonBarBuilder builder = new ButtonBarBuilder();

			showOnlineHelpAction = new ShowOnlineHelpAction( HelpUrls.GROOVYASSERTION_HELP_URL );
			builder.addFixed( UISupport.createToolbarButton( showOnlineHelpAction ) );
			builder.addGlue();
			okButton = new JButton( new OkAction() );
			builder.addFixed( okButton );
			builder.addRelatedGap();
			builder.addFixed( new JButton( new CancelAction() ) );
			builder.setBorder( BorderFactory.createEmptyBorder( 0, 3, 3, 3 ) );
			return builder.getPanel();
		}

		private JComponent buildToolbar()
		{
			JXToolBar toolBar = UISupport.createToolbar();
			JButton runButton = UISupport.createToolbarButton( runAction );
			toolBar.add( runButton );
			toolBar.add( Box.createHorizontalGlue() );
			JLabel label = new JLabel( "<html>Script is invoked with <code>log</code>, <code>context</code> "
					+ "and <code>messageExchange</code> variables</html>" );
			label.setToolTipText( label.getText() );
			label.setMaximumSize( label.getPreferredSize() );

			toolBar.addFixed( label );
			toolBar.addSpace( 3 );

			return toolBar;
		}

		private final class OkAction extends AbstractAction
		{
			public OkAction()
			{
				super( "OK" );
			}

			public void actionPerformed( ActionEvent e )
			{
				dialog.setVisible( false );
				setScriptText( editor.getEditArea().getText() );
			}
		}

		private final class CancelAction extends AbstractAction
		{
			public CancelAction()
			{
				super( "Cancel" );
			}

			public void actionPerformed( ActionEvent e )
			{
				dialog.setVisible( false );
				editor.getEditArea().setText( oldScriptText );
			}
		}

		private class ScriptStepGroovyEditorModel extends AbstractGroovyEditorModel
		{
			public ScriptStepGroovyEditorModel()
			{
				super( new String[] { "log", "context", "messageExchange" }, getAssertable().getModelItem(), "Assertion" );
			}

			public Action getRunAction()
			{
				return runAction;
			}

			public String getScript()
			{
				return getScriptText();
			}

			public void setScript( String text )
			{

			}
		}

		private class RunAction extends AbstractAction
		{
			public RunAction()
			{
				putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run_groovy_script.gif" ) );
				putValue( Action.SHORT_DESCRIPTION,
						"Runs this assertion script against the last messageExchange with a mock testContext" );
			}

			public void actionPerformed( ActionEvent e )
			{
				TestStep testStep = getAssertable().getTestStep();
				MessageExchange exchange = null;

				if( testStep instanceof WsdlTestRequestStep )
				{
					WsdlTestRequestStep testRequestStep = ( WsdlTestRequestStep )testStep;
					exchange = new WsdlResponseMessageExchange( testRequestStep.getTestRequest() );
					( ( WsdlResponseMessageExchange )exchange ).setResponse( testRequestStep.getTestRequest().getResponse() );
				}
				else if( testStep instanceof RestTestRequestStepInterface )
				{
					RestTestRequestStepInterface testRequestStep = ( RestTestRequestStepInterface )testStep;
					exchange = new RestResponseMessageExchange( ( RestRequestInterface )testRequestStep.getTestRequest() );
					( ( RestResponseMessageExchange )exchange )
							.setResponse( ( ( AbstractHttpRequestInterface<?> )testRequestStep.getTestRequest() )
									.getResponse() );
				}
				else if( testStep instanceof HttpTestRequestStepInterface )
				{
					HttpTestRequestStepInterface testRequestStep = ( HttpTestRequestStepInterface )testStep;
					exchange = new HttpResponseMessageExchange( testRequestStep.getTestRequest() );
					( ( HttpResponseMessageExchange )exchange )
							.setResponse( ( ( AbstractHttpRequestInterface<?> )testRequestStep.getTestRequest() )
									.getResponse() );
				}
				else if( testStep instanceof WsdlMockResponseTestStep )
				{
					WsdlMockResponseTestStep mockResponseStep = ( WsdlMockResponseTestStep )testStep;
					exchange = new WsdlMockResponseMessageExchange( mockResponseStep.getMockResponse() );
				}

				try
				{
					setScriptText( editor.getEditArea().getText() );
					String result = assertScript( exchange, new WsdlTestRunContext( testStep ), logger );
					UISupport
							.showInfoMessage( "Script Assertion Passed" + ( ( result == null ) ? "" : ": [" + result + "]" ) );
				}
				catch( AssertionException e1 )
				{
					UISupport.showErrorMessage( e1.getMessage() );
				}
				catch( Throwable t )
				{
					SoapUI.logError( t );
					UISupport.showErrorMessage( t.getMessage() );
				}

				editor.requestFocusInWindow();
			}
		}
	}

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

		if( groovyScriptAssertionPanel != null )
			groovyScriptAssertionPanel.release();
	}

	public static class Factory extends AbstractTestAssertionFactory
	{
		public Factory()
		{
			super( GroovyScriptAssertion.ID, GroovyScriptAssertion.LABEL, GroovyScriptAssertion.class );
		}

		@Override
		public String getCategory()
		{
			return AssertionCategoryMapping.SCRIPT_CATEGORY;
		}

		@Override
		public Class<? extends WsdlMessageAssertion> getAssertionClassType()
		{
			return GroovyScriptAssertion.class;
		}

		@Override
		public AssertionListEntry getAssertionListEntry()
		{
			return new AssertionListEntry( GroovyScriptAssertion.ID, GroovyScriptAssertion.LABEL,
					GroovyScriptAssertion.DESCRIPTION );
		}
	}
}
