/**
 * Copyright 2010-2013 Jingjing Li.
 *
 * This file is part of jplot2d.
 *
 * jplot2d is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 *
 * jplot2d 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 General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jplot2d. If not, see .
 */
package org.jplot2d.env;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.jplot2d.element.Element;
import org.jplot2d.element.PComponent;
import org.jplot2d.element.Plot;
import org.jplot2d.element.impl.ComponentEx;
import org.jplot2d.element.impl.ElementEx;
import org.jplot2d.element.impl.IntermediateCacheEx;
import org.jplot2d.element.impl.PlotEx;
import org.jplot2d.notice.LoggingNotifier;
import org.jplot2d.notice.Notifier;
import org.jplot2d.renderer.CacheableBlock;
import org.jplot2d.transform.PaperTransform;

/**
 * This Environment can host a plot instance and provide undo/redo ability.
 * 
 * @author Jingjing Li
 * 
 */
public class PlotEnvironment extends Environment {

	/**
	 * Keep hard references to cache holder objects.
	 */
	@SuppressWarnings("unused")
	private List cacheHolders;

	/**
	 * the key is impl element, the value is copy of element (for renderer thread safe)
	 */
	protected Map copyMap = new HashMap();

	/**
	 * A copy to proxy map that contains all element in this environment.
	 */
	protected Map copyProxyMap;

	protected final UndoManager undoManager = new UndoManager(Integer.MAX_VALUE);

	/**
	 * Contains all visible cacheable components in z-order. include uncacheable root plot.
	 */
	protected final List cacheBlockList = new ArrayList();

	/**
	 * The plot proxy
	 */
	protected volatile Plot plot;

	protected PlotEx plotImpl;

	protected PlotEx plotCopy;

	public PlotEnvironment(boolean threadSafe) {
		super(threadSafe);
	}

	/**
	 * Sets a plot to this environment. The plot must hosted by a dummy environment. All notices are logged to java
	 * logging facilities.
	 * 
	 * @param plot
	 */
	public void setPlot(Plot plot) {
		setPlot(plot, LoggingNotifier.getInstance());
	}

	/**
	 * Sets a plot to this environment. The plot must hosted by a dummy environment.
	 * 
	 * @param plot
	 * @param notifier
	 *            the notifier to receive and process notices
	 */
	public void setPlot(Plot plot, Notifier notifier) {
		Environment oldEnv;
		synchronized (getGlobalLock()) {
			// remove the env of the given plot
			oldEnv = plot.getEnvironment();
			if (!(oldEnv instanceof DummyEnvironment)) {
				throw new IllegalArgumentException("The plot to be added has been added a PlotEnvironment");
			}

			// check this environment is ready to host plot
			beginCommand("setPlot");

			if (this.plot != null) {
				endCommand();
				throw new IllegalArgumentException("This Environment has hosted a plot");
			}

			oldEnv.beginCommand("setPlot");

			// update environment for all adding components
			for (Element proxy : oldEnv.proxyMap.values()) {
				((ElementAddition) proxy).setEnvironment(this);
			}
		}

		this.plot = plot;
		this.plotImpl = (PlotEx) ((ElementAddition) plot).getImpl();

		componentAdded(plotImpl, oldEnv);

		this.notifier = notifier;
		plotImpl.setNotifier(notifier);
		plotImpl.setRerenderNeeded(true);

		oldEnv.endCommand();
		endCommand();
	}

	public Plot getPlot() {
		return plot;
	}

	/**
	 * Returns the notifier of this environment. The notifier will receive notices during command execution and process
	 * all notices at once when command finished.
	 * 
	 * @return the notifier of this environment
	 */
	public Notifier getNotifier() {
		Notifier result = null;
		begin();
		result = notifier;
		end();
		return result;
	}

	/**
	 * Sets the notifier of this environment. The notifier will receive notices during command execution and process all
	 * notices at once when command finished.
	 * 
	 * @param notifier
	 *            the notifier to be used in this environment
	 */
	public void setNotifier(Notifier notifier) {
		begin();
		this.notifier = notifier;
		plotImpl.setNotifier(notifier);
		end();
	}

	@Override
	protected void commit() {
		plotImpl.commit();
		fireChangeProcessed();

		// create cache holder
		List holders = new ArrayList();
		for (ElementEx element : proxyMap.keySet()) {
			if (element instanceof IntermediateCacheEx) {
				Object cacheHolder = ((IntermediateCacheEx) element).createCacheHolder();
				if (cacheHolder != null) {
					holders.add(cacheHolder);
				}
			}
		}
		this.cacheHolders = holders;

		// fill copyMap and copyProxyMap
		makeUndoMemento();
		// fill cacheBlockList
		buildComponentCacheBlock();

		render();
	}

