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

import icy.common.CollapsibleEvent;
import icy.util.StringUtil;

public class SequenceEvent implements CollapsibleEvent
{
    public enum SequenceEventSourceType
    {
        SEQUENCE_TYPE, SEQUENCE_META, SEQUENCE_COLORMAP, SEQUENCE_COMPONENTBOUNDS, SEQUENCE_DATA, SEQUENCE_ROI, /**
         * @deprecated
         **/
        @Deprecated
        SEQUENCE_PAINTER, SEQUENCE_OVERLAY
    }

    public enum SequenceEventType
    {
        CHANGED, ADDED, REMOVED
    }

    private final Sequence sequence;
    private final SequenceEventSourceType sourceType;
    private SequenceEventType type;
    private Object source;
    private int param;

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType)
    {
        this(sequence, sourceType, null, SequenceEventType.CHANGED, -1);
    }

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType, Object source)
    {
        this(sequence, sourceType, source, SequenceEventType.CHANGED, -1);
    }

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType, Object source, int param)
    {
        this(sequence, sourceType, source, SequenceEventType.CHANGED, param);
    }

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType, SequenceEventType type)
    {
        this(sequence, sourceType, null, type, -1);
    }

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType, Object source, SequenceEventType type)
    {
        this(sequence, sourceType, source, type, -1);
    }

    public SequenceEvent(Sequence sequence, SequenceEventSourceType sourceType, Object source, SequenceEventType type,
            int param)
    {
        super();

        this.sequence = sequence;
        this.sourceType = sourceType;
        this.source = source;
        this.type = type;
        this.param = param;
    }

    /**
     * @return the sequence
     */
    public Sequence getSequence()
    {
        return sequence;
    }

    /**
     * SourceType define the object type of <code>source</code><br>
     * <br>
     * The following source types are available:<br>
     * <code>SEQUENCE_TYPE</code> --> source object is null<br>
     * <code>SEQUENCE_META</code> --> source object define the meta data id (String)<br>
     * It can be <i>null</i> (consider global metadata change)<br>
     * <code>SEQUENCE_COLORMAP</code> --> source object is an instance of IcyColorModel<br>
     * <code>SEQUENCE_COMPONENTBOUNDS</code> --> source object is an instance of IcyColorModel<br>
     * <code>SEQUENCE_DATA</code> --> source object is an instance of IcyBufferedImage<br>
     * source object can be null when severals images has been modified<br>
     * <code>SEQUENCE_ROI</code> --> source object is an instance of ROI<br>
     * source object can be null when severals images has been modified<br>
     * <code>SEQUENCE_OVERLAY</code> --> source object is an instance of Overlay<br>
     * source object can be null when severals images has been modified<br>
     * <code>SEQUENCE_PAINTER</code> --> source object is an instance of Painter<br>
     * source object can be null when severals images has been modified<br>
     * <br>
     */
    public SequenceEventSourceType getSourceType()
    {
        return sourceType;
    }

    /**
     * Source object of the event.<br>
     * The object type here depend of the <code>sourceType</code> value.<br>
     */
    public Object getSource()
    {
        return source;
    }

    /**
     * Type define the type of event.<br>
     * <br>
     * When <code>sourceType</code> is one of the following :<br>
     * <code>SEQUENCE_TYPE, SEQUENCE_META, SEQUENCE_COLORMAP, SEQUENCE_COMPONENTBOUNDS</code><br>
     * the type can only be <code>SequenceEventType.CHANGED</code><br>
     * <br>
     * When <code>sourceType</code> is one of the following :<br>
     * <code>SEQUENCE_DATA, SEQUENCE_ROI, SEQUENCE_PAINTER, SEQUENCE_OVERLAY</code><br>
     * the type can also be <code>SequenceEventType.ADDED</code> or <code>SequenceEventType.REMOVED</code><br>
     * That mean a specific image, roi or painter (if <code>source != null</code>) has been added or
     * removed from the sequence.<br>
     * If <code>source == null</code> that mean we have a global change event and some stuff need to
     * be recalculated.<br>
     * Severals ADDED / CHANGED / REMOVE events can be compacted to one CHANGED event with a null
     * source (global change) for SEQUENCE_DATA source type.
     */
    public SequenceEventType getType()
    {
        return type;
    }

    /**
     * Extra parameter of event.<br>
     * <br>
     * It's used to specify the component number when <code>sourceType</code> is <code>SEQUENCE_COLORMAP</code> or
     * <code>SEQUENCE_COMPONENTBOUNDS</code> (in both case source
     * is instance of <code>IcyColorModel</code>).<br>
     * Also used internally...
     */
    public int getParam()
    {
        return param;
    }

    @Override
    public boolean collapse(CollapsibleEvent event)
    {
        if (equals(event))
        {
            final SequenceEvent e = (SequenceEvent) event;

            switch (sourceType)
            {
                case SEQUENCE_COLORMAP:
                case SEQUENCE_COMPONENTBOUNDS:
                    // join events in one global event
                    if (e.getParam() != param)
                        param = -1;
                    break;

                case SEQUENCE_DATA:
                    // optimize different type event to a single CHANGED event (for DATA only)
                    if (e.getType() != type)
                        type = SequenceEventType.CHANGED;
                    if (e.getSource() != source)
                        source = null;
                    break;

                default:
                    break;
            }

            return true;
        }

        return false;
    }

    @Override
    public int hashCode()
    {
        int res = sequence.hashCode() ^ sourceType.hashCode();

        switch (sourceType)
        {
            case SEQUENCE_META:
                if (source != null)
                    res ^= source.hashCode();
                break;

            case SEQUENCE_PAINTER:
            case SEQUENCE_OVERLAY:
            case SEQUENCE_ROI:
                res ^= type.hashCode();
                if (source != null)
                    res ^= source.hashCode();
                break;

            default:
                break;
        }

        return res;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj instanceof SequenceEvent)
        {
            final SequenceEvent e = (SequenceEvent) obj;

            // same source type
            if ((e.getSequence() == sequence) && (e.getSourceType() == sourceType))
            {
                switch (sourceType)
                {
                    case SEQUENCE_META:
                        return StringUtil.equals((String) e.getSource(), (String) source);

                    case SEQUENCE_COLORMAP:
                    case SEQUENCE_COMPONENTBOUNDS:
                        return true;

                    case SEQUENCE_DATA:
                        return true;

                    case SEQUENCE_PAINTER:
                    case SEQUENCE_OVERLAY:
                    case SEQUENCE_ROI:
                        return ((e.getType() == type) && (e.getSource() == source));

                    case SEQUENCE_TYPE:
                        return true;
                }
            }
        }

        return super.equals(obj);
    }
}