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

import java.awt.event.ActionEvent;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Iterator;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

import com.eviware.soapui.impl.WorkspaceImpl;
import com.eviware.soapui.impl.actions.ImportWsdlProjectAction;
import com.eviware.soapui.impl.actions.SwitchWorkspaceAction;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.iface.Interface;
import com.eviware.soapui.model.iface.Operation;
import com.eviware.soapui.model.iface.Request;
import com.eviware.soapui.model.mock.MockOperation;
import com.eviware.soapui.model.mock.MockResponse;
import com.eviware.soapui.model.mock.MockService;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.support.WorkspaceListenerAdapter;
import com.eviware.soapui.model.testsuite.LoadTest;
import com.eviware.soapui.model.testsuite.TestCase;
import com.eviware.soapui.model.testsuite.TestStep;
import com.eviware.soapui.model.testsuite.TestSuite;
import com.eviware.soapui.model.workspace.Workspace;
import com.eviware.soapui.model.workspace.WorkspaceListener;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.action.SoapUIActionMapping;
import com.eviware.soapui.support.action.support.DefaultActionMapping;
import com.eviware.soapui.support.action.swing.SwingActionDelegate;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.soapui.ui.desktop.DesktopListener;
import com.eviware.soapui.ui.desktop.DesktopPanel;

/**
 * Workspace/Deskopt Listener that updates the recent menus..
 * 
 * @author ole.matzura
 */

public class RecentItemsListener extends WorkspaceListenerAdapter implements WorkspaceListener, DesktopListener
{
	private static final String RECENT_WORKSPACES_SETTING = "RecentWorkspaces";
	private static final String RECENT_PROJECTS_SETTING = "RecentProjects";
	private static final String EMPTYMARKER = "- empty -";
	private static final String CLEAR_ITEMS = "Clear Items";
	private JMenu recentProjectsMenu;
	private JMenu recentWorkspacesMenu;
	private JMenu recentEditorsMenu;
	private boolean switchingWorkspace;

	public RecentItemsListener( JMenu recentWorkspacesMenu2, JMenu recentProjectsMenu2, JMenu recentEditorsMenu2 )
	{
		recentWorkspacesMenu = recentWorkspacesMenu2;
		recentProjectsMenu = recentProjectsMenu2;
		recentEditorsMenu = recentEditorsMenu2;
		recentEditorsMenu.add( EMPTYMARKER ).setEnabled( false );
		recentEditorsMenu.getPopupMenu().addPopupMenuListener( new PopupMenuListener()
		{

			public void popupMenuCanceled( PopupMenuEvent e )
			{
			}

			public void popupMenuWillBecomeInvisible( PopupMenuEvent e )
			{
			}

			public void popupMenuWillBecomeVisible( PopupMenuEvent e )
			{
				int editorCount = 0;
				for( int c = 0; c < recentEditorsMenu.getItemCount(); c++ )
				{
					ShowEditorAction action = getShowEditorAction( recentEditorsMenu, c );
					if( action == null )
						continue;

					editorCount++ ;

					if( action.isReleased() )
					{
						recentEditorsMenu.remove( c );
						c-- ;
					}
					else
					{
						try
						{
							action.update();
						}
						catch( Throwable e1 )
						{
							recentEditorsMenu.remove( c );
							c-- ;
						}
					}
				}

				// Look for a Clear Items JMenuItem, and create it if not existing
				JMenuItem clearAllItem = null;
				for( int i = 0; i < recentEditorsMenu.getItemCount(); ++i )
				{
					JMenuItem editorItem = recentEditorsMenu.getItem( i );
					if( editorItem == null )
						continue;

					if( editorItem.getText().equals( CLEAR_ITEMS ) )
						clearAllItem = editorItem;
				}
				if( clearAllItem == null )
				{
					clearAllItem = new JMenuItem( new ClearEditorsAction() );
					recentEditorsMenu.addSeparator();
					recentEditorsMenu.add( clearAllItem );
				}

				clearAllItem.setEnabled( editorCount > 0 );

				// Create Empty Marker if needed.
				if( editorCount == 0 && recentEditorsMenu.getItemCount() <= 2 )
					recentEditorsMenu.add( new JMenuItem( EMPTYMARKER ), 0 ).setEnabled( false );

			}
		} );

		updateRecentWorkspacesMenu();
		updateRecentProjectsMenu();
	}