	/**
	 * When a command is committed, this method is called to generate a rendering result.
	 */
	protected void render() {

	}

	protected void fireChangeProcessed() {
		ElementChangeListener[] ls = getElementChangeListeners();
		if (ls.length > 0) {
			ElementChangeEvent evt = new ElementChangeEvent(this, null);
			for (ElementChangeListener lsnr : ls) {
				lsnr.propertyChangesProcessed(evt);
			}
		}
	}

	/**
	 * Sort components in cacheable blocks.
	 */
	protected void buildComponentCacheBlock() {
		/*
		 * Contains all visible cacheable components in z-order. include uncacheable root plot.
		 */
		List cacheableComponentList = new ArrayList();

		/*
		 * The key is copy of cacheable components or uncacheable root plot; the value is copy of key's visible
		 * uncacheable descendants, include the key itself, in z-order.
		 */
		Map> subcompsMap = new HashMap>();

		cacheBlockList.clear();

		for (ElementEx element : proxyMap.keySet()) {
			if (element instanceof ComponentEx) {
				ComponentEx comp = (ComponentEx) element;
				ComponentEx copy = (ComponentEx) copyMap.get(comp);
				if (isShowing(copy)) {
					ComponentEx cacheableAncestor;

					if (comp.isCacheable() || comp == plotImpl) {
						cacheableComponentList.add((ComponentEx) element);
						cacheableAncestor = copy;
					} else {
						cacheableAncestor = getCacheableAncestor(copy);
					}

					List subComps = subcompsMap.get(cacheableAncestor);
					if (subComps == null) {
						subComps = new ArrayList();
						subcompsMap.put(cacheableAncestor, subComps);
					}
					subComps.add(copy);
				}
			}
		}

		// sort by z-order
		updateOrder(cacheableComponentList);
		for (ComponentEx comp : cacheableComponentList) {
			ComponentEx copy = (ComponentEx) copyMap.get(comp);
			List subcomps = subcompsMap.get(copy);
			updateOrder(subcomps);
			CacheableBlock cb = new CacheableBlock(comp, copy, subcomps);
			cacheBlockList.add(cb);
		}
	}

	/**
	 * update the list order;
	 */
	protected static void updateOrder(List list) {
		ComponentEx[] comps = list.toArray(new ComponentEx[list.size()]);
		Comparator zComparator = new Comparator() {

			public int compare(PComponent o1, PComponent o2) {
				return o1.getZOrder() - o2.getZOrder();
			}
		};

		Arrays.sort(comps, zComparator);

		list.clear();
		for (ComponentEx comp : comps) {
			list.add(comp);
		}
	}

	/**
	 * Determines whether the component is showing on plot. This means that the component must be visible, and it must
	 * be in a container that is visible and showing.
	 * 
	 * @param comp
	 *            the component
	 * @return whether the component is showing
	 */
	private static boolean isShowing(ComponentEx comp) {
		if (!comp.isVisible()) {
			return false;
		} else if (comp.getParent() != null) {
			return isShowing(comp.getParent());
		} else {
			return true;
		}
	}

	/**
	 * Create a undo memento and add it to change history.
	 */
	private void makeUndoMemento() {

		copyMap.clear();

		plotCopy = (PlotEx) plotImpl.copyStructure(copyMap);
		for (Map.Entry me : copyMap.entrySet()) {
			me.getValue().copyFrom(me.getKey());
		}

		if (undoManager.getCapacity() > 0) {
			// build copy to proxy map
			copyProxyMap = new LinkedHashMap();
			for (Map.Entry me : proxyMap.entrySet()) {
				Element element = me.getKey();
				Element proxy = me.getValue();
				ElementEx copye = copyMap.get(element);
				copyProxyMap.put(copye, proxy);
			}

			undoManager.add(new UndoMemento(plotCopy, copyProxyMap));
		}
	}

	/* ====================== Undo/Redo ====================== */

	/**
	 * Returns the maximum number of reversible operations.
	 * 
	 * @return the maximum number of reversible operations
	 */
	public int getUndoLevels() {
		begin();
		int capacity = undoManager.getCapacity();
		end();

		return capacity;
	}

