/**
 * File:    GridHandler.java
 * Created: 30.01.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Christian Saad, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.editors.actions.handlers;

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jwt.we.editors.actions.WEActionHandler;
import org.eclipse.jwt.we.editors.pages.activityEditor.internal.IActivityEditor;
import org.eclipse.jwt.we.editors.preferences.PreferenceReader;
import org.eclipse.jwt.we.misc.util.GeneralHelper;
import org.eclipse.jwt.we.parts.core.NodeModelElementEditPart;
import org.eclipse.jwt.we.parts.processes.ScopeEditPart;
import org.eclipse.ui.IWorkbenchPart;


/**
 * A handler for the grid actions in the menu. It acts as one of four possible actions
 * depending on the menu element that called it.
 * 
 * @version $Id
 * @author Christian Saad (csaad@gmx.net), Programming distributed Systems Lab, University
 *         of Augsburg, Germany, www.ds-lab.org
 */
public final class GridHandler
		extends WEActionHandler
{

	/**
	 * Internal snap state.
	 */
	private boolean gridSnap;

	/**
	 * Internal visibility state.
	 */
	private boolean gridVisible;

	/**
	 * This constants identify the current function of this handler
	 */
	private final int GRID_VISIBILITY = 0;
	private final int GRID_SNAP_AUTOMATICALLY = 1;
	private final int GRID_SNAP_NOW = 2;
	private final int GRID_SNAP_NOW_CENTERED = 3;


	/**
	 * Constructor.
	 */
	public GridHandler()
	{
		super(true);

		// read snap/visible state from preference reader
		gridSnap = PreferenceReader.gridSnap.get();
		gridVisible = PreferenceReader.gridVisible.get();
	}


	/**
	 * Refresh enabled state if selection has changed. Only enabled when an activity
	 * editor is active.
	 */
	@Override
	public void selectionChanged(IWorkbenchPart part, ISelection selection)
	{
		// set to false if no JWT instance is active
		if (GeneralHelper.getActiveInstance() == null
				|| !(GeneralHelper.getActiveInstance().getActiveEditor() instanceof IActivityEditor))
		{
			setEnabled(false);
			return;
		}

		setEnabled(true);
	}


	/**
	 * Returns the type of the command.
	 * 
	 * @param event
	 * @return Command type.
	 */
	@SuppressWarnings("nls")
	private int getCommandType(ExecutionEvent event)
	{
		// get the name of the command
		String commandId = ""; //$NON-NLS-1$

		try
		{
			commandId = event.getCommand().getId();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return -1;
		}

		// check which command was called and return corresponding type
		if (commandId.equals("org.eclipse.jwt.we.gridSnapCommand")) //$NON-NLS-1$
		{
			return GRID_SNAP_AUTOMATICALLY;
		}
		if (commandId.equals("org.eclipse.jwt.we.gridVisibleCommand")) //$NON-NLS-1$
		{
			return GRID_VISIBILITY;
		}
		if (commandId.equals("org.eclipse.jwt.we.gridSnapNowCornerCommand")) //$NON-NLS-1$
		{
			return GRID_SNAP_NOW;
		}
		if (commandId.equals("org.eclipse.jwt.we.gridSnapNowCenteredCommand")) //$NON-NLS-1$
		{
			return GRID_SNAP_NOW_CENTERED;
		}

		return 0;
	}


	/**
	 * Toggle the grid snap state.
	 */
	private void toggleGridSnap()
	{
		// set internal state
		gridSnap = !gridSnap;

		// set preference state (grid snap is set by preferencechangelistener)
		PreferenceReader.gridSnap.set(gridSnap);
	}


	/**
	 * Toggle the grid visibility state.
	 */
	private void toggleGridVisible()
	{
		// set internal state
		gridVisible = !gridVisible;

		// set preference state (grid visibility is set by preferencechangelistener)
		PreferenceReader.gridVisible.set(gridVisible);
	}


	/**
	 * Takes a rectangular constraint and returns a rectangle which is aligned to a grid
	 * with the given properties.
	 * 
	 * @param constraint
	 * @param gridWidth
	 * @param gridHeight
	 * @return Aligned Rectangle.
	 */
	private Rectangle alignToGridCenteredHelp(Rectangle constraint, int gridWidth,
			int gridHeight)
	{
		Rectangle result = new Rectangle(constraint);

		// the center of the rectangle
		int middleX = result.x + (result.width / 2);
		int middleY = result.y + (result.height / 2);

		// align x coordinate to the grid
		int x_mod = middleX % gridWidth;
		middleX -= x_mod;

		if (x_mod > gridWidth / 2)
		{
			middleX += gridWidth;
		}

		// align y coordinate to the grid
		int y_mod = middleY % gridHeight;
		middleY -= y_mod;

		if (y_mod > gridHeight / 2)
		{
			middleY += gridHeight;
		}

		result.x = middleX - (result.width / 2);
		result.y = middleY - (result.height / 2);

		// return aligned rectangle
		return result;
	}


	/**
	 * Align all elements of current activity to the grid.
	 */
	private void alignToGridCentered()
	{
		// get scope edit part
		EditPart rootEditPart = GeneralHelper.getActiveInstance()
				.getCurrentActivitySheet().getRootEditPart().getContents();

		if (!(rootEditPart instanceof ScopeEditPart))
		{
			return;
		}

		ScopeEditPart scopeEditPart = (ScopeEditPart) rootEditPart;

		// get grid properties
		int gridWidth = PreferenceReader.gridWidth.get();
		int gridHeight = PreferenceReader.gridHeight.get();

		CompoundCommand compoundCommand = new CompoundCommand();

		// align all children
		for (int i = 0; i < scopeEditPart.getChildren().size(); i++)
		{
			if (scopeEditPart.getChildren().get(i) instanceof NodeModelElementEditPart)
			{
				// get edit part
				NodeModelElementEditPart nodeEditPart = (NodeModelElementEditPart) scopeEditPart
						.getChildren().get(i);

				// calculate new constraint
				Rectangle oldConstraint = nodeEditPart.getFigure().getBounds();
				Rectangle newConstraint = alignToGridCenteredHelp(oldConstraint,
						gridWidth, gridHeight);
				int deltaX = newConstraint.x - oldConstraint.x;
				int deltaY = newConstraint.y - oldConstraint.y;

				// get the move command and add to compound command
				ChangeBoundsRequest request = new ChangeBoundsRequest(
						RequestConstants.REQ_RESIZE_CHILDREN);
				request.setMoveDelta(new Point(deltaX, deltaY));
				request.setEditParts(nodeEditPart);
				compoundCommand.add(scopeEditPart.getCommand(request));
			}
		}

		// execute all commands
		GeneralHelper.getActiveInstance().getEditDomain().getCommandStack().execute(
				compoundCommand);
	}


	/**
	 * Takes a rectangular constraint and returns a rectangle which is aligned to a grid
	 * with the given properties.
	 * 
	 * @param constraint
	 * @param gridWidth
	 * @param gridHeight
	 * @return Aligned Rectangle.
	 */
	private Rectangle alignToGridCornerHelp(Rectangle constraint, int gridWidth,
			int gridHeight)
	{
		Rectangle result = new Rectangle(constraint);
		// align x coordinate to the grid
		int x_mod = result.x % gridWidth;
		result.x -= x_mod;

		if (x_mod > gridWidth / 2)
		{
			result.x += gridWidth;
		}

		// align y coordinate to the grid
		int y_mod = result.y % gridHeight;
		result.y -= y_mod;

		if (y_mod > gridHeight / 2)
		{
			result.y += gridHeight;
		}

		// return aligned rectangle
		return result;
	}


	/**
	 * Align all elements of current activity to the grid.
	 */
	private void alignToGridCorner()
	{
		// get scope edit part
		EditPart rootEditPart = GeneralHelper.getActiveInstance()
				.getCurrentActivitySheet().getRootEditPart().getContents();

		if (!(rootEditPart instanceof ScopeEditPart))
		{
			return;
		}

		ScopeEditPart scopeEditPart = (ScopeEditPart) rootEditPart;

		// get grid properties

		int gridWidth = PreferenceReader.gridWidth.get();
		int gridHeight = PreferenceReader.gridHeight.get();

		CompoundCommand compoundCommand = new CompoundCommand();

		// align all children
		for (int i = 0; i < scopeEditPart.getChildren().size(); i++)
		{
			if (scopeEditPart.getChildren().get(i) instanceof NodeModelElementEditPart)
			{
				// get edit part
				NodeModelElementEditPart nodeEditPart = (NodeModelElementEditPart) scopeEditPart
						.getChildren().get(i);

				// calculate new constraint
				Rectangle oldConstraint = nodeEditPart.getFigure().getBounds();
				Rectangle newConstraint = alignToGridCornerHelp(oldConstraint, gridWidth,
						gridHeight);
				int deltaX = newConstraint.x - oldConstraint.x;
				int deltaY = newConstraint.y - oldConstraint.y;

				// get the move command and add to compound command
				ChangeBoundsRequest request = new ChangeBoundsRequest(
						RequestConstants.REQ_RESIZE_CHILDREN);
				request.setMoveDelta(new Point(deltaX, deltaY));
				request.setEditParts(nodeEditPart);
				compoundCommand.add(scopeEditPart.getCommand(request));
			}
		}

		// execute all commands
		GeneralHelper.getActiveInstance().getEditDomain().getCommandStack().execute(
				compoundCommand);
	}


	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.commands.AbstractHandler#execute(org.eclipse.core.commands.ExecutionEvent)
	 */
	public Object execute(ExecutionEvent event) throws ExecutionException
	{

		// get command type
		int commandType = getCommandType(event);

		if (commandType == -1)
		{
			return null;
		}

		// execute
		switch (commandType)
		{
			case GRID_VISIBILITY:
				toggleGridVisible();
				break;
			case GRID_SNAP_AUTOMATICALLY:
				toggleGridSnap();
				break;
			case GRID_SNAP_NOW:
				alignToGridCorner();
				break;
			case GRID_SNAP_NOW_CENTERED:
				alignToGridCentered();
				break;
		}

		return null;
	}

}