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

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.ListModel;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.panels.support.MockTestRunContext;
import com.eviware.soapui.impl.wsdl.panels.support.MockTestRunner;
import com.eviware.soapui.impl.wsdl.panels.support.TestRunComponentEnabler;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.GroovyEditor;
import com.eviware.soapui.impl.wsdl.panels.teststeps.support.GroovyEditorModel;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlGroovyScriptTestStep;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestStepResult;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.settings.Settings;
import com.eviware.soapui.model.settings.SettingsListener;
import com.eviware.soapui.support.ListDataChangeListener;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JComponentInspector;
import com.eviware.soapui.support.components.JEditorStatusBarWithProgress;
import com.eviware.soapui.support.components.JInspectorPanel;
import com.eviware.soapui.support.components.JInspectorPanelFactory;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.log.JLogList;
import com.eviware.soapui.ui.support.ModelItemDesktopPanel;

/**
 * DesktopPanel for WsdlGroovyTestSteps
 * 
 * @author Ole.Matzura
 */

public class GroovyScriptStepDesktopPanel extends ModelItemDesktopPanel<WsdlGroovyScriptTestStep> implements
		PropertyChangeListener
{
	private final WsdlGroovyScriptTestStep groovyStep;
	private GroovyEditor editor;
	private JLogList logArea;
	private Logger logger;
	private TestRunComponentEnabler componentEnabler;
	private RunAction runAction = new RunAction();
	private JEditorStatusBarWithProgress statusBar;
	private SettingsListener settingsListener;
	private JComponentInspector<JComponent> logInspector;
	public boolean updating;
	private JInspectorPanel inspectorPanel;

	public GroovyScriptStepDesktopPanel( WsdlGroovyScriptTestStep groovyStep )
	{
		super( groovyStep );
		this.groovyStep = groovyStep;
		componentEnabler = new TestRunComponentEnabler( groovyStep.getTestCase() );

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

		logger = Logger.getLogger( groovyStep.getName() + "#" + hashCode() );

		addFocusListener( new FocusAdapter()
		{
			public void focusGained( FocusEvent e )
			{
				editor.requestFocusInWindow();
			}
		} );

		groovyStep.addPropertyChangeListener( this );
	}

	protected GroovyEditor getEditor()
	{
		return editor;
	}

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

		logArea = new JLogList( "Groovy Test Log" );
		logArea.addLogger( groovyStep.getName() + "#" + hashCode(), 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 );
			}
		} );

		logArea.getLogList().getModel().addListDataListener( new ListDataChangeListener()
		{

			@Override
			public void dataChanged( ListModel model )
			{
				logInspector.setTitle( "Log Output (" + model.getSize() + ")" );

			}
		} );

		inspectorPanel = JInspectorPanelFactory.build( editor );
		logInspector = inspectorPanel.addInspector( new JComponentInspector<JComponent>( logArea, "Log Output (0)",
				"Groovy Log output for this script", true ) );
		inspectorPanel.setDefaultDividerLocation( 0.8F );
		inspectorPanel.activate( logInspector );
		add( inspectorPanel.getComponent(), BorderLayout.CENTER );
		add( buildToolbar(), BorderLayout.NORTH );
		add( buildStatusBar(), BorderLayout.SOUTH );

		componentEnabler.add( editor );
	}

	private Component buildStatusBar()
	{
		statusBar = new JEditorStatusBarWithProgress( editor );
		return statusBar;
	}

	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>testRunner</code> variables</html>" );
		label.setToolTipText( label.getText() );
		label.setMaximumSize( label.getPreferredSize() );

		toolBar.add( label );
		toolBar.addRelatedGap();
		toolBar.add( UISupport.createToolbarButton( new ShowOnlineHelpAction( HelpUrls.GROOVYSTEPEDITOR_HELP_URL ) ) );

		componentEnabler.add( runButton );

		return toolBar;
	}

	public boolean onClose( boolean canCancel )
	{
		componentEnabler.release();
		editor.release();
		SoapUI.getSettings().removeSettingsListener( settingsListener );
		logger.removeAllAppenders();
		logger = null;
		logArea.release();
		inspectorPanel.release();

		getModelItem().removePropertyChangeListener( this );

		return super.release();
	}

	public JComponent getComponent()
	{
		return this;
	}

	public boolean dependsOn( ModelItem modelItem )
	{
		return modelItem == groovyStep || modelItem == groovyStep.getTestCase()
				|| modelItem == groovyStep.getTestCase().getTestSuite()
				|| modelItem == groovyStep.getTestCase().getTestSuite().getProject();
	}

	private class ScriptStepGroovyEditorModel implements GroovyEditorModel
	{
		public String[] getKeywords()
		{
			return new String[] { "log", "context", "testRunner" };
		}

		public Action getRunAction()
		{
			return runAction;
		}

		public String getScript()
		{
			return groovyStep.getScript();
		}

		public void setScript( String text )
		{
			if( updating )
				return;

			updating = true;
			groovyStep.setScript( text );
			updating = false;
		}

		public Settings getSettings()
		{
			return SoapUI.getSettings();
		}

		public String getScriptName()
		{
			return null;
		}

		public void addPropertyChangeListener( PropertyChangeListener listener )
		{
		}

		public void removePropertyChangeListener( PropertyChangeListener listener )
		{
		}

		public ModelItem getModelItem()
		{
			return groovyStep;
		}
	}

	private class RunAction extends AbstractAction
	{
		public RunAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run_groovy_script.gif" ) );
			putValue( Action.SHORT_DESCRIPTION,
					"Runs this script in a seperate thread using a mock testRunner and testContext" );
		}

		public void actionPerformed( ActionEvent e )
		{
			SoapUI.getThreadPool().execute( new Runnable()
			{
				public void run()
				{
					MockTestRunner mockTestRunner = new MockTestRunner( groovyStep.getTestCase(), logger );
					statusBar.setIndeterminate( true );
					WsdlTestStepResult result = ( WsdlTestStepResult )groovyStep.run( mockTestRunner,
							new MockTestRunContext( mockTestRunner, groovyStep ) );
					statusBar.setIndeterminate( false );

					Throwable er = result.getError();
					if( er != null )
					{
						String message = er.getMessage();

						// ugly...
						editor.selectError( message );

						UISupport.showErrorMessage(StringUtils.join( result.getMessages(), "\n" ));
						editor.requestFocus();
					}
					else if( result.getMessages().length > 0 )
					{
						UISupport.showInfoMessage( StringUtils.join( result.getMessages(), "\n" ) );
					}
				}
			} );
		}
	}

	public void propertyChange( PropertyChangeEvent evt )
	{
		if( evt.getPropertyName().equals( "script" ) && !updating )
		{
			updating = true;
			editor.getEditArea().setText( ( String )evt.getNewValue() );
			updating = false;
		}

		super.propertyChange( evt );
	}
}
