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

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;

import icy.common.listener.ChangeListener;
import icy.system.thread.ThreadUtil;

/**
 * Utility class to handle <code>Update</code> type event.
 * 
 * @author stephane
 */
public class UpdateEventHandler
{
    ChangeListener parent;

    /**
     * dispatch in AWT dispatch thread
     */
    private boolean awtDispatch;
    /**
     * internal update counter
     */
    private int updateCnt;
    /**
     * internal pending change events
     */
    private final LinkedHashMap<CollapsibleEvent, CollapsibleEvent> pendingChanges;

    /**
     * 
     */
    public UpdateEventHandler(ChangeListener parent, boolean awtDispatch)
    {
        super();

        this.parent = parent;
        this.awtDispatch = awtDispatch;

        updateCnt = 0;
        pendingChanges = new LinkedHashMap<CollapsibleEvent, CollapsibleEvent>();
    }

    /**
     * 
     */
    public UpdateEventHandler(ChangeListener parent)
    {
        this(parent, false);
    }

    /**
     * @return the awtDispatch
     */
    public boolean isAwtDispatch()
    {
        return awtDispatch;
    }

    /**
     * @param awtDispatch
     *        the awtDispatch to set
     */
    public void setAwtDispatch(boolean awtDispatch)
    {
        this.awtDispatch = awtDispatch;
    }

    public Collection<CollapsibleEvent> getPendingChanges()
    {
        return pendingChanges.values();
    }

    public void beginUpdate()
    {
        updateCnt++;
    }

    public void endUpdate()
    {
        updateCnt--;
        if (updateCnt <= 0)
        {
            final List<CollapsibleEvent> events;

            synchronized (pendingChanges)
            {
                events = new ArrayList<CollapsibleEvent>(pendingChanges.values());
                pendingChanges.clear();
            }

            // dispatch all contained events (use copy to avoid concurrent changes)
            for (CollapsibleEvent event : events)
                dispatchOnChanged(event);
        }
    }

    public boolean isUpdating()
    {
        return updateCnt > 0;
    }

    public boolean hasPendingChanges()
    {
        return !pendingChanges.isEmpty();
    }

    protected void addPendingChange(CollapsibleEvent change)
    {
        final CollapsibleEvent previousChange;

        // TODO: can take sometime (select all on many ROI)
        // TODO: check how fast is it now...
        synchronized (pendingChanges)
        {
            // search in pending changes if we have an equivalent change
            previousChange = pendingChanges.get(change);

            // not already existing ? --> just add the new change
            if (previousChange == null)
                pendingChanges.put(change, change);
        }

        // found an equivalent previous change ? --> collapse the new change into the old one
        if (previousChange != null)
            previousChange.collapse(change);
    }

    public void changed(CollapsibleEvent event)
    {
        if (isUpdating())
            addPendingChange(event);
        else
            dispatchOnChanged(event);
    }

    protected void dispatchOnChanged(CollapsibleEvent event)
    {
        final CollapsibleEvent e = event;

        if (awtDispatch)
        {
            // dispatch on AWT Dispatch Thread now
            ThreadUtil.invokeNow(new Runnable()
            {
                @Override
                public void run()
                {
                    parent.onChanged(e);
                }
            });
        }
        else
            parent.onChanged(e);
    }
}