/*
 * org.openmicroscopy.shoola.agents.metadata.rnd.RendererModel 
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2014 University of Dundee. All rights reserved.
 *
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program 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 Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.agents.metadata.rnd;


//Java imports
import java.awt.Color;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

//Third-party libraries
import com.sun.opengl.util.texture.TextureData;

//Application-internal dependencies
import omero.romio.PlaneDef;

import org.apache.commons.collections.CollectionUtils;
import org.openmicroscopy.shoola.agents.metadata.MetadataViewerAgent;
import org.openmicroscopy.shoola.agents.metadata.RenderingControlShutDown;
import org.openmicroscopy.shoola.agents.metadata.view.MetadataViewer;
import org.openmicroscopy.shoola.agents.util.ViewerSorter;
import org.openmicroscopy.shoola.env.data.DSOutOfServiceException;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.log.LogMessage;
import org.openmicroscopy.shoola.env.rnd.RenderingControl;
import org.openmicroscopy.shoola.env.rnd.RenderingServiceException;
import org.openmicroscopy.shoola.env.rnd.RndProxyDef;
import org.openmicroscopy.shoola.env.rnd.data.ResolutionLevel;
import org.openmicroscopy.shoola.util.file.modulo.ModuloInfo;
import org.openmicroscopy.shoola.util.file.modulo.ModuloParser;
import org.openmicroscopy.shoola.util.image.geom.Factory;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import pojos.ChannelData;
import pojos.ImageData;
import pojos.PixelsData;
import pojos.XMLAnnotationData;

/** 
 * The Model component in the <code>Renderer</code> MVC triad.
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 * <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since 3.0-Beta4
 */
class RendererModel
{

	/** The maximum width of the preview image. */
	static final int PREVIEW_WIDTH = 256;

	/** The maximum height of the preview image. */
	static final int PREVIEW_HEIGHT = 256;

	/** Identifies the minimum value of the device space. */
	static final int CD_START = 0;

	/** Identifies the maximum value of the device space. */
	static final int CD_END = 255;

	/** Flag to select a 1-bit depth (<i>=2^1-1</i>) output interval. */
	static final int DEPTH_1BIT = RenderingControl.DEPTH_1BIT;

	/** Flag to select a 2-bit depth (<i>=2^2-1</i>) output interval. */
	static final int DEPTH_2BIT = RenderingControl.DEPTH_2BIT;

	/** Flag to select a 3-bit depth (<i>=2^3-1</i>) output interval. */
	static final int DEPTH_3BIT = RenderingControl.DEPTH_3BIT;

	/** Flag to select a 4-bit depth (<i>=2^4-1</i>) output interval. */
	static final int DEPTH_4BIT = RenderingControl.DEPTH_4BIT;

	/** Flag to select a 5-bit depth (<i>=2^5-1</i>) output interval. */
	static final int DEPTH_5BIT = RenderingControl.DEPTH_5BIT;

	/** Flag to select a 6-bit depth (<i>=2^6-1</i>) output interval. */
	static final int DEPTH_6BIT = RenderingControl.DEPTH_6BIT;

	/** Flag to select a 7-bit depth (<i>=2^7-1</i>) output interval. */
	static final int DEPTH_7BIT = RenderingControl.DEPTH_7BIT;

	/** Flag to select a 8-bit depth (<i>=2^8-1</i>) output interval. */
	static final int DEPTH_8BIT = RenderingControl.DEPTH_8BIT;

	/** Identifies the <code>Linear</code> family. */
	static final String LINEAR = RenderingControl.LINEAR;

	/** Identifies the <code>Exponential</code> family. */
	static final String LOGARITHMIC = RenderingControl.LOGARITHMIC;

	/** Identifies the <code>Exponential</code> family. */
	static final String EXPONENTIAL = RenderingControl.EXPONENTIAL;

	/** Identifies the <code>Exponential</code> family. */
	static final String POLYNOMIAL = RenderingControl.POLYNOMIAL;

	/** Reference to the component that embeds this model. */
	private Renderer component;

	/** Reference to the rendering control. */
	private RenderingControl rndControl;

	/** The current state of the component. */
	private int state;

	/** The index of the selected channel. */
	private int selectedChannelIndex;

	/** Flag to denote if the widget is visible or not. */
	private boolean visible;

    /** The index of the rendering. */
    private int rndIndex;

    /** The collection of sorted channels. */
    private List<ChannelData> sortedChannel;

    /**
     * The global minimum of all channels if the number of channels is
     * greater than {@code Renderer#MAX_CHANNELS}.
     */
    private Double globalMinChannels;

    /**
     * The global maximum of all channels if the number of channels is
     * greater than {@code Renderer#MAX_CHANNELS}.
     */
    private Double globalMaxChannels;

    /** The plane object to render. */
    private PlaneDef plane;

    /** The dimension of the preview image. */
    private Dimension previewSize;

    /** The rendering settings. */
    private RndProxyDef rndDef;

    /** Reference to the image. */
    private ImageData image;

