/*
 * 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.canvas;

import icy.main.Icy;
import icy.painter.Overlay;
import icy.painter.Overlay.OverlayPriority;
import icy.painter.OverlayEvent;
import icy.painter.OverlayEvent.OverlayEventType;
import icy.painter.OverlayListener;
import icy.painter.OverlayWrapper;
import icy.painter.Painter;
import icy.painter.WeakOverlayListener;
import icy.roi.ROI;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/**
 * Layer class.<br>
 * This class encapsulate {@link Overlay} in a canvas to<br>
 * add specific display properties (visibility, transparency...).
 */
public class Layer implements OverlayListener, Comparable<Layer>
{
    public interface LayerListener
    {
        public void layerChanged(Layer source, String propertyName);
    }

    public final static String PROPERTY_NAME = Overlay.PROPERTY_NAME;
    public final static String PROPERTY_PRIORITY = Overlay.PROPERTY_PRIORITY;
    public final static String PROPERTY_READONLY = Overlay.PROPERTY_READONLY;
    public final static String PROPERTY_CANBEREMOVED = Overlay.PROPERTY_CANBEREMOVED;
    public final static String PROPERTY_RECEIVEKEYEVENTONHIDDEN = Overlay.PROPERTY_RECEIVEKEYEVENTONHIDDEN;
    public final static String PROPERTY_RECEIVEMOUSEEVENTONHIDDEN = Overlay.PROPERTY_RECEIVEMOUSEEVENTONHIDDEN;
    /**
     * @deprecated Use {@link #PROPERTY_OPACITY} instead
     */
    @Deprecated
    public final static String PROPERTY_ALPHA = "alpha";
    public final static String PROPERTY_OPACITY = "opacity";
    public final static String PROPERTY_VISIBLE = "visible";

    public final static String DEFAULT_NAME = "layer";

    /**
     * Returns true if the Layer need to be repainted when the specified property has changed.
     */
    public static boolean isPaintProperty(String propertyName)
    {
        if (propertyName == null)
            return false;

        return propertyName.equals(PROPERTY_OPACITY) || propertyName.equals(PROPERTY_PRIORITY)
                || propertyName.equals(PROPERTY_VISIBLE);
    }

    static Overlay createOverlayWrapper(@SuppressWarnings("deprecation") Painter painter, String name)
    {
        if (painter instanceof Overlay)
            return (Overlay) painter;

        final Overlay result = new OverlayWrapper(painter, name);

        if (name == null)
            result.setName(DEFAULT_NAME);

        // default priority
        result.setPriority(OverlayPriority.SHAPE_NORMAL);

        return result;
    }

    private final Overlay overlay;
    // cache for ROI
    private WeakReference<ROI> roi;

    private boolean visible;
    private float alpha;

    /**
     * listeners
     */
    protected final List<LayerListener> listeners;

    public Layer(Overlay overlay)
    {
        this.overlay = overlay;

        overlay.addOverlayListener(new WeakOverlayListener(this));

        visible = true;
        alpha = 1f;
        roi = null;

        listeners = new ArrayList<LayerListener>();
    }

    /**
     * @deprecated Use {@link #Layer(Overlay)} instead.
     */
    @Deprecated
    public Layer(Painter painter, String name)
    {
        this(createOverlayWrapper(painter, name));
    }

    /**
     * @deprecated Use {@link #Layer(Overlay)} instead.
     */
    @Deprecated
    public Layer(Painter painter)
    {
        this(painter, null);
    }

    /**
     * Returns the attached {@link Overlay}.
     */
    public Overlay getOverlay()
    {
        return overlay;
    }

    /**
     * @deprecated Use {@link #getOverlay()} instead.
     */
    @Deprecated
    public Painter getPainter()
    {
        final Overlay result = getOverlay();

        if (result instanceof OverlayWrapper)
            return ((OverlayWrapper) result).getPainter();

        return result;
    }

    /**
     * Returns layer priority (internally use the overlay priority).
     * 
     * @see Overlay#getPriority()
     */
    public OverlayPriority getPriority()
    {
        return overlay.getPriority();
    }

    /**
     * Set the layer priority (internally set the overlay priority).
     * 
     * @see Overlay#setPriority(OverlayPriority)
     */
    public void setPriority(OverlayPriority priority)
    {
        overlay.setPriority(priority);
    }

    /**
     * Returns layer name (internally use the overlay name).
     * 
     * @see Overlay#getName()
     */
    public String getName()
    {
        return overlay.getName();
    }

