/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy 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 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy 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 Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.image.colorspace;

import icy.common.CollapsibleEvent;
import icy.common.UpdateEventHandler;
import icy.common.listener.ChangeListener;
import icy.image.colormap.FromRGBColorMap;
import icy.image.colormap.IcyColorMap;
import icy.image.colormap.IcyColorMap.IcyColorMapType;
import icy.image.colormap.IcyColorMapEvent;
import icy.image.colormap.IcyColorMapListener;
import icy.image.colormap.LinearColorMap;
import icy.image.colormodel.IcyColorModel;
import icy.type.DataType;
import icy.type.collection.array.ArrayUtil;
import icy.util.ColorUtil;

import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.util.ArrayList;
import java.util.List;

/**
 * @author stephane
 */
public class IcyColorSpace extends ColorSpace implements ChangeListener, IcyColorMapListener
{
    /**
     * 
     */
    private static final long serialVersionUID = 6413334779215415163L;

    /**
     * toRGB colormaps
     */
    private final IcyColorMap[] toRGBmaps;
    /**
     * fromRGB colormaps
     */
    private final FromRGBColorMap[] fromRGBmaps;

    /**
     * use alpha
     */
    // private boolean alphaEnabled;

    /**
     * listeners
     */
    private final List<IcyColorSpaceListener> listeners;

    /**
     * internal updater
     */
    private final UpdateEventHandler updater;

    /**
     * Create an icy colorspace object
     * 
     * @param numComponents
     *        number of color component
     */
    public IcyColorSpace(int numComponents)
    {
        super((numComponents > 1) ? 10 + numComponents : ColorSpace.TYPE_GRAY, numComponents);

        if (numComponents == 0)
            throw new IllegalArgumentException("numComponents must be > 0");

        // allocating toRGB colormaps
        toRGBmaps = new IcyColorMap[numComponents];
        for (int i = 0; i < numComponents; i++)
        {
            final IcyColorMap colormap = new IcyColorMap("component " + i);
            toRGBmaps[i] = colormap;
            // add listener
            colormap.addListener(this);
        }

        // allocating fromRGB colormaps
        fromRGBmaps = new FromRGBColorMap[4];
        for (int i = 0; i < 4; i++)
            fromRGBmaps[i] = new FromRGBColorMap(numComponents);

        listeners = new ArrayList<IcyColorSpaceListener>();
        updater = new UpdateEventHandler(this, false);

        // // alpha is enabled by default
        // alphaEnabled = true;

        beginUpdate();
        try
        {
            // single component: gray colormap
            if (numComponents == 1)
                setColorMap(0, LinearColorMap.white_, true);
            else
            {
                // define default colormaps depending the number of component
                for (int i = 0; i < numComponents; i++)
                {
                    switch (i % 7)
                    {
                        case 0:
                            setColorMap(i, LinearColorMap.red_, true);
                            break;
                        case 1:
                            setColorMap(i, LinearColorMap.green_, true);
                            break;
                        case 2:
                            setColorMap(i, LinearColorMap.blue_, true);
                            break;
                        case 3:
                            setColorMap(i, LinearColorMap.cyan_, true);
                            break;
                        case 4:
                            setColorMap(i, LinearColorMap.magenta_, true);
                            break;
                        case 5:
                            setColorMap(i, LinearColorMap.yellow_, true);
                            break;
                        case 6:
                            setColorMap(i, LinearColorMap.white_, true);
                            break;
                    }
                }
            }
        }
        finally
        {
            endUpdate();
        }

        // generate fromRGB maps
        generateFromRGBColorMaps();
    }

    /**
     * Return true if the colorspace's colormap contains an alpha component<br>
     * This is different from the isAlphaEnabled flag
     */
    public boolean hasAlphaComponent()
    {
        for (int comp = 0; comp < toRGBmaps.length; comp++)
            if (toRGBmaps[comp].getType() == IcyColorMapType.ALPHA)
                return true;

        return false;
    }