    /** The security context.*/
    private SecurityContext ctx;

    /** Map hosting the extra dimension if available.*/
    private Map<Integer, ModuloInfo> modulo;

	/**
	 * Creates a new instance.
	 *
	 * @param ctx The security context.
	 * @param rndControl Reference to the component that controls the
	 *                   rendering settings. Mustn't be <code>null</code>.
	 * @param rndIndex The index associated to the renderer.
	 */
	RendererModel(SecurityContext ctx, RenderingControl rndControl,
			int rndIndex)
	{
		if (rndControl == null)
			throw new NullPointerException("No rendering control.");
		setRenderingControl(rndControl);
		this.ctx = ctx;
		this.rndIndex = rndIndex;
		visible = false;
		globalMaxChannels = null;
		globalMinChannels = null;
		plane = new PlaneDef();
		plane.slice = omero.romio.XY.value;
	}

	/**
	 * Returns the security context.
	 *
	 * @return See above.
	 */
	SecurityContext getSecurityContext() { return ctx; }

	/**
	 * Sets the image the component is for.
	 * 
	 * @param image The value to set.
	 */
	void setImage(ImageData image) { this.image = image; }

	/**
	 * Sets the rendering control.
	 * 
	 * @param rndControl Reference to the component that controls the
	 *                   rendering settings. Mustn't be <code>null</code>.
	 */
	void setRenderingControl(RenderingControl rndControl)
	{
		this.rndControl = rndControl;
		if (rndControl != null) rndDef = rndControl.getRndSettingsCopy();
	}

	/**
	 * Returns the image the renderer is for.
	 *
	 * @return See above.
	 */
	ImageData getRefImage() { return image; }

	/**
	 * Returns <code>true</code> if one channel is selected,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean hasSelectedChannel() { return selectedChannelIndex >= 0; }

	/**
	 * Returns the index of a channel or <code>-1</code>.
	 *
	 * @return See above.
	 */
	int createSelectedChannel()
	{
		//Set the selected channel
		List<Integer> active = getActiveChannels();
		List<ChannelData> list = getChannelData();
		Iterator<ChannelData> i = list.iterator();
		ChannelData channel;
		int index;
		int setIndex = -1;
		while (i.hasNext()) {
			channel = i.next();
			index = channel.getIndex();
			if (active.contains(index) && setIndex < 0) {
				setIndex = index;
				break;
			}
		}
		return setIndex;
	}

	/**
	 * Returns the color associated to the channel.
	 *
	 * @param index The index of the channel.
	 * @return See above.
	 */
	Color getChannelColor(int index)
	{
		if (rndControl == null) return Color.white;
		return rndControl.getRGBA(index);
	}

	/**
	 * Returns the status of the window.
	 *
	 * @return See above.
	 */
	boolean isVisible() { return visible; }

	/**
	 * Called by the <code>Renderer</code> after creation to allow this
	 * object to store a back reference to the embedding component.
	 *
	 * @param component The embedding component.
	 */
	void initialize(Renderer component) { this.component = component; }

	/** Discards component. */
	void discard() 
	{
		if (rndControl == null) return;
		RenderingControlShutDown loader =
			new RenderingControlShutDown(ctx, rndControl.getPixelsID());
		loader.load();
		rndControl = null;
	}

	/**
	 * Returns the state of the component.
	 *
	 * @return See above.
	 */
	int getState() { return state; }

	/**
	 * Sets the pixels intensity interval for the specified channel.
	 * or for all channels if the number of channels is greater than 
	 * {@link Renderer#MAX_CHANNELS}
	 *
	 * @param index The index of the channel.
	 * @param start The lower bound of the interval.
	 * @param end The upper bound of the interval.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setInputInterval(int index, double start, double end)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		if (isLifetimeImage() && getModuloT() == null) {
		    for (int i = 0; i < getMaxC(); i++) {
                rndControl.setChannelWindow(i, start, end);
            }
		} else rndControl.setChannelWindow(index, start, end);
	}

	/**
	 * Returns the upper bound of the sub-interval of the device space.
	 *
	 * @return See above.
	 */
	int getCodomainEnd()
	{
		if (rndControl == null) return -1;
		return rndControl.getCodomainEnd();
	}

	/**
	 * Returns the lower bound of the sub-interval of the device space.
	 *
	 * @return See above.
	 */
	int getCodomainStart()
	{
		if (rndControl == null) return -1;
		return rndControl.getCodomainStart();
	}

	/**
	 * Sets the sub-interval of the device space.
	 * 
	 * @param s The lower bound of the interval.
	 * @param e The upper bound of the interval.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setCodomainInterval(int s, int e)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setCodomainInterval(s, e);
	}

	/**
	 * Sets the quantum strategy.
	 * 
	 * @param v The bit resolution defining the strategy.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setBitResolution(int v)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setQuantumStrategy(v);
	}

	/**
	 * Sets the selected channel.
	 * 
	 * @param index The index of the selected channel.
	 */
	void setSelectedChannel(int index) { selectedChannelIndex = index; }