	@SuppressWarnings( "unchecked" )
	private void updateRecentWorkspacesMenu()
	{
		String recent = SoapUI.getSettings().getString( RECENT_WORKSPACES_SETTING, null );
		StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml( recent );

		recentWorkspacesMenu.removeAll();

		if( history.size() > 0 )
		{
			for( Iterator<String> i = history.keySet().iterator(); i.hasNext(); )
			{
				String filePath = i.next();
				DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
						SwitchWorkspaceAction.SOAPUI_ACTION_ID, null, null, false, filePath );
				String wsName = history.get( filePath );

				if( SoapUI.getWorkspace().getPath().equals( filePath ) )
					continue;

				mapping.setName( wsName );
				mapping.setDescription( "Switches to the [" + wsName + "] workspace" );

				AbstractAction delegate = new SwingActionDelegate( mapping, SoapUI.getWorkspace() );
				recentWorkspacesMenu.add( new JMenuItem( delegate ) );
			}
		}
		else
		{
			recentWorkspacesMenu.add( EMPTYMARKER ).setEnabled( false );
		}

		recentWorkspacesMenu.addSeparator();
		recentWorkspacesMenu.add( new ClearWorkspacesAction() ).setEnabled( history.size() > 0 );
	}

	@SuppressWarnings( "unchecked" )
	private void updateRecentProjectsMenu()
	{
		recentProjectsMenu.removeAll();

		String recent = SoapUI.getSettings().getString( RECENT_PROJECTS_SETTING, null );
		StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml( recent );

		if( history.size() > 0 )
		{
			for( Iterator<String> i = history.keySet().iterator(); i.hasNext(); )
			{
				String filePath = i.next();
				DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
						ImportWsdlProjectAction.SOAPUI_ACTION_ID, null, null, false, filePath );
				String wsName = history.get( filePath );
				mapping.setName( wsName );
				mapping.setDescription( "Switches to the [" + wsName + "] project" );

				AbstractAction delegate = new SwingActionDelegate( mapping, SoapUI.getWorkspace() );
				recentProjectsMenu.add( new JMenuItem( delegate ) );
			}
		}
		else
		{
			recentProjectsMenu.add( EMPTYMARKER ).setEnabled( false );
		}

		recentProjectsMenu.addSeparator();
		recentProjectsMenu.add( new ClearProjectsAction() ).setEnabled( history.size() > 0 );
	}

	@SuppressWarnings( "unchecked" )
	public void projectAdded( Project project )
	{
		if( switchingWorkspace )
			return;

		String filePath = ( ( WsdlProject )project ).getPath();
		if( filePath == null )
			return;

		String recent = SoapUI.getSettings().getString( RECENT_PROJECTS_SETTING, null );
		if( recent != null )
		{
			StringToStringMap history = StringToStringMap.fromXml( recent );
			history.remove( filePath );
			SoapUI.getSettings().setString( RECENT_PROJECTS_SETTING, history.toXml() );
		}

		for( int c = 0; c < recentProjectsMenu.getItemCount() - 2; c++ )
		{
			JMenuItem item = recentProjectsMenu.getItem( c );
			if( item == null )
				continue;

			Action action = item.getAction();
			if( !( action instanceof SwingActionDelegate ) )
				continue;

			SwingActionDelegate actionDelegate = ( SwingActionDelegate )action;
			if( actionDelegate == null )
				continue;

			SoapUIActionMapping mapping = actionDelegate.getMapping();
			if( filePath.equals( mapping.getParam() ) )
			{
				recentProjectsMenu.remove( c );
				break;
			}
		}

		if( recentProjectsMenu.getItemCount() == 2 )
		{
			recentProjectsMenu.add( new JMenuItem( EMPTYMARKER ), 0 ).setEnabled( false );
			recentProjectsMenu.getItem( recentProjectsMenu.getItemCount() - 1 ).setEnabled( false );
		}
	}

	public void projectChanged( Project project )
	{
	}

	@SuppressWarnings( "unchecked" )
	public void projectRemoved( Project project )
	{
		if( switchingWorkspace )
			return;

		String filePath = ( ( WsdlProject )project ).getPath();

		String recent = SoapUI.getSettings().getString( RECENT_PROJECTS_SETTING, null );
		StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml( recent );
		history.put( filePath, project.getName() );
		SoapUI.getSettings().setString( RECENT_PROJECTS_SETTING, history.toXml() );

		DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
				ImportWsdlProjectAction.SOAPUI_ACTION_ID, null, null, false, filePath );
		mapping.setName( project.getName() );
		mapping.setDescription( "Switches to the [" + project.getName() + "] project" );

		AbstractAction delegate = new SwingActionDelegate( mapping, SoapUI.getWorkspace() );

		recentProjectsMenu.add( new JMenuItem( delegate ), recentProjectsMenu.getItemCount() - 2 );

		recentProjectsMenu.getItem( recentProjectsMenu.getItemCount() - 1 ).setEnabled( true );

		if( isEmptyMarker( recentProjectsMenu.getItem( 0 ) ) )
			recentProjectsMenu.remove( 0 );

		removeProjectEditors( project );

	}

	private void removeProjectEditors( Project project )
	{
		for( int c = 0; c < recentEditorsMenu.getItemCount(); c++ )
		{
			ShowEditorAction action = getShowEditorAction( recentEditorsMenu, c );
			if( action == null )
				continue;

			if( action.isReleased() )
			{
				recentEditorsMenu.remove( c );
				c-- ;
			}
			else
			{
				try
				{
					action.update();
					if( dependsOnProject( action.getModelItem(), project ) )
					{
						recentEditorsMenu.remove( c );
						c-- ;
					}
				}
				catch( Throwable e1 )
				{
					recentEditorsMenu.remove( c );
					c-- ;
				}
			}
		}
	}

	private boolean dependsOnProject( ModelItem modelItem, Project project )
	{
		if( modelItem instanceof Interface )
		{
			return ( ( Interface )modelItem ).getProject() == project;
		}
		else if( modelItem instanceof Operation )
		{
			return ( ( Operation )modelItem ).getInterface().getProject() == project;
		}
		else if( modelItem instanceof Request )
		{
			return ( ( Request )modelItem ).getOperation().getInterface().getProject() == project;
		}
		else if( modelItem instanceof TestSuite )
		{
			return ( ( TestSuite )modelItem ).getProject() == project;
		}
		else if( modelItem instanceof TestCase )
		{
			return ( ( TestCase )modelItem ).getTestSuite().getProject() == project;
		}
		else if( modelItem instanceof TestStep )
		{
			return ( ( TestStep )modelItem ).getTestCase().getTestSuite().getProject() == project;
		}
		else if( modelItem instanceof LoadTest )
		{
			return ( ( LoadTest )modelItem ).getTestCase().getTestSuite().getProject() == project;
		}
		else if( modelItem instanceof MockService )
		{
			return ( ( MockService )modelItem ).getProject() == project;
		}
		else if( modelItem instanceof MockOperation )
		{
			return ( ( MockOperation )modelItem ).getMockService().getProject() == project;
		}
		else if( modelItem instanceof MockResponse )
		{
			return ( ( MockResponse )modelItem ).getMockOperation().getMockService().getProject() == project;
		}

		return false;
	}

	@SuppressWarnings( "unchecked" )
	public void workspaceSwitched( Workspace workspace )
	{
		switchingWorkspace = false;

		String filePath = workspace.getPath();

		String recent = SoapUI.getSettings().getString( RECENT_WORKSPACES_SETTING, null );
		if( recent != null )
		{
			StringToStringMap history = StringToStringMap.fromXml( recent );
			history.remove( filePath );
			SoapUI.getSettings().setString( RECENT_WORKSPACES_SETTING, history.toXml() );
		}

		for( int c = 0; c < recentWorkspacesMenu.getItemCount(); c++ )
		{
			JMenuItem item = recentWorkspacesMenu.getItem( c );
			if( item == null )
				continue;

			Action action = item.getAction();
			if( !( action instanceof SwingActionDelegate ) )
				continue;

			SwingActionDelegate actionDelegate = ( SwingActionDelegate )action;
			if( actionDelegate == null )
				continue;

			SoapUIActionMapping mapping = actionDelegate.getMapping();
			if( filePath.equals( mapping.getParam() ) )
			{
				recentWorkspacesMenu.remove( c );
				break;
			}
		}

		if( recentWorkspacesMenu.getItemCount() == 2 )
			recentWorkspacesMenu.add( new JMenuItem( EMPTYMARKER ), 0 ).setEnabled( false );
	}

	@SuppressWarnings( "unchecked" )
	public void workspaceSwitching( Workspace workspace )
	{
		switchingWorkspace = true;
		recentEditorsMenu.removeAll();
		if( recentEditorsMenu.getItemCount() == 0 )
			recentEditorsMenu.add( EMPTYMARKER ).setEnabled( false );

		String filePath = workspace.getPath();
		DefaultActionMapping<WorkspaceImpl> mapping = new DefaultActionMapping<WorkspaceImpl>(
				SwitchWorkspaceAction.SOAPUI_ACTION_ID, null, null, false, filePath );
		mapping.setName( workspace.getName() );
		mapping.setDescription( "Switches to the [" + workspace.getName() + "] workspace" );

		AbstractAction delegate = new SwingActionDelegate( mapping, SoapUI.getWorkspace() );
		recentWorkspacesMenu.add( new JMenuItem( delegate ), recentWorkspacesMenu.getItemCount() - 2 );

		recentWorkspacesMenu.getItem( recentWorkspacesMenu.getItemCount() - 1 ).setEnabled( true );
		System.out.println( recentWorkspacesMenu.getItem( recentWorkspacesMenu.getItemCount() - 1 ).getText() );

		String recent = SoapUI.getSettings().getString( RECENT_WORKSPACES_SETTING, null );
		StringToStringMap history = recent == null ? new StringToStringMap() : StringToStringMap.fromXml( recent );
		history.put( filePath, workspace.getName() );
		SoapUI.getSettings().setString( RECENT_WORKSPACES_SETTING, history.toXml() );

		if( isEmptyMarker( recentWorkspacesMenu.getItem( 0 ) ) )
			recentWorkspacesMenu.remove( 0 );

		recentEditorsMenu.removeAll();
	}

	public void desktopPanelClosed( DesktopPanel desktopPanel )
	{
		ModelItem modelItem = desktopPanel.getModelItem();
		if( modelItem == null || recentEditorsMenu.getItemCount() == 0 )
			return;

		if( isEmptyMarker( recentEditorsMenu.getItem( 0 ) ) )
			recentEditorsMenu.remove( 0 );

		recentEditorsMenu.add( new JMenuItem( new ShowEditorAction( modelItem ) ), 0 );
	}

	public void desktopPanelCreated( DesktopPanel desktopPanel )
	{
		for( int c = 0; c < recentEditorsMenu.getItemCount(); c++ )
		{
			ShowEditorAction action = getShowEditorAction( recentEditorsMenu, c );
			if( action == null )
				continue;

			if( action.isReleased() )
			{
				recentEditorsMenu.remove( c );
				c-- ;
			}
			else if( action.getModelItem().equals( desktopPanel.getModelItem() ) )
			{
				recentEditorsMenu.remove( c );
				break;
			}
		}

		if( recentEditorsMenu.getItemCount() == 2 )
			recentEditorsMenu.add( new JMenuItem( EMPTYMARKER ), 0 ).setEnabled( false );
	}

	public void desktopPanelSelected( DesktopPanel desktopPanel )
	{
	}

	private static class ShowEditorAction extends AbstractAction
	{
		private Reference<ModelItem> ref;

		public ShowEditorAction( ModelItem modelItem )
		{
			super( modelItem.getName() );

			putValue( Action.SHORT_DESCRIPTION, "Reopen editor for [" + modelItem.getName() + "]" );
			ref = new WeakReference<ModelItem>( modelItem );
		}

		public ModelItem getModelItem()
		{
			return ref.get();
		}

		public void update()
		{
			ModelItem modelItem = ref.get();
			if( modelItem == null )
				return;

			putValue( Action.NAME, modelItem.getName() );
			putValue( Action.SHORT_DESCRIPTION, "Reopen editor for [" + modelItem.getName() + "]" );
		}

		public boolean isReleased()
		{
			return ref.get() == null;
		}

		public void actionPerformed( ActionEvent e )
		{
			ModelItem modelItem = ref.get();
			if( modelItem != null )
				UISupport.showDesktopPanel( modelItem );
			else
				UISupport.showErrorMessage( "Item [" + getValue( Action.NAME ) + "] is no longer available" );
		}
	}

	/* Helper methods */

	/**
	 * Confirms that the item of index i in menu is a ShowEditorAction, and
	 * extracts the ShowEditorAction.
	 * 
	 * @param menu
	 *           The menu to get the JMenuItem, containing the action from.
	 * @param i
	 *           The index where the JMenuItem is located in menu.
	 * @return The ShowEditorAction if confirmed. Otherwise null.
	 */
	private ShowEditorAction getShowEditorAction( JMenu menu, int i )
	{
		JMenuItem menuItem = recentEditorsMenu.getItem( i );
		if( menuItem == null )
			return null;

		Action unknownAction = menuItem.getAction();

		if( unknownAction == null || !( unknownAction instanceof ShowEditorAction ) )
			return null;

		return ( ShowEditorAction )unknownAction;
	}

	/**
	 * Checks whether a JMenuItem is an Empty marker (marking an empty list).
	 * 
	 * @param item
	 *           The item to check.
	 * @return True if item is an Empty marker. False otherwise (including if
	 *         item is null).
	 */
	private boolean isEmptyMarker( JMenuItem item )
	{
		if( item == null )
			return false;
		if( item.getText().equals( EMPTYMARKER ) )
			return true;
		return false;
	}

	/*
	 * Action classes for clearing all items in the Recent
	 * Editors/Projects/Workspaces submenu
	 */

	@SuppressWarnings( "serial" )
	private class ClearProjectsAction extends AbstractAction
	{
		public ClearProjectsAction()
		{
			super( CLEAR_ITEMS );
			putValue( Action.SHORT_DESCRIPTION, "Clear all recent projects" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( !UISupport.confirm( "Remove all Projects from this menu?", "Question" ) )
				return;

			StringToStringMap emptyMap = new StringToStringMap();

			SoapUI.getSettings().setString( RECENT_PROJECTS_SETTING, emptyMap.toXml() );
			updateRecentProjectsMenu();

		}
	}

	@SuppressWarnings( "serial" )
	private class ClearWorkspacesAction extends AbstractAction
	{
		public ClearWorkspacesAction()
		{
			super( CLEAR_ITEMS );
			putValue( Action.SHORT_DESCRIPTION, "Clear all recent workspaces" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( !UISupport.confirm( "Remove all Workspaces from this menu?", "Question" ) )
				return;

			StringToStringMap emptyMap = new StringToStringMap();

			SoapUI.getSettings().setString( RECENT_WORKSPACES_SETTING, emptyMap.toXml() );
			updateRecentWorkspacesMenu();

		}
	}

	@SuppressWarnings( "serial" )
	private class ClearEditorsAction extends AbstractAction
	{
		public ClearEditorsAction()
		{
			super( CLEAR_ITEMS );
			putValue( Action.SHORT_DESCRIPTION, "Clear all recent Editors" );
		}

		public void actionPerformed( ActionEvent e )
		{
			if( !UISupport.confirm( "Remove all Editors from this menu?", "Question" ) )
				return;

			recentEditorsMenu.removeAll();
			recentEditorsMenu.add( EMPTYMARKER ).setEnabled( false );
			recentEditorsMenu.addSeparator();
			recentEditorsMenu.add( new ClearEditorsAction() );
		}
	}

}