	/**
	 * Sets the maximum number of reversible operations.
	 * 
	 * @param levels
	 *            the maximum number of reversible operations
	 */
	public void setUndoLevels(int levels) {
		begin();
		undoManager.setCapacity(levels);
		end();
	}

	/**
	 * Returns true if undo is possible.
	 * 
	 * @return
	 */
	public boolean canUndo() {
		begin();
		boolean b = undoManager.canUndo();
		end();

		return b;
	}

	/**
	 * Returns true if undo is possible.
	 * 
	 * @return
	 */
	public boolean canRedo() {
		begin();
		boolean b = undoManager.canRedo();
		end();

		return b;
	}

	/**
	 * Undo the last change. If there is nothing to undo, an exception is thrown.
	 * 
	 * @return
	 */
	public void undo() {
		begin();

		UndoMemento memento = undoManager.undo();
		if (memento == null) {
			throw new RuntimeException("Cannot undo");
		}

		restore(memento);
		buildComponentCacheBlock();

		plotImpl.setRerenderNeeded(false);
		render();

		end();
	}

	/**
	 * Undo the last change. If there is nothing to undo, an exception is thrown.
	 * 
	 * @return
	 */
	public void redo() {
		begin();

		UndoMemento memento = undoManager.redo();
		if (memento == null) {
			throw new RuntimeException("Cannot redo");
		}

		restore(memento);
		buildComponentCacheBlock();

		plotImpl.setRerenderNeeded(false);
		render();

		end();
	}

	/**
	 * copy the memento back to working environment. This method create a copy of memento, and assign the copy as
	 * working plotImpl.
	 * 
	 * @param memento
	 *            the memento to be copied from
	 */
	private void restore(UndoMemento memento) {

		copyMap.clear();

		// the key is element in memento, the value is copy of element for restoring
		Map rcopyMap = new HashMap();

		// copy the memento as implements
		plotCopy = memento.getPlot();
		plotImpl = (PlotEx) plotCopy.copyStructure(rcopyMap);
		plotImpl.setNotifier(notifier);

		proxyMap.clear();
		copyProxyMap = memento.getProxyMap();
		for (Map.Entry me : copyProxyMap.entrySet()) {
			ElementEx mmte = me.getKey(); // the memento element
			Element proxy = me.getValue(); // the proxy
			ElementEx impl = rcopyMap.get(mmte); // the copy of memento element, as the new impl
			// copy properties from memento element to new impl
			impl.copyFrom(mmte);
			// replace impl in invocation handler
			ElementIH ih = (ElementIH) Proxy.getInvocationHandler(proxy);
			ih.replaceImpl(impl);

			proxyMap.put(impl, proxy);
			copyMap.put(impl, mmte);
		}
	}

	/**
	 * Return the top selectable component at the given location.
	 * 
	 * @param dp
	 *            the device point relative to top-left corner.
	 * @return
	 */
	public PComponent getSelectableCompnentAt(Point2D dp) {
		PComponent result = null;

		begin();

		for (int i = cacheBlockList.size() - 1; i >= 0; i--) {
			List uccList = cacheBlockList.get(i).getSubcomps();
			for (int j = uccList.size() - 1; j >= 0; j--) {
				ComponentEx ucc = uccList.get(j);
				if (ucc.isSelectable()) {
					PaperTransform pxf = ucc.getPaperTransform();
					Rectangle2D sbnd = ucc.getSelectableBounds();
					if (pxf != null && sbnd != null && sbnd.contains(pxf.getDtoP(dp))) {
						result = (PComponent) copyProxyMap.get(ucc);
						break;
					}
				}
			}
		}

		end();

		return result;
	}

	/**
	 * Return the most inner plot that contains the given location.
	 * 
	 * @param dp
	 *            the device point relative to top-left corner.
	 * @return
	 */
	public Plot getPlotAt(Point2D dp) {
		Plot result = null;

		begin();

		for (int i = cacheBlockList.size() - 1; i >= 0; i--) {
			List uccList = cacheBlockList.get(i).getSubcomps();
			for (int j = uccList.size() - 1; j >= 0; j--) {
				ComponentEx ucc = uccList.get(j);
				if (ucc instanceof PlotEx) {
					Point2D p = ucc.getPaperTransform().getDtoP(dp);
					if (ucc.getBounds().contains(p)) {
						result = (Plot) copyProxyMap.get(ucc);
						break;
					}
				}
			}
		}

		end();

		return result;
	}

}

 
 

Ads help maintain this website.