    /**
     * Set the layer name (internally set the overlay name)
     * 
     * @see Overlay#setName(String)
     */
    public void setName(String name)
    {
        overlay.setName(name);
    }

    /**
     * Returns the read only property name (internally use the overlay read only property).
     * 
     * @see Overlay#isReadOnly()
     */
    public boolean isReadOnly()
    {
        return overlay.isReadOnly();
    }

    /**
     * Set read only property (internally set the overlay read only property).
     * 
     * @see Overlay#setReadOnly(boolean)
     */
    public void setReadOnly(boolean readOnly)
    {
        overlay.setReadOnly(readOnly);
    }

    /**
     * Returns fixed property.
     * 
     * @deprecated Use {@link #getCanBeRemoved()} instead.
     */
    @Deprecated
    public boolean isFixed()
    {
        return !getCanBeRemoved();
    }

    /**
     * @deprecated Use {@link #setCanBeRemoved(boolean)} instead.
     */
    @Deprecated
    public void setFixed(boolean value)
    {
        setCanBeRemoved(value);
    }

    /**
     * Returns <code>true</code> if the layer can be freely removed from the Canvas where it
     * appears and <code>false</code> otherwise.<br/>
     * 
     * @see Overlay#getCanBeRemoved()
     */
    public boolean getCanBeRemoved()
    {
        return overlay.getCanBeRemoved();
    }

    /**
     * Set the <code>canBeRemoved</code> property.<br/>
     * Set it to false if you want to prevent the layer to be removed from the Canvas where it
     * appears.
     * 
     * @see Overlay#setCanBeRemoved(boolean)
     */
    public void setCanBeRemoved(boolean value)
    {
        overlay.setCanBeRemoved(value);
    }

    /**
     * @see Overlay#getReceiveKeyEventOnHidden()
     */
    public boolean getReceiveKeyEventOnHidden()
    {
        return overlay.getReceiveKeyEventOnHidden();
    }

    /**
     * @see Overlay#setReceiveKeyEventOnHidden(boolean)
     */
    public void setReceiveKeyEventOnHidden(boolean value)
    {
        overlay.setReceiveKeyEventOnHidden(value);
    }

    /**
     * @see Overlay#getReceiveMouseEventOnHidden()
     */
    public boolean getReceiveMouseEventOnHidden()
    {
        return overlay.getReceiveMouseEventOnHidden();
    }

    /**
     * @see Overlay#setReceiveMouseEventOnHidden(boolean)
     */
    public void setReceiveMouseEventOnHidden(boolean value)
    {
        overlay.setReceiveMouseEventOnHidden(value);
    }

    /**
     * @return the attachedROI
     */
    public ROI getAttachedROI()
    {
        if (roi == null)
            // search for attached ROI
            roi = new WeakReference<ROI>(Icy.getMainInterface().getROI(overlay));

        return roi.get();
    }

    /**
     * @return the visible
     */
    public boolean isVisible()
    {
        return visible;
    }

    /**
     * @param visible
     *        the visible to set
     */
    public void setVisible(boolean visible)
    {
        if (this.visible != visible)
        {
            this.visible = visible;
            changed(PROPERTY_VISIBLE);
        }
    }

    /**
     * @return the layer opacity
     */
    public float getOpacity()
    {
        return alpha;
    }

    /**
     * Set the layer opacity
     */
    public void setOpacity(float value)
    {
        if (alpha != value)
        {
            alpha = value;
            changed(PROPERTY_OPACITY);
        }
    }

    /**
     * @deprecated Use {@link #getOpacity()} instead
     */
    @Deprecated
    public float getAlpha()
    {
        return getOpacity();
    }

    /**
     * @deprecated Use {@link #setOpacity(float)} instead.
     */
    @Deprecated
    public void setAlpha(float value)
    {
        setOpacity(value);
    }

    /**
     * Called on layer property change
     */
    protected void changed(String propertyName)
    {
        // notify listener
        fireChangedEvent(propertyName);
    }

    /**
     * fire event
     */
    private void fireChangedEvent(String propertyName)
    {
        final List<LayerListener> list;

        synchronized (listeners)
        {
            list = new ArrayList<Layer.LayerListener>(listeners);
        }

        for (LayerListener listener : list)
            listener.layerChanged(this, propertyName);
    }

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

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

    @Override
    public void overlayChanged(OverlayEvent event)
    {
        // only interested by property change here
        if (event.getType() == OverlayEventType.PROPERTY_CHANGED)
            changed(event.getPropertyName());
    }

    @Override
    public int compareTo(Layer layer)
    {
        // compare with overlay
        return getOverlay().compareTo(layer.getOverlay());
    }
}