/*
 *  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.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.teststeps.actions.AddAssertionAction;
import com.eviware.soapui.model.ModelItem;
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.TestAssertion;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.action.swing.ActionList;
import com.eviware.soapui.support.action.swing.ActionListBuilder;
import com.eviware.soapui.support.action.swing.ActionSupport;
import com.eviware.soapui.support.components.JXToolBar;

/**
 * Seperate panel for holding/managing assertions
 * 
 * @author ole.matzura
 */

public class AssertionsPanel extends JPanel
{
	protected AssertionListModel assertionListModel;
	protected JList assertionList;
	private JPopupMenu assertionListPopup;
	private Assertable assertable;
	private AddAssertionAction addAssertionAction;
	private ConfigureAssertionAction configureAssertionAction;
	private RemoveAssertionAction removeAssertionAction;
	private MoveAssertionUpAction moveAssertionUpAction;
	private MoveAssertionDownAction moveAssertionDownAction;

	public AssertionsPanel( Assertable assertable )
	{
		super( new BorderLayout() );
		this.assertable = assertable;

		initListAndModel();

		assertionList.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );

		assertionListPopup = new JPopupMenu();
		addAssertionAction = new AddAssertionAction( assertable );
		assertionListPopup.add( addAssertionAction );

		assertionListPopup.addPopupMenuListener( new PopupMenuListener()
		{

			public void popupMenuWillBecomeVisible( PopupMenuEvent e )
			{
				while( assertionListPopup.getComponentCount() > 1 )
					assertionListPopup.remove( 1 );

				int ix = assertionList.getSelectedIndex();
				if( ix == -1 )
				{
					assertionListPopup.addSeparator();
					assertionListPopup.add( new ShowOnlineHelpAction( getHelpUrl() ) );
					return;
				}
				int[] indices = assertionList.getSelectedIndices();
				if( indices.length == 1 )
				{
					TestAssertion assertion = assertionListModel.getAssertionAt( ix );
					ActionSupport.addActions( ActionListBuilder.buildActions( assertion ), assertionListPopup );

				}
				else
				{
					TestAssertion[] testAssertion = new TestAssertion[indices.length];
					for( int c = 0; c < indices.length; c++ )
						testAssertion[c] = assertionListModel.getAssertionAt( indices[c] );

					ActionSupport.addActions( ActionListBuilder.buildMultiActions( testAssertion ), assertionListPopup );
				}
			}

			public void popupMenuWillBecomeInvisible( PopupMenuEvent e )
			{
			}

			public void popupMenuCanceled( PopupMenuEvent e )
			{
			}
		} );

		assertionList.setComponentPopupMenu( assertionListPopup );