	/**
	 * Sets the family used during the mapping process for the specified channel.
	 * 
	 * @param channel The selected channel.
	 * @param family The family to set.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setFamily(int channel, String family)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		if (channel < 0 || channel > getMaxC()) channel = selectedChannelIndex;
		boolean b = rndControl.getChannelNoiseReduction(selectedChannelIndex);
		double k = rndControl.getChannelCurveCoefficient(selectedChannelIndex);
		rndControl.setQuantizationMap(channel, family, k, b);
	}

	/**
	 * Selects one curve in the family.
	 * 
	 * @param k The coefficient identifying a curve within a family.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setCurveCoefficient(double k)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		boolean b = rndControl.getChannelNoiseReduction(selectedChannelIndex);
		String family = rndControl.getChannelFamily(selectedChannelIndex);
		rndControl.setQuantizationMap(selectedChannelIndex, family, k, b);
	}

	/**
	 * Turns on and off the noise reduction algorithm mapping.
	 * 
	 * @param b Pass <code>true</code>  to turn it on,
	 *          <code>false</code> otherwise.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setNoiseReduction(boolean b)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		String family = rndControl.getChannelFamily(selectedChannelIndex);
		double k = rndControl.getChannelCurveCoefficient(selectedChannelIndex);
		rndControl.setQuantizationMap(selectedChannelIndex, family, k, b);
	}

	/**
	 * Updates the specified {@link CodomainMapContext context}.
	 * 
	 * @param ctx The context to update.
	 * @throws RenderingServiceException 	If an error occurred while setting 
	 * 										the value.
	 * @throws DSOutOfServiceException  	If the connection is broken.
	 */
	/*
	void updateCodomainMap(CodomainMapContext ctx)
	throws RenderingServiceException, DSOutOfServiceException
	{
		rndControl.updateCodomainMap(ctx);
	}
	*/

	/**
	 * Returns the codomain map context corresponding to the specified 
	 * <code>codomain</code> class. Returns <code>null</code> if there is no
	 * context matching the class.
	 * 
	 * @param mapType The class corresponding to the context to retrieve.
	 * @return See above.
	 */
	/*
	CodomainMapContext getCodomainMap(Class mapType)
	{
		List maps = getCodomainMaps();
		Iterator i = maps.iterator();
		CodomainMapContext ctx;
		while (i.hasNext()) {
			ctx = (CodomainMapContext) i.next();
			if (ctx.getClass().equals(mapType)) return ctx;
		}
		return null;
	}
	*/

	/**
	 * Returns a read-only list of {@link CodomainMapContext}s using during
	 * the mapping process in the device space.
	 *
	 * @return See above.
	 */
	List getCodomainMaps()
	{ 
		if (rndControl == null) return new ArrayList();
		return rndControl.getCodomainMaps();
	}

	/**
	 * Removes the codomain map identified by the class from the chain of 
	 * codomain transformations.
	 * 
	 * @param mapType The type to identify the codomain map.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	/*
	void removeCodomainMap(Class mapType)
	throws RenderingServiceException, DSOutOfServiceException
	{
		CodomainMapContext ctx = getCodomainMap(mapType);
		if (ctx != null) rndControl.removeCodomainMap(ctx);
	}
	*/

	/**
	 * Adds the codomain map identified by the class to the chain of 
	 * codomain transformations.
	 *
	 * @param mapType The type to identify the codomain map.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	/*
	void addCodomainMap(Class mapType)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (mapType.equals(ReverseIntensityContext.class)) {
			ReverseIntensityContext riCtx = new ReverseIntensityContext();
			riCtx.setReverse(Boolean.TRUE);
			rndControl.addCodomainMap(riCtx);
		} else if (mapType.equals(PlaneSlicingContext.class)) {

		} else if (mapType.equals(ContrastStretchingContext.class)) {

		}
	}
	*/

	/** 
	 * Returns the index of the currently selected channel.
	 * 
	 * @return See above.
	 */
	int getSelectedChannel() { return selectedChannelIndex; }

	/**
	 * Returns a list of available mapping families.
	 * 
	 * @return See above.
	 */
	List getFamilies()
	{ 
		if (rndControl == null) return new ArrayList();
		return rndControl.getFamilies();
	}

	/**
	 * Returns the mapping family used for to map the selected channel.
	 *
	 * @return See above.
	 */
	String getFamily() { return getFamily(selectedChannelIndex); }

	/**
	 * Returns the mapping family used for to map the selected channel.
	 *
	 * @param channel The selected channel.
	 * @return See above.
	 */
	String getFamily(int channel)
	{
	    if (rndControl == null) return "";
	    return rndControl.getChannelFamily(channel);
	}

	/**
	 * Returns the map selected in the family for the selected channel.
	 *
	 * @return See above.
	 */
	double getCurveCoefficient()
	{
		return getCurveCoefficient(selectedChannelIndex);
	}

