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

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragSource;
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.Arrays;
import java.util.HashMap;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;

import com.eviware.soapui.impl.wsdl.WsdlTestSuite;
import com.eviware.soapui.impl.wsdl.actions.testsuite.AddNewTestCaseAction;
import com.eviware.soapui.impl.wsdl.panels.support.ProgressBarTestCaseAdapter;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestCase;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.support.TestSuiteListenerAdapter;
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.action.swing.SwingActionDelegate;
import com.eviware.soapui.support.dnd.DropType;
import com.eviware.soapui.support.dnd.SoapUIDragAndDropHandler;
import com.eviware.soapui.support.dnd.SoapUIDragAndDropable;
import com.eviware.soapui.support.swing.AutoscrollSupport;

/**
 * A panel showing a scrollable list of TestCases in a TestSuite.
 * 
 * @author Ole.Matzura
 */

public class JTestSuiteTestCaseList extends JPanel
{
	private Map<TestCase, TestCaseListPanel> panels = new HashMap<TestCase, TestCaseListPanel>();
	private final WsdlTestSuite testSuite;
	private final InternalTestSuiteListener testSuiteListener = new InternalTestSuiteListener();
	private TestCaseListPanel selectedTestCase;

	public JTestSuiteTestCaseList( WsdlTestSuite testSuite )
	{
		this.testSuite = testSuite;
		setLayout( new BoxLayout( this, BoxLayout.Y_AXIS ) );

		for( int c = 0; c < testSuite.getTestCaseCount(); c++ )
		{
			TestCaseListPanel testCaseListPanel = createTestCaseListPanel( testSuite.getTestCaseAt( c ) );
			panels.put( testSuite.getTestCaseAt( c ), testCaseListPanel );
			add( testCaseListPanel );
		}

		add( Box.createVerticalGlue() );
		setBackground( Color.WHITE );

		testSuite.addTestSuiteListener( testSuiteListener );

		ActionList actions = ActionListBuilder.buildActions( testSuite );
		actions.removeAction( 0 );
		actions.removeAction( 0 );
		setComponentPopupMenu( ActionSupport.buildPopup( actions ) );

		DragSource dragSource = DragSource.getDefaultDragSource();

		SoapUIDragAndDropHandler dragAndDropHandler = new SoapUIDragAndDropHandler(
				new TestCaseListDragAndDropable( this ), DropType.AFTER );

		dragSource.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_COPY_OR_MOVE, dragAndDropHandler );
	}

	public void reset()
	{
		for( TestCaseListPanel testCasePanel : panels.values() )
		{
			testCasePanel.reset();
		}
	}

	@Override
	public void addNotify()
	{
		super.addNotify();
		testSuite.addTestSuiteListener( testSuiteListener );
	}

	@Override
	public void removeNotify()
	{
		super.removeNotify();
		testSuite.removeTestSuiteListener( testSuiteListener );
	}

	private final class InternalTestSuiteListener extends TestSuiteListenerAdapter
	{
		public void testCaseAdded( TestCase testCase )
		{
			TestCaseListPanel testCaseListPanel = createTestCaseListPanel( testCase );
			panels.put( testCase, testCaseListPanel );
			add( testCaseListPanel, testCase.getTestSuite().getIndexOfTestCase( testCase ) );
			revalidate();
			repaint();
		}

		public void testCaseRemoved( TestCase testCase )
		{
			TestCaseListPanel testCaseListPanel = panels.get( testCase );
			if( testCaseListPanel != null )
			{
				remove( testCaseListPanel );
				panels.remove( testCase );
				revalidate();
				repaint();
			}
		}

		@Override
		public void testCaseMoved( TestCase testCase, int index, int offset )
		{
			TestCaseListPanel testCaseListPanel = panels.get( testCase );
			if( testCaseListPanel != null )
			{
				boolean hadFocus = testCaseListPanel.hasFocus();

				remove( testCaseListPanel );
				add( testCaseListPanel, index + offset );

				revalidate();
				repaint();

				if( hadFocus )
					testCaseListPanel.requestFocus();
			}
		}
	}

	public final class TestCaseListPanel extends JPanel implements Autoscroll
	{
		private final WsdlTestCase testCase;
		private JProgressBar progressBar;
		private JLabel label;
		private ProgressBarTestCaseAdapter progressBarAdapter;
		private TestCasePropertyChangeListener testCasePropertyChangeListener;
		private AutoscrollSupport autoscrollSupport;

		public TestCaseListPanel( WsdlTestCase testCase )
		{
			super( new BorderLayout() );

			setFocusable( true );

			this.testCase = testCase;
			autoscrollSupport = new AutoscrollSupport( this );

			progressBar = new JProgressBar( 0, 100 )
			{
				protected void processMouseEvent( MouseEvent e )
				{
					if( e.getID() == MouseEvent.MOUSE_PRESSED || e.getID() == MouseEvent.MOUSE_RELEASED )
					{
						TestCaseListPanel.this.processMouseEvent( translateMouseEvent( e ) );
					}
				}

				protected void processMouseMotionEvent( MouseEvent e )
				{
					TestCaseListPanel.this.processMouseMotionEvent( translateMouseEvent( e ) );
				}

				/**
				 * Translates the given mouse event to the enclosing map panel's
				 * coordinate space.
				 */
				private MouseEvent translateMouseEvent( MouseEvent e )
				{
					return new MouseEvent( TestCaseListPanel.this, e.getID(), e.getWhen(), e.getModifiers(), e.getX()
							+ getX(), e.getY() + getY(), e.getClickCount(), e.isPopupTrigger(), e.getButton() );
				}
			};

			JPanel progressPanel = UISupport.createProgressBarPanel( progressBar, 5, false );

			progressBar.setMinimumSize( new Dimension( 0, 10 ) );
			progressBar.setBackground( Color.WHITE );
			progressBar.setInheritsPopupMenu( true );

			label = new JLabel( testCase.getLabel() );
			label.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) );
			label.setInheritsPopupMenu( true );
			label.setEnabled( !testCase.isDisabled() );

			add( progressPanel, BorderLayout.CENTER );
			add( label, BorderLayout.NORTH );

			testCasePropertyChangeListener = new TestCasePropertyChangeListener();

			initPopup( testCase );

			addMouseListener( new MouseAdapter()
			{

				@Override
				public void mousePressed( MouseEvent e )
				{
					requestFocus();
				}

				public void mouseClicked( MouseEvent e )
				{
					if( e.getClickCount() < 2 )
					{
						if( selectedTestCase != null )
							selectedTestCase.setSelected( false );

						setSelected( true );
						selectedTestCase = TestCaseListPanel.this;
						return;
					}

					UISupport.selectAndShow( TestCaseListPanel.this.testCase );
				}
			} );

			addKeyListener( new TestCaseListPanelKeyHandler() );

			// init border
			setSelected( false );
		}

		public void reset()
		{
			progressBar.setValue( 0 );
			progressBar.setString( "" );
		}

		private void initPopup( WsdlTestCase testCase )
		{
			ActionList actions = ActionListBuilder.buildActions( testCase );
			actions.insertAction(
					SwingActionDelegate.createDelegate( AddNewTestCaseAction.SOAPUI_ACTION_ID, testSuite, null, null ), 0 );
			actions.insertAction( ActionSupport.SEPARATOR_ACTION, 1 );

			setComponentPopupMenu( ActionSupport.buildPopup( actions ) );
		}

		public void addNotify()
		{
			super.addNotify();
			testCase.addPropertyChangeListener( testCasePropertyChangeListener );
			progressBarAdapter = new ProgressBarTestCaseAdapter( progressBar, testCase );
		}

		public void removeNotify()
		{
			super.removeNotify();
			if( progressBarAdapter != null )
			{
				testCase.removePropertyChangeListener( testCasePropertyChangeListener );
				progressBarAdapter.release();

				progressBarAdapter = null;
			}
		}

		public Dimension getMaximumSize()
		{
			Dimension size = super.getMaximumSize();
			size.height = 50;
			return size;
		}

		public void setSelected( boolean selected )
		{
			if( selected )
			{
				setBorder( BorderFactory.createLineBorder( Color.GRAY ) );
			}
			else
			{
				setBorder( BorderFactory.createLineBorder( Color.WHITE ) );
			}
		}

		public boolean isSelected()
		{
			return selectedTestCase != null && selectedTestCase.getTestCase() == testCase;
		}

		private final class TestCasePropertyChangeListener implements PropertyChangeListener
		{
			public void propertyChange( PropertyChangeEvent evt )
			{
				if( evt.getPropertyName().equals( TestCase.LABEL_PROPERTY ) )
				{
					label.setEnabled( !testCase.isDisabled() );
					label.setText( testCase.getLabel() );
				}
				else if( evt.getPropertyName().equals( TestCase.DISABLED_PROPERTY ) )
				{
					initPopup( testCase );
				}
			}
		}

		protected TestCase getTestCase()
		{
			return testCase;
		}

		public ModelItem getModelItem()
		{
			return testCase;
		}

		public void autoscroll( Point pt )
		{
			int ix = getIndexOf( this );
			if( pt.getY() < 12 && ix > 0 )
			{
				Rectangle bounds = JTestSuiteTestCaseList.this.getComponent( ix - 1 ).getBounds();
				JTestSuiteTestCaseList.this.scrollRectToVisible( bounds );
			}
			else if( pt.getY() > getHeight() - 12 && ix < testSuite.getTestCaseCount() - 1 )
			{
				Rectangle bounds = JTestSuiteTestCaseList.this.getComponent( ix + 1 ).getBounds();
				JTestSuiteTestCaseList.this.scrollRectToVisible( bounds );
			}
		}

		public Insets getAutoscrollInsets()
		{
			return autoscrollSupport.getAutoscrollInsets();
		}

		private final class TestCaseListPanelKeyHandler extends KeyAdapter
		{
			public void keyPressed( KeyEvent e )
			{
				if( e.getKeyChar() == KeyEvent.VK_ENTER )
				{
					UISupport.selectAndShow( testCase );
					e.consume();
				}
				else
				{
					ActionList actions = ActionListBuilder.buildActions( testCase );
					if( actions != null )
						actions.dispatchKeyEvent( e );
				}
			}
		}
	}

	protected int getIndexOf( TestCaseListPanel panel )
	{
		return Arrays.asList( getComponents() ).indexOf( panel );
	}

	protected TestCaseListPanel createTestCaseListPanel( TestCase testCase )
	{
		TestCaseListPanel testCaseListPanel = new TestCaseListPanel( ( WsdlTestCase )testCase );

		DragSource dragSource = DragSource.getDefaultDragSource();

		SoapUIDragAndDropHandler dragAndDropHandler = new SoapUIDragAndDropHandler( new TestCaseListPanelDragAndDropable(
				testCaseListPanel ), DropType.BEFORE_AND_AFTER );

		dragSource.createDefaultDragGestureRecognizer( testCaseListPanel, DnDConstants.ACTION_COPY_OR_MOVE,
				dragAndDropHandler );

		return testCaseListPanel;
	}

	private class TestCaseListDragAndDropable implements SoapUIDragAndDropable<ModelItem>
	{
		private final JTestSuiteTestCaseList list;

		public TestCaseListDragAndDropable( JTestSuiteTestCaseList list )
		{
			this.list = list;
		}

		public JComponent getComponent()
		{
			return list;
		}

		public Rectangle getModelItemBounds( ModelItem modelItem )
		{
			return list.getBounds();
		}

		public ModelItem getModelItemForLocation( int x, int y )
		{
			int testCaseCount = testSuite.getTestCaseCount();
			return testCaseCount == 0 ? testSuite : testSuite.getTestCaseAt( testCaseCount - 1 );
		}

		public Component getRenderer( ModelItem modelItem )
		{
			return null;
		}

		public void selectModelItem( ModelItem modelItem )
		{
		}

		public void setDragInfo( String dropInfo )
		{
			list.setToolTipText( dropInfo );
		}

		public void toggleExpansion( ModelItem modelItem )
		{
		}
	}

	private static class TestCaseListPanelDragAndDropable implements SoapUIDragAndDropable<ModelItem>
	{
		private final TestCaseListPanel testCasePanel;

		public TestCaseListPanelDragAndDropable( TestCaseListPanel testCasePanel )
		{
			this.testCasePanel = testCasePanel;
		}

		public JComponent getComponent()
		{
			return testCasePanel;
		}

		public void setDragInfo( String dropInfo )
		{
			testCasePanel.setToolTipText( dropInfo.length() == 0 ? null : dropInfo );
		}

		public Rectangle getModelItemBounds( ModelItem path )
		{
			return new Rectangle( testCasePanel.getSize() );
		}

		public ModelItem getModelItemForLocation( int x, int y )
		{
			return testCasePanel.getModelItem();
		}

		public Component getRenderer( ModelItem path )
		{
			return null;
		}

		public void selectModelItem( ModelItem path )
		{
			testCasePanel.setSelected( !testCasePanel.isSelected() );
		}

		public void toggleExpansion( ModelItem last )
		{
		}
	}
}