    // /**
    // * @return the alphaEnabled
    // */
    // public boolean isAlphaEnabled()
    // {
    // return alphaEnabled;
    // }
    //
    // /**
    // * @param alphaEnabled
    // * the alphaEnabled to set
    // */
    // public void setAlphaEnabled(boolean alphaEnabled)
    // {
    // this.alphaEnabled = alphaEnabled;
    // }

    /**
     * Generate FromRGB colormaps from ToRGB ones
     */
    private void generateFromRGBColorMaps()
    {
        for (int comp = 0; comp < toRGBmaps.length; comp++)
        {
            final IcyColorMap toRGBmap = toRGBmaps[comp];
            final float step = 1.0f / FromRGBColorMap.COLORMAP_MAX;

            for (float intensity = 0.0f; intensity <= 1.0f; intensity += step)
            {
                // blue
                fromRGBmaps[0].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedBlue(intensity));
                // green
                fromRGBmaps[1].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedGreen(intensity));
                // red
                fromRGBmaps[2].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedRed(intensity));
                // alpha
                fromRGBmaps[3].setFromRGBColor(comp, intensity, toRGBmap.getNormalizedAlpha(intensity));
            }
        }
    }

    /**
     * @see java.awt.color.ColorSpace#fromCIEXYZ(float[])
     */
    @Override
    public float[] fromCIEXYZ(float[] colorvalue)
    {
        return fromRGB(ColorUtil.sRGB.fromCIEXYZ(colorvalue));
    }

    /**
     * @see java.awt.color.ColorSpace#toCIEXYZ(float[])
     */
    @Override
    public float[] toCIEXYZ(float[] colorvalue)
    {
        return ColorUtil.sRGB.toCIEXYZ(toRGB(colorvalue));
    }

    /**
     * @see java.awt.color.ColorSpace#fromRGB(float[])
     */
    @Override
    public float[] fromRGB(float[] rgb)
    {
        final int numComponents = getNumComponents();
        final float[] result = new float[numComponents];

        final FromRGBColorMap blueMap = fromRGBmaps[0];
        final FromRGBColorMap greenMap = fromRGBmaps[1];
        final FromRGBColorMap redMap = fromRGBmaps[2];
        final FromRGBColorMap alphaMap = fromRGBmaps[3];

        final float blue = rgb[0];
        final float green = rgb[1];
        final float red = rgb[2];
        final float alpha;
        if (rgb.length > 3)
            alpha = rgb[3];
        else
            alpha = 1.0f;

        for (int comp = 0; comp < numComponents; comp++)
        {
            result[comp] = blueMap.getFromRGBColor(comp, blue);
            result[comp] += greenMap.getFromRGBColor(comp, green);
            result[comp] += redMap.getFromRGBColor(comp, red);
            result[comp] *= alphaMap.getFromRGBColor(comp, alpha);
            // limit
            if (result[comp] > 1.0f)
                result[comp] = 1.0f;
        }

        return result;
    }

    /**
     * return normalized component values from ARGB packed integer values
     * 
     * @param rgb
     * @return float[]
     */
    public float[] fromRGB(int rgb)
    {
        final int numComponents = getNumComponents();
        final float[] result = new float[numComponents];

        final FromRGBColorMap blueMap = fromRGBmaps[0];
        final FromRGBColorMap greenMap = fromRGBmaps[1];
        final FromRGBColorMap redMap = fromRGBmaps[2];
        final FromRGBColorMap alphaMap = fromRGBmaps[3];

        final int red, grn, blu, alp;
        alp = (rgb >> 24) & 0xFF;
        red = (rgb >> 16) & 0xFF;
        grn = (rgb >> 8) & 0xFF;
        blu = rgb & 0xFF;

        for (int comp = 0; comp < numComponents; comp++)
        {
            result[comp] = blueMap.maps[comp][blu];
            result[comp] += greenMap.maps[comp][grn];
            result[comp] += redMap.maps[comp][red];
            result[comp] *= alphaMap.maps[comp][alp];
            // limit
            if (result[comp] > 1.0f)
                result[comp] = 1.0f;
        }

        return result;
    }

    /**
     * return unnormalized ARGB values from colorMap scaled components values
     * 
     * @param colorvalue
     * @return ARGB as int
     */
    public int toRGBUnnorm(final int[] colorvalue)
    {
        final int numComponents = Math.min(getNumComponents(), colorvalue.length);

        // default alpha
        float alpha = 1f;
        // default max local alpha
        float maxLocalAlpha = 0f;
        // default RGB
        int r = 0, g = 0, b = 0;

        for (int comp = 0; comp < numComponents; comp++)
        {
            final IcyColorMap cm = toRGBmaps[comp];

            if (cm.isEnabled())
            {
                final int value = colorvalue[comp];

                final float alphaValue = cm.alpha.mapf[value];

                // alpha channel ?
                if (cm.getType() == IcyColorMapType.ALPHA)
                    alpha = alphaValue;
                else if (alphaValue > maxLocalAlpha)
                    maxLocalAlpha = alphaValue;

                final int premulRGB[] = cm.getPremulRGB()[value];

                b += premulRGB[0];
                g += premulRGB[1];
                r += premulRGB[2];
            }
        }

        // final alpha = alpha component value * maximum local alpha value
        final int a = (int) (alpha * maxLocalAlpha * IcyColorMap.MAX_LEVEL);

        if (a != 0)
        {
            final int inv = (1 << (IcyColorMap.COLORMAP_BITS + 8)) / a;

            // normalize on alpha
            b = (b * inv) >> 8;
            g = (g * inv) >> 8;
            r = (r * inv) >> 8;
        }

        return ((b > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : b)
                | (((g > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : g) << 8)
                | (((r > IcyColorMap.MAX_LEVEL) ? IcyColorMap.MAX_LEVEL : r) << 16) | (a << 24);
    }

    @Override
    public float[] toRGB(float[] colorvalue)
    {
        final float[] result = new float[4];
        final int numComponents = Math.min(getNumComponents(), colorvalue.length);

        // default alpha
        float alpha = 1f;
        // default max local alpha
        float maxLocalAlpha = 0f;
        // default RGB
        float rf = 0f, gf = 0f, bf = 0f;

        for (int comp = 0; comp < numComponents; comp++)
        {
            final IcyColorMap cm = toRGBmaps[comp];

            if (cm.isEnabled())
            {
                final int value = (int) (colorvalue[comp] * IcyColorMap.MAX_INDEX);
                final float alphaValue = cm.alpha.mapf[value];

                // alpha channel ?
                if (cm.getType() == IcyColorMapType.ALPHA)
                    alpha = alphaValue;
                else if (alphaValue > maxLocalAlpha)
                    maxLocalAlpha = alphaValue;

                final float premulRGBNorm[] = cm.getPremulRGBNorm()[value];

                bf += premulRGBNorm[0];
                gf += premulRGBNorm[1];
                rf += premulRGBNorm[2];
            }
        }

        // final alpha = alpha component value * maximum local alpha value
        final float af = alpha * maxLocalAlpha;

        if (af != 0f)
        {
            final float inv = 1f / af;

            // normalize on alpha
            bf *= inv;
            gf *= inv;
            rf *= inv;
        }

        result[0] = (bf > 1f) ? 1f : bf;
        result[1] = (gf > 1f) ? 1f : gf;
        result[2] = (rf > 1f) ? 1f : rf;
        result[3] = af;

        return result;
    }

    /**
     * Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
     * 
     * @param unnormSrc
     *        source buffer containing unnormalized values ([0..255] range) for each component
     * @param dest
     *        ARGB components buffer
     */
    public void fillARGBBuffer(int[][] unnormSrc, int[] dest, int offset, int length)
    {
        final int numComponents = getNumComponents();

        if (numComponents > 0)
        {
            final int[] input = new int[numComponents];

            for (int i = 0; i < length; i++)
            {
                // get data value
                for (int comp = 0; comp < numComponents; comp++)
                    input[comp] = unnormSrc[comp][i];

                // convert to RGBA
                dest[offset + i] = toRGBUnnorm(input);
            }
        }
    }

    /**
     * Set 8 bit ARGB data in an ARGB buffer from a scaled input buffer
     * 
     * @param unnormSrc
     *        source buffer containing unnormalized values ([0..255] range) for each component
     * @param dest
     *        ARGB components buffer
     */
    public void fillARGBBuffer(int[][] unnormSrc, int[] dest)
    {
        if ((unnormSrc == null) || (dest == null))
        {
            throw new IllegalArgumentException("Parameters 'unnormSrc' and 'destARGB' should not be null !");
        }

        final int numComponents = getNumComponents();

        if (unnormSrc.length != numComponents)
        {
            throw new IllegalArgumentException("Parameters 'unnormSrc' size is [" + unnormSrc.length + "][..] where ["
                    + numComponents + "][..] is expected !");
        }

        if (numComponents > 0)
        {
            final int size = unnormSrc[0].length;
            final int[] input = new int[numComponents];

            for (int i = 0; i < size; i++)
            {
                // get data value
                for (int comp = 0; comp < numComponents; comp++)
                    input[comp] = unnormSrc[comp][i];

                // convert to RGBA
                dest[i] = toRGBUnnorm(input);
            }
        }
    }

    /**
     * Return the number of component of colorSpace
     */
    @Override
    public int getNumComponents()
    {
        return toRGBmaps.length;
    }

    /**
     * Return the colormap of the specified component.
     */
    public IcyColorMap getColorMap(int component)
    {
        return toRGBmaps[component];
    }

    /**
     * @deprecated Use {@link #getColorMap(int)} instead (different case).
     */
    @Deprecated
    public IcyColorMap getColormap(int component)
    {
        return getColorMap(component);
    }

    /**
     * Set the colormap for the specified component (actually copy the content of source colormap).
     * 
     * @param component
     *        component we want to set the colormap
     * @param colorMap
     *        source colorMap
     * @param setAlpha
     *        also set the alpha information
     */
    public void setColorMap(int component, IcyColorMap colorMap, boolean setAlpha)
    {
        toRGBmaps[component].copyFrom(colorMap, setAlpha);
    }

    /**
     * @deprecated Use {@link #setColorMap(int, IcyColorMap, boolean)} instead.
     */
    @Deprecated
    public void setColormap(int component, IcyColorMap map)
    {
        setColorMap(component, map, true);
    }

    /**
     * @deprecated Use <code>setColormap(channel, map)</code> instead.
     */
    @Deprecated
    public void copyColormap(int component, IcyColorMap map, boolean copyName, boolean copyAlpha)
    {
        setColorMap(component, map, copyAlpha);

        if (copyName)
            toRGBmaps[component].setName(map.getName());
    }

    /**
     * @deprecated Use <code>setColormap(channel, map)</code> instead.
     */
    @Deprecated
    public void copyColormap(int component, IcyColorMap map, boolean copyName)
    {
        setColorMap(component, map, true);

        if (copyName)
            toRGBmaps[component].setName(map.getName());
    }

    /**
     * @deprecated Use <code>setColormap(channel, map)</code> instead.
     */
    @Deprecated
    public void copyColormap(int component, IcyColorMap map)
    {
        setColorMap(component, map, true);
    }

    /**
     * Return the RGB inverse colormap for specified RGB component.
     */
    public FromRGBColorMap getFromRGBMap(int component)
    {
        return fromRGBmaps[component];
    }

    /**
     * Set the RGB colormaps from a compatible colorModel.
     */
    public void setColorMaps(ColorModel cm)
    {
        if (cm instanceof IcyColorModel)
            setColorMaps((IcyColorSpace) cm.getColorSpace(), true);
        else
        {
            // get datatype and numComponent of source colorModel
            final Object srcElem = cm.getDataElements(0x0, null);
            final DataType srcDataType = ArrayUtil.getDataType(srcElem);
            final int srcNumComponents = ArrayUtil.getLength(srcElem);
            final DataType dataType = DataType.getDataTypeFromDataBufferType(cm.getTransferType());
            final int numComponents = getNumComponents();

            // can't recover colormap if we have different dataType or numComponents
            if ((srcNumComponents != numComponents) || (srcDataType != dataType))
                return;

            final boolean hasAlpha = cm.hasAlpha();

            for (int comp = 0; comp < numComponents; comp++)
            {
                final IcyColorMap map = toRGBmaps[comp];

                map.beginUpdate();
                try
                {
                    // set type
                    if (hasAlpha && (comp == (numComponents - 1)))
                        map.setType(IcyColorMapType.ALPHA);
                    else
                        map.setType(IcyColorMapType.RGB);

                    for (int index = 0; index < IcyColorMap.SIZE; index++)
                    {
                        switch (dataType.getJavaType())
                        {
                            case BYTE:
                            {
                                final byte bvalues[] = new byte[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        bvalues[i] = (byte) (index * (1 << 8) / IcyColorMap.SIZE);
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        bvalues[i] = (byte) (IcyColorMap.MAX_INDEX * (1 << 8) / IcyColorMap.SIZE);
                                    else
                                        bvalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(bvalues));
                                map.setRed(index, (short) cm.getRed(bvalues));
                                map.setGreen(index, (short) cm.getGreen(bvalues));
                                map.setBlue(index, (short) cm.getBlue(bvalues));
                                break;
                            }

                            case SHORT:
                            {
                                final short svalues[] = new short[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        svalues[i] = (short) (index * (1 << 16) / IcyColorMap.SIZE);
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        svalues[i] = (short) (IcyColorMap.MAX_INDEX * (1 << 16) / IcyColorMap.SIZE);
                                    else
                                        svalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(svalues));
                                map.setRed(index, (short) cm.getRed(svalues));
                                map.setGreen(index, (short) cm.getGreen(svalues));
                                map.setBlue(index, (short) cm.getBlue(svalues));
                                break;
                            }

                            case INT:
                            {
                                final int ivalues[] = new int[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        ivalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        ivalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
                                    else
                                        ivalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(ivalues));
                                map.setRed(index, (short) cm.getRed(ivalues));
                                map.setGreen(index, (short) cm.getGreen(ivalues));
                                map.setBlue(index, (short) cm.getBlue(ivalues));
                                break;
                            }

                            case LONG:
                            {
                                final long lvalues[] = new long[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        lvalues[i] = (index * (1 << 32) / IcyColorMap.SIZE);
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        lvalues[i] = (IcyColorMap.MAX_INDEX * (1 << 32) / IcyColorMap.SIZE);
                                    else
                                        lvalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(lvalues));
                                map.setRed(index, (short) cm.getRed(lvalues));
                                map.setGreen(index, (short) cm.getGreen(lvalues));
                                map.setBlue(index, (short) cm.getBlue(lvalues));
                                break;
                            }

                            case FLOAT:
                            {
                                final float fvalues[] = new float[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        fvalues[i] = (float) index / (float) IcyColorMap.SIZE;
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        fvalues[i] = (IcyColorMap.MAX_INDEX / (float) IcyColorMap.SIZE);
                                    else
                                        fvalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(fvalues));
                                map.setRed(index, (short) cm.getRed(fvalues));
                                map.setGreen(index, (short) cm.getGreen(fvalues));
                                map.setBlue(index, (short) cm.getBlue(fvalues));
                                break;
                            }

                            case DOUBLE:
                            {
                                final double dvalues[] = new double[numComponents];

                                // build an pixel element
                                for (int i = 0; i < numComponents; i++)
                                {
                                    if (i == comp)
                                        dvalues[i] = (double) index / (double) IcyColorMap.SIZE;
                                    else if (hasAlpha && (i == (numComponents - 1)))
                                        dvalues[i] = (IcyColorMap.MAX_INDEX / (double) IcyColorMap.SIZE);
                                    else
                                        dvalues[i] = 0;
                                }

                                // set colormap data
                                map.setAlpha(index, (short) cm.getAlpha(dvalues));
                                map.setRed(index, (short) cm.getRed(dvalues));
                                map.setGreen(index, (short) cm.getGreen(dvalues));
                                map.setBlue(index, (short) cm.getBlue(dvalues));
                                break;
                            }

                            default:
                                break;
                        }
                    }
                }
                finally
                {
                    map.endUpdate();
                }
            }
        }
    }

    /**
     * @deprecated Use {@link #setColorMaps(ColorModel)} instead (different case).
     */
    @Deprecated
    public void setColormaps(ColorModel cm)
    {
        setColorMaps(cm);
    }

    /**
     * @deprecated Use {@link #setColorMaps(ColorModel)} instead.
     */
    @Deprecated
    public void copyColormaps(ColorModel cm)
    {
        setColorMaps(cm);
    }

    /**
     * Set colormaps from specified colorSpace (do a copy).
     * 
     * @param source
     *        source colorspace to copy the colormaps from
     * @param setAlpha
     *        also set the alpha information
     */
    public void setColorMaps(IcyColorSpace source, boolean setAlpha)
    {
        final int numComponents = Math.min(source.getNumComponents(), getNumComponents());

        beginUpdate();
        try
        {
            // copy colormap
            for (int comp = 0; comp < numComponents; comp++)
                setColorMap(comp, source.getColorMap(comp), setAlpha);
        }
        finally
        {
            endUpdate();
        }
    }

    /**
     * @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
     */
    @Deprecated
    public void setColormaps(IcyColorSpace source)
    {
        setColorMaps(source, true);
    }

    /**
     * @deprecated Use {@link #setColorMaps(IcyColorSpace, boolean)} instead.
     */
    @Deprecated
    public void copyColormaps(IcyColorSpace source)
    {
        setColorMaps(source, true);
    }

    /**
     * get index of the specified colormap
     * 
     * @param colormap
     * @return index
     */
    public int indexOfColorMap(IcyColorMap colormap)
    {
        for (int i = 0; i < toRGBmaps.length; i++)
            if (toRGBmaps[i].equals(colormap))
                return i;

        return -1;
    }

    @Override
    public String getName(int idx)
    {
        // TODO: should get name from metadata
        return "Component #" + idx;
    }

    /**
     * Add a listener
     * 
     * @param listener
     */
    public void addListener(IcyColorSpaceListener listener)
    {
        listeners.add(listener);
    }

    /**
     * Remove a listener
     * 
     * @param listener
     */
    public void removeListener(IcyColorSpaceListener listener)
    {
        listeners.remove(listener);
    }

    /**
     * fire event
     */
    public void fireEvent(IcyColorSpaceEvent e)
    {
        for (IcyColorSpaceListener listener : new ArrayList<IcyColorSpaceListener>(listeners))
            listener.colorSpaceChanged(e);
    }

    /**
     * called when colorspace has changed (afaik when a colormap has changed)
     */
    private void changed(int component)
    {
        final IcyColorMap colorMap = getColorMap(component);

        // we can have only 1 alpha colormap
        if (colorMap != null)
        {
            // alpha type colormap ?
            if (colorMap.getType() == IcyColorMapType.ALPHA)
            {
                // check that others colormap are non alpha
                for (IcyColorMap map : toRGBmaps)
                {
                    if (map != colorMap)
                    {
                        // we have another ALPHA colormap ?
                        if (map.getType() == IcyColorMapType.ALPHA)
                            // set it to RGB
                            map.setType(IcyColorMapType.RGB);
                    }
                }
            }
        }

        // handle changed via updater object
        updater.changed(new IcyColorSpaceEvent(this, component));
    }

    /**
     * process on colorspace change
     */
    @Override
    public void onChanged(CollapsibleEvent compare)
    {
        final IcyColorSpaceEvent event = (IcyColorSpaceEvent) compare;

        // recalculate fromRGB colormaps
        generateFromRGBColorMaps();

        // notify listener we have changed
        fireEvent(event);
    }

    @Override
    public void colorMapChanged(IcyColorMapEvent e)
    {
        final int index = indexOfColorMap(e.getColormap());

        // colormap found ? raise a "changed" event
        if (index != -1)
            changed(index);
    }

    /**
     * @see icy.common.UpdateEventHandler#beginUpdate()
     */
    public void beginUpdate()
    {
        updater.beginUpdate();
    }

    /**
     * @see icy.common.UpdateEventHandler#endUpdate()
     */
    public void endUpdate()
    {
        updater.endUpdate();
    }

    /**
     * @see icy.common.UpdateEventHandler#isUpdating()
     */
    public boolean isUpdating()
    {
        return updater.isUpdating();
    }

}