	/**
	 * Returns the map selected in the family for the selected channel.
	 *
	 * @param channel The selected channel.
	 * @return See above.
	 */
	double getCurveCoefficient(int channel)
	{
	    if (rndControl == null) return -1;
	    return rndControl.getChannelCurveCoefficient(channel);
	}
    
	/**
	 * Returns the bit resolution value.
	 * 
	 * @return See above.
	 */
	int getBitResolution()
	{
		if (rndControl == null) return -1;
		return rndControl.getBitResolution();
	}

	/**
	 * Returns <code>true</code> if the noise reduction flag is turned on
	 * for the selected channel, <code>false</code> otherwise.
	 *
	 * @return See above.
	 */
	boolean isNoiseReduction()
	{
		if (rndControl == null) return false;
		return rndControl.getChannelNoiseReduction(selectedChannelIndex);
	}

	/**
	 * Returns a list of <code>Channel Data</code> objects.
	 *
	 * @return See above.
	 */
	List<ChannelData> getChannelData()
	{
		if (rndControl == null) return new ArrayList<ChannelData>();
		if (sortedChannel == null) {
			ChannelData[] data = rndControl.getChannelData();
			ViewerSorter sorter = new ViewerSorter();
			sortedChannel = Collections.unmodifiableList(sorter.sort(data));
		}
		return sortedChannel;
	}

	/**
	 * Returns the global minimum of the currently selected channel
	 * or of all channels if the number of channels is greater
	 * {@link Renderer#MAX_CHANNELS}.
	 *
	 * @return See above.
	 */
	double getGlobalMin()
	{
		if (getMaxC() > Renderer.MAX_CHANNELS) {
			if (globalMinChannels == null) {
				double min = Double.MAX_VALUE;
				double value;
				for (int i = 0; i < getMaxC(); i++) {
					value = getGlobalMin(i); 
					if (value < min) min = value;
				}
				globalMinChannels = min;
			}
			return globalMinChannels.doubleValue();
		}
		return getGlobalMin(selectedChannelIndex);
	}

	/**
	 * Returns the rounding factor used for the input value.
	 *
	 * @return See above.
	 */
	int getRoundFactor()
	{
		return getRoundFactor(selectedChannelIndex);
	}
	
	/**
	 * Returns the rounding factor used for the input value.
	 *
	 * @param channel The channel to handle.
	 * @return See above.
	 */
	int getRoundFactor(int channel)
	{
		double min = getGlobalMin(channel);
		double max = getGlobalMax(channel);
		double rmin = UIUtilities.roundTwoDecimals(min);
		double rmax = UIUtilities.roundTwoDecimals(max);
		if (rmin == min && rmax == max) return 1;
		return 100;
	}

	/**
	 * Returns the global maximum of the currently selected channel
	 * or of all channels if the number of channels is greater
	 * {@link Renderer#MAX_CHANNELS}.
	 *
	 * @return See above.
	 */
	double getGlobalMax()
	{
		if (getMaxC() > Renderer.MAX_CHANNELS) {
			if (globalMaxChannels == null) {
				double max = Double.MIN_VALUE;
				double value;
				for (int i = 0; i < getMaxC(); i++) {
					value = getGlobalMax(i);
					if (value > max) max = value;
				}
				globalMaxChannels = max;
			}
			return globalMaxChannels.doubleValue();
		}
		return getGlobalMax(selectedChannelIndex);
	}

	/**
	 * Returns the global maximum of the passed channel.
	 * 
	 * @param index The index of the channel.
	 * @return See above.
	 */
	double getGlobalMax(int index)
	{
		if (rndControl == null) return -1;
		return rndControl.getChannelData(index).getGlobalMax();
	}

	/**
	 * Returns the global minimum of the passed channel.
	 * 
	 * @param index The index of the channel.
	 * @return See above.
	 */
	double getGlobalMin(int index)
	{
		if (rndControl == null) return -1;
		return rndControl.getChannelData(index).getGlobalMin();
	}

	/**
	 * Returns the lowest possible value.
	 *
	 * @return See above.
	 */
	double getLowestValue()
	{
		return getLowestValue(selectedChannelIndex);
	}

	/**
	 * Returns the lowest possible value for the passed channel.
	 *
	 * @param channel The channel to handle.
	 * @return See above.
	 */
	double getLowestValue(int channel)
	{
		if (rndControl == null) return -1;
		return rndControl.getPixelsTypeLowerBound(channel);
	}

	/**
	 * Returns the highest possible value.
	 * 
	 * @return See above.
	 */
	double getHighestValue()
	{
		return getHighestValue(selectedChannelIndex);
	}

	/**
	 * Returns the highest possible value.
	 * 
	 * @param channel The channel to handle.
	 * @return See above.
	 */
	double getHighestValue(int channel)
	{
		if (rndControl == null) return -1;
		return rndControl.getPixelsTypeUpperBound(channel);
	}

