/*
 *  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.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.util.Date;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.text.Document;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.components.ModelItemXmlEditor;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockResponse;
import com.eviware.soapui.impl.wsdl.mock.WsdlMockResult;
import com.eviware.soapui.impl.wsdl.panels.mockoperation.AbstractWsdlMockResponseDesktopPanel;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlMockResponseTestStep;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.support.TestRunListenerAdapter;
import com.eviware.soapui.model.testsuite.Assertable;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.model.testsuite.AssertionsListener;
import com.eviware.soapui.model.testsuite.LoadTestRunner;
import com.eviware.soapui.model.testsuite.TestAssertion;
import com.eviware.soapui.model.testsuite.TestCaseRunContext;
import com.eviware.soapui.model.testsuite.TestCaseRunner;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestStepResult;
import com.eviware.soapui.monitor.support.TestMonitorListenerAdapter;
import com.eviware.soapui.security.SecurityTestRunner;
import com.eviware.soapui.support.DocumentListenerAdapter;
import com.eviware.soapui.support.ModelItemPropertyEditorModel;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JComponentInspector;
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.xml.XmlUtils;

public class WsdlMockResponseStepDesktopPanel extends
		AbstractWsdlMockResponseDesktopPanel<WsdlMockResponseTestStep, WsdlMockResponse>
{
	private JTextArea logArea;
	private AssertionsPanel assertionsPanel;
	private JTextField portField;
	private JTextField pathField;
	private InternalTestRunListener testRunListener;
	private InternalTestMonitorListener testMonitorListener = new InternalTestMonitorListener();
	private InternalAssertionsListener assertionsListener = new InternalAssertionsListener();
	private JInspectorPanel inspectorPanel;
	private JComponentInspector<JComponent> assertionInspector;
	private JComponentInspector<JComponent> logInspector;
	private ModelItemPropertyEditorModel<WsdlMockResponseTestStep> queryEditorModel;
	private ModelItemPropertyEditorModel<WsdlMockResponseTestStep> matchEditorModel;

	public WsdlMockResponseStepDesktopPanel( WsdlMockResponseTestStep mockResponseStep )
	{
		super( mockResponseStep );
		init( mockResponseStep.getMockResponse() );

		testRunListener = new InternalTestRunListener();
		mockResponseStep.getTestCase().addTestRunListener( testRunListener );

		SoapUI.getTestMonitor().addTestMonitorListener( testMonitorListener );
		setEnabled( !SoapUI.getTestMonitor().hasRunningTest( mockResponseStep.getTestCase() ) );

		mockResponseStep.addAssertionsListener( assertionsListener );
	}

	@Override
	protected JComponent buildContent()
	{
		inspectorPanel = JInspectorPanelFactory.build( super.buildContent() );

		assertionsPanel = buildAssertionsPanel();

		assertionInspector = new JComponentInspector<JComponent>( assertionsPanel, "Assertions ("
				+ getModelItem().getAssertionCount() + ")", "Assertions for this Test Request", true );

		inspectorPanel.addInspector( assertionInspector );

		logInspector = new JComponentInspector<JComponent>( buildLogPanel(), "Request Log (0)", "Log of requests", true );
		inspectorPanel.addInspector( logInspector );

		inspectorPanel.addInspector( new JComponentInspector<JComponent>( buildQueryMatchPanel(), "Query/Match",
				"Query/Match configuration", true ) );

		inspectorPanel.setDefaultDividerLocation( 0.6F );
		inspectorPanel.setCurrentInspector( "Assertions" );

		updateStatusIcon();

		return inspectorPanel.getComponent();
	}

	private void updateStatusIcon()
	{
		Assertable.AssertionStatus status = getModelItem().getAssertionStatus();
		switch( status )
		{
		case FAILED :
		{
			assertionInspector.setIcon( UISupport.createImageIcon( "/failed_assertion.gif" ) );
			inspectorPanel.activate( assertionInspector );
			break;
		}
		case UNKNOWN :
		{
			assertionInspector.setIcon( UISupport.createImageIcon( "/unknown_assertion.gif" ) );
			break;
		}
		case VALID :
		{
			assertionInspector.setIcon( UISupport.createImageIcon( "/valid_assertion.gif" ) );
			inspectorPanel.deactivate();
			break;
		}
		}
	}

	private JComponent buildLogPanel()
	{
		logArea = new JTextArea();
		logArea.setEditable( false );
		logArea.setToolTipText( "Response Log" );

		JPanel panel = new JPanel( new BorderLayout() );
		panel.add( new JScrollPane( logArea ), BorderLayout.CENTER );

		return panel;
	}

	public void setContent( JComponent content )
	{
		inspectorPanel.setContentComponent( content );
	}

	public void removeContent( JComponent content )
	{
		inspectorPanel.setContentComponent( null );
	}

	@Override
	protected void createToolbar( JXToolBar toolbar )
	{
		toolbar.addUnrelatedGap();
		toolbar.addFixed( new JLabel( "Path" ) );
		toolbar.addRelatedGap();
		pathField = new JTextField( getModelItem().getPath(), 15 );
		pathField.getDocument().addDocumentListener( new DocumentListenerAdapter()
		{

			@Override
			public void update( Document document )
			{
				getModelItem().setPath( pathField.getText() );
			}
		} );

		toolbar.addFixed( pathField );

		toolbar.addUnrelatedGap();
		toolbar.addFixed( new JLabel( "Port" ) );
		toolbar.addRelatedGap();
		portField = new JTextField( String.valueOf( getModelItem().getPort() ), 5 );
		portField.getDocument().addDocumentListener( new DocumentListenerAdapter()
		{

			@Override
			public void update( Document document )
			{
				try
				{
					getModelItem().setPort( Integer.parseInt( portField.getText() ) );
				}
				catch( NumberFormatException e )
				{
				}
			}
		} );

		toolbar.addFixed( portField );
	}

	private JComponent buildQueryMatchPanel()
	{
		JPanel panel = new JPanel( new BorderLayout() );
		panel.add( buildQueryMatchToolbar(), BorderLayout.NORTH );
		JSplitPane splitPane = UISupport.createHorizontalSplit( buildQueryEditor(), buildMatchEditor() );
		panel.add( splitPane, BorderLayout.CENTER );
		splitPane.setDividerLocation( 200 );
		return panel;
	}

	private Component buildMatchEditor()
	{
		JPanel panel = new JPanel( new BorderLayout() );

		matchEditorModel = new ModelItemPropertyEditorModel<WsdlMockResponseTestStep>( getModelItem(), "match" );
		panel.add( UISupport.getEditorFactory().buildXmlEditor( matchEditorModel ), BorderLayout.CENTER );

		UISupport.addTitledBorder( panel, "Matching Value" );

		return panel;
	}

	private Component buildQueryEditor()
	{
		JPanel panel = new JPanel( new BorderLayout() );

		queryEditorModel = new ModelItemPropertyEditorModel<WsdlMockResponseTestStep>( getModelItem(), "query" );
		panel.add( UISupport.getEditorFactory().buildXPathEditor( queryEditorModel ), BorderLayout.CENTER );

		UISupport.addTitledBorder( panel, "XPath Query" );

		return panel;
	}

	protected JXToolBar buildQueryMatchToolbar()
	{
		JXToolBar toolBar = UISupport.createSmallToolbar();
		toolBar.addFixed( new JButton( new SelectFromCurrentAction() ) );
		return toolBar;
	}

	public class SelectFromCurrentAction extends AbstractAction
	{
		public SelectFromCurrentAction()
		{
			super( "Select from current" );
			putValue( Action.SHORT_DESCRIPTION, "Selects the Query XPath expression from the last request Match field" );
		}

		public void actionPerformed( ActionEvent arg0 )
		{
			if( getModelItem().getLastResult() != null && getModelItem().getLastResult().getMockRequest() != null
					&& StringUtils.hasContent( getModelItem().getQuery() ) )
			{
				getModelItem().setMatch(
						XmlUtils.getXPathValue( getModelItem().getLastResult().getMockRequest().getRequestContent(),
								PropertyExpander.expandProperties( getModelItem(), getModelItem().getQuery() ) ) );
			}
		}
	}

	private AssertionsPanel buildAssertionsPanel()
	{
		assertionsPanel = new AssertionsPanel( getModelItem() )
		{
			protected void selectError( AssertionError error )
			{
				ModelItemXmlEditor<?, ?> editor = getResponseEditor();
				editor.requestFocus();
			}
		};

		return assertionsPanel;
	}

	@Override
	public boolean onClose( boolean canCancel )
	{
		getModelItem().getTestCase().removeTestRunListener( testRunListener );
		SoapUI.getTestMonitor().removeTestMonitorListener( testMonitorListener );
		assertionsPanel.release();

		queryEditorModel.release();
		matchEditorModel.release();

		inspectorPanel.release();

		getModelItem().removeAssertionsListener( assertionsListener );
		return super.onClose( canCancel );
	}

	public void setEnabled( boolean enabled )
	{
		super.setEnabled( enabled );

		pathField.setEnabled( enabled );
		portField.setEnabled( enabled );
	}

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

	public class InternalTestRunListener extends TestRunListenerAdapter
	{
		@Override
		public void afterRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
		{
			setEnabled( true );
		}

		@Override
		public void beforeRun( TestCaseRunner testRunner, TestCaseRunContext runContext )
		{
			setEnabled( false );
		}

		@Override
		public void beforeStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStep testStep )
		{
			if( testStep == getModelItem() )
			{
				logArea.setText( logArea.getText() + new Date( System.currentTimeMillis() ).toString()
						+ ": Waiting for request on http://127.0.0.1:" + getModelItem().getPort() + getModelItem().getPath()
						+ "\r\n" );
			}
		}

		@Override
		public void afterStep( TestCaseRunner testRunner, TestCaseRunContext runContext, TestStepResult result )
		{
			if( result.getTestStep() == getModelItem() )
			{
				String msg = new Date( result.getTimeStamp() ).toString() + ": Handled request in " + result.getTimeTaken()
						+ "ms";
				logArea.setText( logArea.getText() + msg + "\r\n" );
			}
		}
	}

	private class InternalTestMonitorListener extends TestMonitorListenerAdapter
	{
		public void loadTestFinished( LoadTestRunner runner )
		{
			setEnabled( !SoapUI.getTestMonitor().hasRunningTest( getModelItem().getTestCase() ) );
		}

		public void loadTestStarted( LoadTestRunner runner )
		{
			if( runner.getLoadTest().getTestCase() == getModelItem().getTestCase() )
				setEnabled( false );
		}

		public void securityTestFinished( SecurityTestRunner runner )
		{
			setEnabled( !SoapUI.getTestMonitor().hasRunningTest( getModelItem().getTestCase() ) );
		}

		public void securityTestStarted( SecurityTestRunner runner )
		{
			if( runner.getSecurityTest().getTestCase() == getModelItem().getTestCase() )
				setEnabled( false );
		}

		public void testCaseFinished( TestCaseRunner runner )
		{
			setEnabled( !SoapUI.getTestMonitor().hasRunningTest( getModelItem().getTestCase() ) );
		}

		public void testCaseStarted( TestCaseRunner runner )
		{
			if( runner.getTestCase() == getModelItem().getTestCase() )
				setEnabled( false );
		}
	}

	public void propertyChange( PropertyChangeEvent evt )
	{
		super.propertyChange( evt );

		if( evt.getPropertyName().equals( WsdlMockResponseTestStep.STATUS_PROPERTY ) )
			updateStatusIcon();
	}

	@SuppressWarnings( "unused" )
	private final class DeclareNamespacesAction extends AbstractAction
	{
		public DeclareNamespacesAction()
		{
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/declareNs.gif" ) );
			putValue( Action.SHORT_DESCRIPTION,
					"Declare available response/request namespaces in source/target expressions" );
		}

		public void actionPerformed( ActionEvent e )
		{
			try
			{
				WsdlMockResult lastResult = getMockResponse().getMockResult();
				String content = null;
				if( lastResult == null )
				{
					if( !UISupport.confirm( "Missing last result, declare from default request instead?",
							"Declare Namespaces" ) )
					{
						return;
					}

					content = getMockResponse().getMockOperation().getOperation().createRequest( true );
				}
				else
				{
					content = lastResult.getMockRequest().getRequestContent();
				}

				String path = getModelItem().getQuery();
				if( path == null )
					path = "";

				getModelItem().setQuery( XmlUtils.declareXPathNamespaces( content ) + path );
			}
			catch( Exception e1 )
			{
				UISupport.showErrorMessage( e1 );
			}
		}
	}

	private final class InternalAssertionsListener implements AssertionsListener
	{
		public void assertionAdded( TestAssertion assertion )
		{
			assertionInspector.setTitle( "Assertions (" + getModelItem().getAssertionCount() + ")" );
		}

		public void assertionRemoved( TestAssertion assertion )
		{
			assertionInspector.setTitle( "Assertions (" + getModelItem().getAssertionCount() + ")" );
		}

		public void assertionMoved( TestAssertion assertion, int ix, int offset )
		{
			assertionInspector.setTitle( "Assertions (" + getModelItem().getAssertionCount() + ")" );
		}
	}
}
