package easik.model.states;

//~--- non-JDK imports --------------------------------------------------------
import java.util.ArrayList;
//~--- JDK imports ------------------------------------------------------------
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;

import org.jgraph.graph.GraphConstants;

import easik.graph.EasikGraphModel;
import easik.model.Model;
import easik.model.edge.ModelEdge;
import easik.model.path.ModelPath;
import easik.model.ui.ModelFrame;
import easik.model.vertex.ModelVertex;

/**
 * The GetPathState is a state which can be used to retrieve a single path from
 * the user. It ensures that the only edges which are selectable are those which
 * are attached to the previous edge.
 *
 * @author Rob Fletcher 2005
 * @author Kevin Green 2006
 * @author Vera Ranieri 2006
 * @version 2006-07-13 Kevin Green
 * @version 06-2014 Federico Mora
 */
public class GetPathState<F extends ModelFrame<F, GM, M, N, E>, GM extends EasikGraphModel, M extends Model<F, GM, M, N, E>, N extends ModelVertex<F, GM, M, N, E>, E extends ModelEdge<F, GM, M, N, E>>
		extends ModelState<F, GM, M, N, E> {
	/** Stores whether the next state is the finish state */
	protected boolean _finishState;

	/** Stores whether the next state is to add a new path */
	protected boolean _nextState;

	/** The path created from the edges chosen */
	protected ModelPath<F, GM, M, N, E> _pathToReturn;

	/** Contains elements of the sketch that are selectable */
	@SuppressWarnings("rawtypes")
	protected Map _selectable;

	/** Stores the desired start node */
	protected N _sourceNode;

	/** Stores the desired target node */
	protected N _targetNode;

	/** Contains elements of the sketch that are not selectable */
	@SuppressWarnings("rawtypes")
	protected Map _unselectable;

	/**
	 * Default Constructor
	 *
	 * @param next     boolean determining whether the user should be allowed to
	 *                 select next on this round. True if next could be selected,
	 *                 false otherwise.
	 * @param finish   boolean determining whether the user should be allowed to
	 *                 select finish on this round. True if finish could be
	 *                 selected, false otherwise.
	 * @param inSketch the sketch in which this is occurring
	 */
	public GetPathState(boolean next, boolean finish, M inModel) {
		this(next, finish, inModel, null, null);
	}

	/**
	 * Gets a path that must begin at the specified source node.
	 *
	 * @param next       boolean determining whether the user should be allowed to
	 *                   select next on this round. True if next could be selected,
	 *                   false otherwise.
	 * @param finish     boolean determining whether the user should be allowed to
	 *                   select finish on this round. True if finish could be
	 *                   selected, false otherwise.
	 * @param inSketch   the sketch in which this is occurring
	 * @param sourceNode N at which the path must begin; only edges with this node
	 *                   as source will be initially selectable. null means the path
	 *                   can start anywhere.
	 * @param targetNode N at which the path must end; until a path ending at
	 *                   targetNode is selected, the user will be unable to click
	 *                   next/finish. null means the path can end anywhere.
	 */
	public GetPathState(boolean next, boolean finish, M inModel, N sourceNode, N targetNode) {
		super(inModel);

		_pathToReturn = null;
		_nextState = next;
		_finishState = finish;
		_sourceNode = sourceNode;
		_targetNode = targetNode;
	}

	/**
	 * When pushed on, the first thing done is clearing the selection and then
	 * disabling selection for all items except for edges.
	 */
	@Override
	@SuppressWarnings("rawtypes")
	public void pushedOn() {
		setNextButton(false);
		setCancelButton(true);
		setFinishButton(false);

		_selectable = new Hashtable();
		_unselectable = new Hashtable();

		GraphConstants.setSelectable(_selectable, true);
		GraphConstants.setSelectable(_unselectable, false);

		// Initially, we allow all edges and entities to be selected
		_ourModel.clearSelection();
		_ourModel.refresh();

		// Gets pushed on by all "add constraint" states. We don't want to be
		// able to imbed adding constraints
		_ourModel.getFrame().enableAddConstraintItems(false);
	}

	/**
	 * Returns the edges that are to be selectable. Only edges extending from the
	 * current path are selectable; if there is no path, then any edge qualifies.
	 *
	 * @return
	 */
	@Override
	public Object[] getSelectables() {
		ArrayList<Object> selectable = new ArrayList<>();
		/*
		 * if we are allowing the creation of edges and nodes during the constraint
		 * creation time then everything has to be selectable
		 * 
		 */

		for (Object cell : _ourModel.getRoots()) {
			selectable.add(cell);
		}

		return selectable.toArray();

	}

	/**
	 * Update the selection so that the only selectable items will be those within
	 * reach of the existing edges.
	 */
	@Override
	public void selectionUpdated() {
		_ourModel.refresh();

		Object[] newSelection = _ourModel.getSelectionCells();

		// First check to see if the selection is empty
		if (newSelection.length == 0) {
			// We can't have a next/finish button available:
			setNextButton(false);
			setFinishButton(false);
		} else {
			// make sure selection consists of edges
			for (Object o : newSelection) {
				if (!(o instanceof ModelEdge)) {
					return;
				}
			}

			// If we have an explicit target, you can hit next only if the
			// selected path codomain matches
			@SuppressWarnings("unchecked")
			boolean codoOK = (_targetNode == null) ? true // No target node:
															// always okay
					: ((E) newSelection[newSelection.length - 1]).getTargetEntity() == _targetNode;

			// Since we have something selected, then we can enable the 'next'
			// button, if it should be.
			setNextButton(_nextState && codoOK);

			// If the user can finish this round, then the Finish button should
			// be activated.
			setFinishButton(_finishState && codoOK);
		}
	}

	/**
	 * When the state gets popped, then it should tell the new top item what path it
	 * had collected before being popped. Since this is called AFTER popping, it can
	 * use peek() to get the top item.
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void poppedOff() {
		((PathAcceptingState<F, GM, M, N, E>) _ourModel.getStateManager().peekState()).passPath(_pathToReturn);
		_ourModel.getGraphLayoutCache().reload();
		_ourModel.clearSelection();
	}

	/**
	 * If path collection has been cancelled, then pop off, and set the path to be
	 * null.
	 */
	@Override
	public void cancelClicked() {
		_pathToReturn = null;

		resetSelection();
		_ourModel.getStateManager().popState();
	}

	/**
	 * When next is clicked, pop off after preparing an array containing the edges
	 * in the path. Convert to the proper graph edge of the sketch.
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void nextClicked() {
		resetSelection();

		LinkedList<E> path = new LinkedList<>();
		Object[] selCells = _ourModel.getSelectionCells();

		if ((selCells.length == 1) && (selCells[0] instanceof ModelVertex)) {
			_pathToReturn = new ModelPath<>((N) selCells[0]);
		} else {
			for (int i = 0; i < selCells.length; i++) {
				path.add((E) selCells[i]);
			}

			_pathToReturn = new ModelPath<>(path);
		}

		_ourModel.getStateManager().popState();
	}

	/**
	 * When finish is clicked, the stack is popped off after an array containing the
	 * path is created.
	 */
	@Override
	public void finishClicked() {
		nextClicked();
	}

	/**
	 * State string identifier.
	 *
	 * @return String literal "Select a path"
	 */
	@Override
	public String toString() {
		return "Select a path";
	}

	/**
	 * Set everything to be selectable
	 */
	private void resetSelection() {
		_ourModel.getGraphLayoutCache().edit(_ourModel.getRoots(), _selectable);
	}
}