	/**
	 * Returns the lower bound of the pixels intensity interval of the 
	 * specified channel.
	 * 
	 * @param channel The index of the channel.
	 * @return See above.
	 */
	double getWindowStart(int channel)
	{
		if (rndControl == null) return -1;
		return rndControl.getChannelWindowStart(channel);
	}

	/**
	 * Returns the upper bound of the pixels intensity interval of the 
	 * specified channel.
	 * 
	 * @param channel The index of the channel.
	 * @return See above.
	 */
	double getWindowEnd(int channel)
	{
		if (rndControl == null) return -1;
		return rndControl.getChannelWindowEnd(channel);
	}

	/**
	 * Returns the lower bound of the pixels intensity interval of the 
	 * currently selected channel.
	 * 
	 * @return See above.
	 */
	double getWindowStart()
	{
		return getWindowStart(selectedChannelIndex);
	}

	/**
	 * Returns the upper bound of the pixels intensity interval of the 
	 * currently selected channel.
	 * 
	 * @return See above.
	 */
	double getWindowEnd()
	{
		return getWindowEnd(selectedChannelIndex);
	}

	/**
	 * Returns <code>true</code> if the grey scale is selected,
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean isGreyScale()
	{
		if (rndControl == null) return false;
		return rndControl.getModel().equals(RenderingControl.GREY_SCALE);
	}

	/**
	 * Saves the rendering settings.
	 *
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void saveRndSettings()
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.saveCurrentSettings();
	}

	/**
	 * Returns <code>true</code> if the channel is mapped, <code>false</code>
	 * otherwise.
	 * 
	 * @param w The channel's index.
	 * @return See above.
	 */
	boolean isChannelActive(int w)
	{
		if (rndControl == null) return false;
		return rndControl.isActive(w);
	}

	/**
	 * Returns a list of active channels.
	 * 
	 * @return See above.
	 */
	List<Integer> getActiveChannels()
	{
		List<Integer> active = new ArrayList<Integer>();
		if (rndControl == null) return active;
		for (int i = 0; i < getMaxC(); i++) {
			if (rndControl.isActive(i)) active.add(Integer.valueOf(i));
		}
		return active;
	}