		assertionList.addMouseListener( new MouseAdapter()
		{

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

				int ix = assertionList.getSelectedIndex();
				if( ix == -1 )
					return;

				Object obj = assertionList.getModel().getElementAt( ix );
				if( obj instanceof TestAssertion )
				{
					TestAssertion assertion = ( TestAssertion )obj;
					if( assertion.isConfigurable() )
						assertion.configure();

					return;
				}

				if( obj instanceof AssertionError )
				{
					AssertionError error = ( AssertionError )obj;
					if( error.getLineNumber() >= 0 )
					{
						selectError( error );
					}
					else
						Toolkit.getDefaultToolkit().beep();
				}
				else
					Toolkit.getDefaultToolkit().beep();
			}
		} );

		assertionList.addKeyListener( new KeyAdapter()
		{
			public void keyPressed( KeyEvent e )
			{
				int ix = assertionList.getSelectedIndex();
				if( ix == -1 )
					return;

				int[] indices = assertionList.getSelectedIndices();
				if( indices.length == 1 )
				{
					TestAssertion assertion = assertionListModel.getAssertionAt( ix );
					if( e.getKeyChar() == KeyEvent.VK_ENTER )
					{
						if( assertion.isConfigurable() )
							assertion.configure();
					}
					else
					{
						ActionList actions = ActionListBuilder.buildActions( assertion );
						if( actions != null )
						{
							actions.dispatchKeyEvent( e );
						}
					}
				}
				else
				{
					TestAssertion[] testAssertion = new TestAssertion[indices.length];
					for( int c = 0; c < indices.length; c++ )
					{
						testAssertion[c] = assertionListModel.getAssertionAt( indices[c] );
					}

					ActionList actions = ActionListBuilder.buildMultiActions( testAssertion );
					ActionSupport.addActions( actions, assertionListPopup );

					if( actions != null )
					{
						actions.dispatchKeyEvent( e );
					}
				}
			}
		} );

		add( new JScrollPane( assertionList ), BorderLayout.CENTER );
		add( buildToolbar(), BorderLayout.NORTH );
	}

	/**
	 * 
	 */
	protected void initListAndModel()
	{
		assertionListModel = new AssertionListModel();
		assertionList = new JList( assertionListModel );
		assertionList.setToolTipText( "Assertions for this request" );
		assertionList.setCellRenderer( new AssertionCellRenderer() );
	}

	private JComponent buildToolbar()
	{
		configureAssertionAction = new ConfigureAssertionAction();
		removeAssertionAction = new RemoveAssertionAction();
		moveAssertionUpAction = new MoveAssertionUpAction();
		moveAssertionDownAction = new MoveAssertionDownAction();

		JXToolBar toolbar = UISupport.createToolbar();
		addToolbarButtons( toolbar );

		toolbar.addGlue();
		toolbar.add( new ShowOnlineHelpAction( getHelpUrl() ) );

		assertionList.addListSelectionListener( new ListSelectionListener()
		{

			public void valueChanged( ListSelectionEvent e )
			{
				int ix = assertionList.getSelectedIndex();

				configureAssertionAction.setEnabled( ix >= 0 );
				removeAssertionAction.setEnabled( ix >= 0 );
				moveAssertionUpAction.setEnabled( ix >= 0 );
				moveAssertionDownAction.setEnabled( ix >= 0 );

				if( ix == -1 )
					return;
				TestAssertion assertion = assertionListModel.getAssertionAt( ix );
				configureAssertionAction.setEnabled( assertion != null && assertion.isConfigurable() );
			}
		} );

		return toolbar;
	}

	protected void addToolbarButtons( JXToolBar toolbar )
	{
		toolbar.addFixed( UISupport.createToolbarButton( addAssertionAction ) );
		toolbar.addFixed( UISupport.createToolbarButton( configureAssertionAction ) );
		toolbar.addFixed( UISupport.createToolbarButton( removeAssertionAction ) );
		toolbar.addFixed( UISupport.createToolbarButton( moveAssertionUpAction ) );
		toolbar.addFixed( UISupport.createToolbarButton( moveAssertionDownAction ) );
	}

	public void setEnabled( boolean enabled )
	{
		assertionList.setEnabled( enabled );
	}

	protected void selectError( AssertionError error )
	{
	}

	private static class AssertionCellRenderer extends JLabel implements ListCellRenderer
	{
		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
				boolean cellHasFocus )
		{
			setEnabled( list.isEnabled() );

			if( value instanceof TestAssertion )
			{
				TestAssertion assertion = ( TestAssertion )value;
				setText( assertion.getLabel() + " - " + assertion.getStatus().toString() );
				setIcon( assertion.getIcon() );

				if( assertion.isDisabled() && isEnabled() )
					setEnabled( false );
			}
			else if( value instanceof AssertionError )
			{
				AssertionError assertion = ( AssertionError )value;
				setText( " -> " + assertion.toString() );
				setIcon( null );
			}
			else if( value instanceof String )
			{
				setText( value.toString() );
			}

			if( isSelected )
			{
				setBackground( list.getSelectionBackground() );
				setForeground( list.getSelectionForeground() );
			}
			else
			{
				setBackground( list.getBackground() );
				setForeground( list.getForeground() );
			}

			setFont( list.getFont() );
			setOpaque( true );

			return this;
		}
	}

	protected class AssertionListModel extends AbstractListModel implements PropertyChangeListener, AssertionsListener
	{
		protected List<Object> items = new ArrayList<Object>();

		public AssertionListModel()
		{
			init();
		}

		public int getSize()
		{
			return items.size();
		}

		public Object getElementAt( int index )
		{
			return index >= items.size() ? null : items.get( index );
		}

		public TestAssertion getAssertionAt( int index )
		{
			Object object = items.get( index );
			while( !( object instanceof TestAssertion ) && index > 0 )
			{
				object = items.get( --index );
			}

			return ( TestAssertion )( ( object instanceof TestAssertion ) ? object : null );
		}

		public void refresh()
		{
			synchronized( this )
			{
				release();
				init();
				fireContentsChanged( this, 0, getSize() - 1 );
			}
		}

		private void init()
		{
			assertable.addAssertionsListener( this );

			for( int c = 0; c < assertable.getAssertionCount(); c++ )
			{
				TestAssertion assertion = assertable.getAssertionAt( c );
				addAssertion( assertion );
			}
		}

		public void release()
		{
			items.clear();

			for( int c = 0; c < assertable.getAssertionCount(); c++ )
			{
				TestAssertion assertion = assertable.getAssertionAt( c );
				assertion.removePropertyChangeListener( this );
			}

			assertable.removeAssertionsListener( this );
		}

		public synchronized void propertyChange( PropertyChangeEvent evt )
		{
			if( SwingUtilities.isEventDispatchThread() )
				refresh();
			else
				SwingUtilities.invokeLater( new Runnable()
				{

					public void run()
					{
						refresh();
					}
				} );
		}

		public void assertionAdded( TestAssertion assertion )
		{
			synchronized( this )
			{
				int sz = getSize();
				addAssertion( assertion );

				fireIntervalAdded( this, sz, items.size() - 1 );
			}
		}

		protected void addAssertion( TestAssertion assertion )
		{
			assertion.addPropertyChangeListener( this );
			items.add( assertion );

			AssertionError[] errors = assertion.getErrors();
			if( errors != null )
			{
				for( int i = 0; i < errors.length; i++ )
					items.add( errors[i] );
			}
		}

		public void assertionRemoved( TestAssertion assertion )
		{
			synchronized( this )
			{
				int ix = items.indexOf( assertion );
				if( ix == -1 )
					return;

				assertion.removePropertyChangeListener( this );
				items.remove( ix );
				fireIntervalRemoved( this, ix, ix );

				// remove associated errors
				while( ix < items.size() && items.get( ix ) instanceof AssertionError )
				{
					items.remove( ix );
					fireIntervalRemoved( this, ix, ix );
				}
			}
		}

		public void assertionMoved( TestAssertion newAssertion, int ix, int offset )
		{
			synchronized( this )
			{
				// int ix = items.indexOf( assertion );
				TestAssertion assertion = ( TestAssertion )items.get( ix );
				// if first selected can't move up and if last selected can't move
				// down
				if( ( ix == 0 && offset == -1 ) || ( ix == items.size() - 1 && offset == 1 ) )
				{
					return;
				}

				assertion.removePropertyChangeListener( this );
				items.remove( ix );
				fireIntervalRemoved( this, ix, ix );

				// remove associated errors
				while( ix < items.size() && items.get( ix ) instanceof AssertionError )
				{
					items.remove( ix );
					fireIntervalRemoved( this, ix, ix );
				}
				newAssertion.addPropertyChangeListener( this );
				items.add( ix + offset, newAssertion );
				fireIntervalAdded( this, ix + offset, ix + offset );
				// add associated errors
				while( ix < items.size() && items.get( ix ) instanceof AssertionError )
				{
					items.add( newAssertion );
					fireIntervalAdded( this, ix + offset, ix + offset );
				}
			}
		}

	}

	public void release()
	{
		assertionListModel.release();
		addAssertionAction.release();
		assertable = null;
	}

	public class ConfigureAssertionAction extends AbstractAction
	{
		ConfigureAssertionAction()
		{
			super( "Configure" );
			putValue( Action.SHORT_DESCRIPTION, "Configures the selection assertion" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/options.gif" ) );
			setEnabled( false );
		}

		public void actionPerformed( ActionEvent e )
		{
			int ix = assertionList.getSelectedIndex();
			if( ix == -1 )
				return;

			TestAssertion assertion = assertionListModel.getAssertionAt( ix );
			if( assertion.isConfigurable() )
			{
				assertion.configure();
			}
			else
				Toolkit.getDefaultToolkit().beep();
		}
	}

	public class RemoveAssertionAction extends AbstractAction
	{
		public RemoveAssertionAction()
		{
			super( "Remove Assertion" );
			putValue( Action.SHORT_DESCRIPTION, "Removes the selected assertion" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/remove_assertion.gif" ) );
			setEnabled( false );
		}

		public void actionPerformed( ActionEvent e )
		{

			List<TestAssertion> removeAssertionList = new ArrayList<TestAssertion>();
			int indices[] = assertionList.getSelectedIndices();

			if( indices.length == 0 )
				return;

			if( hasRunningTestCase( indices[0] ) )
				return;

			for( int i : indices )
			{
				removeAssertionList.add( assertionListModel.getAssertionAt( i ) );
			}

			if( removeAssertionList.size() == 1 )
			{
				int selectedIndex = assertionList.getSelectedIndex();
				removeSingleAssertion( removeAssertionList.get( 0 ) );
				if( assertionList.getLastVisibleIndex() >= selectedIndex )
				{
					assertionList.setSelectedIndex( selectedIndex );
				}
			}
			else
			{
				removeMultipleAssertions( removeAssertionList );
			}

		}

		private boolean hasRunningTestCase( int assertionIndex )
		{
			if( assertionListModel.getAssertionAt( assertionIndex ).getParent().getParent() instanceof TestCase )
				if( SoapUI.getTestMonitor().hasRunningTestCase(
						( TestCase )assertionListModel.getAssertionAt( assertionIndex ).getParent().getParent() ) )
				{
					UISupport.showInfoMessage( "Can not remove assertion(s) while test case is running" );
					return true;
				}
			if( assertionListModel.getAssertionAt( assertionIndex ).getParent().getParent().getParent() instanceof TestCase )
				if( SoapUI.getTestMonitor().hasRunningSecurityTest(
						( TestCase )assertionListModel.getAssertionAt( assertionIndex ).getParent().getParent().getParent() ) )
				{
					UISupport.showInfoMessage( "Can not remove assertion(s) while test case is running" );
					return true;
				}
			return false;
		}

		private void removeMultipleAssertions( List<TestAssertion> removeAssertionList )
		{
			if( UISupport.confirm( "Remove all selected assertions?", "Remove Multiple Assertions" ) )
			{
				// remove duplicates
				Set<TestAssertion> assertions = new HashSet<TestAssertion>();

				for( ModelItem target : removeAssertionList )
				{
					assertions.add( ( TestAssertion )target );
				}

				for( TestAssertion assertion : assertions )
				{
					( ( Assertable )assertion.getParent() ).removeAssertion( assertion );
				}
			}
		}

		private void removeSingleAssertion( TestAssertion assertion )
		{
			if( UISupport.confirm( "Remove assertion [" + assertion.getName() + "]", "Remove Assertion" ) )
			{
				assertable.removeAssertion( assertion );
			}
		}
	}

	private class MoveAssertionUpAction extends AbstractAction
	{
		public MoveAssertionUpAction()
		{
			super( "Move Assertion Up" );
			putValue( Action.SHORT_DESCRIPTION, "Moves selected asertion up one row" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/up_arrow.gif" ) );
			setEnabled( false );

		}

		public void actionPerformed( ActionEvent e )
		{
			int ix = assertionList.getSelectedIndex();
			TestAssertion assertion = assertionListModel.getAssertionAt( ix );
			if( ix != -1 )
			{
				assertion = assertable.moveAssertion( ix, -1 );
			}
			assertionList.setSelectedValue( assertion, true );
		}
	}

	private class MoveAssertionDownAction extends AbstractAction
	{
		public MoveAssertionDownAction()
		{
			super( "Move Assertion Down" );
			putValue( Action.SHORT_DESCRIPTION, "Moves selected asertion down one row" );
			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/down_arrow.gif" ) );
			setEnabled( false );

		}

		public void actionPerformed( ActionEvent e )
		{
			int ix = assertionList.getSelectedIndex();
			TestAssertion assertion = assertionListModel.getAssertionAt( ix );
			if( ix != -1 )
			{
				assertion = assertable.moveAssertion( ix, 1 );
			}
			assertionList.setSelectedValue( assertion, true );
		}
	}

	public JList getAssertionsList()
	{
		return assertionList;
	}

	public String getHelpUrl()
	{
		return HelpUrls.RESPONSE_ASSERTIONS_HELP_URL;
	}
}