	/**
	 * Returns the number of channels.
	 *
	 * @return See above.
	 */
	int getMaxC()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getPixelsDimensionsC();
	}

	/**
	 * Returns the index associated to the renderer.
	 * 
	 * @return See above.
	 */
	int getRndIndex() { return rndIndex; }

	/**
	 * Returns <code>true</code> if the renderer is for a general perspective
	 * or for a specific view.
	 * 
	 * @return See above.
	 */
	boolean isGeneralIndex()
	{ 
		return getRndIndex() == MetadataViewer.RND_GENERAL;
	}

	/**
	 * Sets the color for the specified channel.
	 *
	 * @param index The channel's index.
	 * @param color The color to set.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setChannelColor(int index, Color color)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setRGBA(index, color);
	}

	/**
	 * Returns the color model.
	 * 
	 * @return See above.
	 */
	String getColorModel()
	{
		if (rndControl == null) return null;
		return rndControl.getModel();
	}

	/**
	 * Sets the color model.
	 * 
	 * @param colorModel The color model to set.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setColorModel(String colorModel)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setModel(colorModel);
	}

	/**
	 * Returns the number of pixels along the X-axis.
	 *
	 * @return See above.
	 */
	int getMaxX()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getPixelsDimensionsX();
	}
	
	/**
	 * Returns the number of pixels along the Y-axis.
	 *
	 * @return See above.
	 */
	int getMaxY()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getPixelsDimensionsY();
	}

	/**
	 * Returns the maximum number of z-sections.
	 *
	 * @return See above.
	 */
	int getMaxZ()
	{
		if (rndControl == null) return -1;
		return rndControl.getPixelsDimensionsZ();
	}

	/**
	 * Returns the maximum number of time-points.
	 * 
	 * @return See above.
	 */
	int getMaxT()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getPixelsDimensionsT();
	}

	/**
	 * Returns the number of time points if modulo available.
	 *
	 * @return See above.
	 */
	int getRealT()
	{
	    if (hasModuloT()) {
	        int sizeBin = modulo.get(ModuloInfo.T).getSize();
	        return getMaxT()/sizeBin;
	    }
	    return getMaxT();
	}

	/**
	 * Returns the currently selected z-section.
	 *
	 * @return See above.
	 */
	int getDefaultZ()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getDefaultZ();
	}

	/**
	 * Returns the currently selected time-point.
	 *
	 * @return See above.
	 */
	int getDefaultT()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getDefaultT();
	}

	/**
	 * Returns the currently selected time-point.
	 * 
	 * @return See above.
	 */
    int getRealSelectedT()
    { 
        if (rndControl == null) return -1;
        if (hasModuloT()) {
            return rndControl.getDefaultT() / getMaxLifetimeBin();
        }
        return rndControl.getDefaultT();
    }

	/**
	 * Sets the selected plane.
	 *
	 * @param z The z-section to set.
	 * @param t The time-point to set.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setSelectedXYPlane(int z, int t)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		if (t >= 0 && t != getDefaultT()) rndControl.setDefaultT(t);
		if (z >= 0 && z != getDefaultZ()) rndControl.setDefaultZ(z);
	}

	/**
	 * Turns on or off the specified channel.
	 * 
	 * @param index The index of the channel.
	 * @param active Pass <code>true</code> to turn the channel on,
	 * <code>false</code> to turn in off.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setChannelActive(int index, boolean active)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setActive(index, active);
	}

	/**
	 * Returns the compression level.
	 *
	 * @return See above.
	 */
	int getCompressionLevel()
	{
		if (rndControl == null) return -1;
		return rndControl.getCompressionLevel();
	}

	/**
	 * Returns the physical size of a pixels along the Y-axis.
	 *
	 * @return See above.
	 */
	double getPixelsSizeY()
	{ 
		if (rndControl == null) return -1;
		return rndControl.getPixelsPhysicalSizeY();
	}

	/**
	 * Returns the physical size of a pixels along the X-axis.
	 *
	 * @return See above.
	 */
	double getPixelsSizeX()
	{
		if (rndControl == null) return -1;
		return rndControl.getPixelsPhysicalSizeX();
	}
	
	/**
	 * Returns the physical size of a pixels along the Z-axis.
	 *
	 * @return See above.
	 */
	double getPixelsSizeZ()
	{
		if (rndControl == null) return -1;
		return rndControl.getPixelsPhysicalSizeZ();
	}

    /**
     * Returns a copy of the current rendering settings.
     * 
     * @return See above.
     */
	RndProxyDef getRndSettingsCopy()
	{ 
		if (rndControl == null) return null;
		return rndControl.getRndSettingsCopy();
	}

    /**
     * Returns the initial rendering settings.
     * 
     * @return See above.
     */
	RndProxyDef getInitialRndSettings() { return rndDef; }

	/**
	 * Returns <code>true</code> if an active channel
	 * is mapped to <code>Red</code> if the band is <code>0</code>,
	 * <code>Red</code> if the band is <code>0</code>,
	 * <code>Red</code> if the band is <code>0</code>,
	 * <code>false</code> otherwise.
	 *
	 * @param band Pass <code>0</code> for <code>Red</code>,
	 * 			   <code>1</code> for <code>Green</code>,
	 * 			   <code>2</code> for <code>Blue</code>.
	 * @return See above
	 */
	boolean hasActiveChannel(int band)
	{
		if (rndControl == null) return false;
		switch (band) {
			case 0: return rndControl.hasActiveChannelRed();
			case 1: return rndControl.hasActiveChannelGreen();
			case 2: return rndControl.hasActiveChannelBlue();
		}
		return false;
	}

	/**
	 * Returns <code>true</code> if the compression is turned on,
	 * <code>false</code> otherwise.
	 *
	 * @return See above.
	 */
	boolean isCompressed()
	{
		if (rndControl == null) return false;
		return rndControl.isCompressed();
	}

	/**
	 * Returns <code>true</code> if the specified channel 
	 * is mapped to <code>Red</code> if the band is <code>0</code>,
	 * <code>Red</code> if the band is <code>0</code>, 
	 * <code>Red</code> if the band is <code>0</code>,
	 * <code>false</code> otherwise.
	 * 
	 * @param band Pass <code>0</code> for <code>Red</code>,
	 * 			   <code>1</code> for <code>Green</code>,
	 * 			   <code>2</code> for <code>Blue</code>.
	 * @param index The index of the channel.
	 * @return See above
	 */
	boolean isColorComponent(int band, int index)
	{
		if (rndControl == null) return false;
		switch (band) {
			case 0: return rndControl.isChannelRed(index);
			case 1: return rndControl.isChannelGreen(index);
			case 2: return rndControl.isChannelBlue(index);
		}
		return false;
	}

	/**
	 * Resets the default settings.
	 * 
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void resetDefaults()
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.resetDefaults();
	}

	/**
	 * Resets the passed rendering settings.
	 * 
	 * @param settings The rendering settings to reset.
	 * @throws RenderingServiceException 	If an error occurred while setting 
	 * 										the value.
	 * @throws DSOutOfServiceException  	If the connection is broken.
	 */
	void resetSettings(RndProxyDef settings)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.resetSettings(settings);
	}

	/**
	 * Saves the current settings.
	 *
	 * @return See above.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	RndProxyDef saveCurrentSettings()
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return null;
		return rndControl.saveCurrentSettings();
	}

	/**
	 * Turns on or off the specified channel.
	 *
	 * @param index The index of the channel
	 * @param active Pass <code>true</code> to turn the channel on,
	 * 				 <code>false</code> to turn it off.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setActive(int index, boolean active)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setActive(index, active);
	}

	/**
	 * Sets the compression level.
	 * 
	 * @param compression  The compression level.
	 */
	void setCompression(int compression)
	{
		if (rndControl == null) return;
		rndControl.setCompression(compression);
	}

	/**
	 * Sets the original settings.
	 *
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	void setOriginalRndSettings()
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setOriginalRndSettings();
	}

	/**
	 * Checks if the passed set of pixels is compatible.
	 * Returns <code>true</code> if the pixels set is compatible,
	 * <code>false</code> otherwise.
	 * 
	 * @param pixels The pixels to check.
	 * @return See above.
	 */
	boolean validatePixels(PixelsData pixels)
	{
		if (rndControl == null) return false;
		return rndControl.validatePixels(pixels);
	}

	/**
	 * Renders the specified plane.
	 * 
	 * @param pDef The plane to render.
	 * @param region The region to render, If <code>null</code> the plane
	 * 				 is rendered.
	 * @return See above.
	 * @throws RenderingServiceException If an error occurred while setting
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	BufferedImage render(PlaneDef pDef)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return null;
		return rndControl.render(pDef);
	}

	/**
	 * Renders the specified plane.
	 *
	 * @param pDef The plane to render.
	 * @return See above.
	 * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
	TextureData renderPlaneAsTexture(PlaneDef pDef)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return null;
		return rndControl.renderAsTexture(pDef);
	}
	
	/**
	 * Returns <code>true</code> if the passed rendering settings are the same
	 * that the current one, <code>false</code> otherwise.
	 *
	 * @param def The settings to check.
	 * @param checkPlane Pass <code>true</code> to take into account the
	 * 					 z-section and time-point, <code>false</code> 
	 * 					 otherwise.
	 * @return See above.
	 */
	boolean isSameSettings(RndProxyDef def, boolean checkPlane)
	{
		if (rndControl == null) return false;
		return rndControl.isSameSettings(def, checkPlane);
	}

    /**
     * Returns <code>true</code> if the image with the active channels
     * is an RGB image, <code>false</code> otherwise.
     * 
     * @return See above.
     */
	boolean isMappedImageRGB(List channels)
	{
		if (rndControl == null) return false;
		return rndControl.isMappedImageRGB(channels);
	}

    /**
     * Sets the overlays.
     * 
     * @param tableID  The id of the table.
     * @param overlays The overlays to set, or <code>null</code> to turn
     * the overlays off.
     */
    void setOverlays(long tableID, Map<Long, Integer> overlays)
    	throws RenderingServiceException, DSOutOfServiceException
    {
    	if (rndControl == null) return;
    	rndControl.setOverlays(tableID, overlays);
    }

    /** 
     * Renders the default plane.
     *
     * @return See above.
     */
    BufferedImage renderImage()
    {
    	plane.t = getDefaultT();
    	plane.z = getDefaultZ();
    	try {
    		if (rndControl == null) return null;
    		return rndControl.render(plane, RenderingControl.LOW);
		} catch (Exception e) {
		    LogMessage msg = new LogMessage();
            msg.append("Error while rendering the image.");
            msg.print(e);
            MetadataViewerAgent.getRegistry().getLogger().error(this, msg);
		}
    	return null;
    }

    /**
     * Returns the dimension of the preview image.
     * 
     * @return See above.
     */
    Dimension getPreviewDimension()
    {
    	if (previewSize != null) return previewSize;
    	previewSize = Factory.computeThumbnailSize(PREVIEW_WIDTH,
    			PREVIEW_HEIGHT, getMaxX(), getMaxY());
    	return previewSize;
    }
    
    /** 
     * Resets the rendering settings.
     *
     * @throws RenderingServiceException If an error occurred while setting 
	 * 									the value.
	 * @throws DSOutOfServiceException If the connection is broken.
	 */
    void resetRenderingSettings()
    	throws RenderingServiceException, DSOutOfServiceException
    {
    	rndControl.resetSettings(rndDef);
    }
    
	/**
	 * Returns <code>true</code> if it is a large image, 
	 * <code>false</code> otherwise.
	 *
	 * @return See above.
	 */
	boolean isBigImage()
	{
		if (rndControl == null) return false;
		return rndControl.isBigImage();
	}

	/**
	 * Returns the number of bins per time interval.
	 * 
	 * @return See above
	 */
	int getMaxLifetimeBin()
	{
	    if (hasModuloT()) {
	        ModuloInfo info = modulo.get(ModuloInfo.T);
	        return info.getSize();
	    }
		if (isLifetimeImage()) return getMaxC()-1;
		return 0;
	}

	/**
	 * Returns <code>true</code> if the image has extra dimension along T.
	 * <code>false</code> otherwise.
	 * 
	 * @return See above.
	 */
	boolean hasModuloT()
	{
	    return (modulo != null && modulo.containsKey(ModuloInfo.T));
	}

	/**
	 * Returns the modulo info if it exists.
	 *
	 * @return See above.
	 */
	ModuloInfo getModuloT()
	{
	    if (!hasModuloT()) return null;
	    return modulo.get(ModuloInfo.T);
	}

	/**
	 * Returns <code>true</code> if the image is a lifetime image,
	 * <code>false</code> otherwise.
	 *
	 * @return See above.
	 */
	boolean isLifetimeImage()
	{
	    if (hasModuloT()) return true;
		if (getMaxC() >= Renderer.MAX_CHANNELS) return true;
		return false;
	}

	/**
	 * Returns the selected bin for lifetime image.
	 * 
	 * @return See above.
	 */
	int getSelectedBin()
	{
	    if (!isLifetimeImage()) return -1;
	    if (hasModuloT()) {
	        return getDefaultT()-getRealSelectedT()*getMaxLifetimeBin();
	    }
	    List<Integer> active = getActiveChannels();
	    if (active == null || active.size() != 1) return 0;
	    return active.get(0);
	}

	/**
	 * Sets the selected lifetime bin.
	 *
	 * @param bin The selected bin.
	 * @param t The selected t.
	 */
	void setSelectedBin(int bin, int t)
		throws RenderingServiceException, DSOutOfServiceException
	{
	    if (hasModuloT()) {
	        int binSize = getMaxLifetimeBin();
	        int v = bin + t * binSize;
	        setSelectedXYPlane(getDefaultZ(), v);
	        return;
	    }
		List<ChannelData> channels = getChannelData();
		ChannelData channel;
		Iterator<ChannelData> i = channels.iterator();
		int index;
		while (i.hasNext()) {
			channel = i.next();
			index = channel.getIndex();
			setActive(index, index == bin);
		}
	}

	/**
	 * Returns the dimension of a tile.
	 * 
	 * @return See above.
	 */
	Dimension getTileSize()
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return new Dimension(0, 0);
		return rndControl.getTileSize();
	}

	/**
	 * Returns the possible resolution levels. This method should only be used
	 * when dealing with large images.
	 *
	 * @return See above.
	 */
	int getResolutionLevels()
	{
		if (rndControl == null) return 1;
		return rndControl.getResolutionLevels();
	}

	/**
	 * Returns the currently selected resolution level. This method should only 
	 * be used when dealing with large images.
	 *
	 * @return See above.
	 */
	int getSelectedResolutionLevel()
	{
		if (rndControl == null) return 0;
		return rndControl.getSelectedResolutionLevel();
	}

	/**
	 * Sets resolution level. This method should only be used when dealing with
	 * large images.
	 *
	 * @param level The value to set.
	 */
	void setSelectedResolutionLevel(int level)
		throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return;
		rndControl.setSelectedResolutionLevel(level);
	}

	/**
	 * Sets the channels.
	 *
	 * @param channels The updated channels.
	 */
	void setChannels(List<ChannelData> channels)
	{
		ViewerSorter sorter = new ViewerSorter();
		sortedChannel = Collections.unmodifiableList(sorter.sort(channels));
	}

	/**
	 * Returns <code>true</code> if the object can be annotated,
	 * <code>false</code> otherwise, depending on the permission.
	 * 
	 * @return See above.
	 */
	boolean canAnnotate()
	{
		ImageData image = getRefImage();
		if (image == null) return false;
		return image.canAnnotate();
	}

	/**
	 * Returns the collection of rendering controls. This method should only 
	 * be invoked when loading tiles.
	 *
	 * @return See above.
	 */
	List<RenderingControl> getRenderingControls()
	{
		if (rndControl == null) return null;
		List<RenderingControl> list = new ArrayList<RenderingControl>();
		list.add(rndControl);
		List<RenderingControl> slaves = rndControl.getSlaves();
		if (slaves != null && slaves.size() > 0) list.addAll(slaves);
		return list;
	}

	/**
	 * Returns the list of the levels.
	 * 
	 * @return See above.
	 */
	List<ResolutionLevel> getResolutionDescriptions()
	throws RenderingServiceException, DSOutOfServiceException
	{
		if (rndControl == null) return null;
		return rndControl.getResolutionDescriptions();
	}

	/**
	 * Sets the annotations and parses the content.
	 * 
	 * @param annotations The annotations to parse.
	 */
	void setXMLAnnotations(Collection<XMLAnnotationData> annotations)
    {
	    modulo = new HashMap<Integer, ModuloInfo>();
	    if (CollectionUtils.isEmpty(annotations)) return;
        ModuloParser parser;
        Iterator<XMLAnnotationData> i = annotations.iterator();
        XMLAnnotationData data;
        List<ModuloInfo> infos;
        Iterator<ModuloInfo> j;
        ModuloInfo info;
        while (i.hasNext()) {
            data = i.next();
            parser = new ModuloParser(data.getText());
            try {
                parser.parse();
                infos = parser.getModulos();
                j = infos.iterator();
                while (j.hasNext()) {
                   info = j.next();
                    modulo.put(info.getModuloIndex(), info);
                }
            } catch (Exception e) {
                LogMessage msg = new LogMessage();
                msg.append("Error while reading modulo annotation.");
                msg.print(e);
                MetadataViewerAgent.getRegistry().getLogger().error(this, msg);
            }
        }
    }
}
