/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.internal.navigator.framelist;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.commands.common.EventManager;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;

/**
 * Supports a web-browser style of navigation by maintaining a list
 * of frames.  Each frame holds a snapshot of a view at some point
 * in time.
 * <p>
 * The frame list obtains a snapshot of the current frame from a frame source
 * on creation, and whenever switching to a different frame.
 * </p>
 * <p>
 * A property change notification is sent whenever the current page changes.
 * </p>
 * @since 3.4
 */
public class FrameList extends EventManager {

    /** Property name constant for the current frame. */
    public static final String P_CURRENT_FRAME = "currentFrame"; //$NON-NLS-1$

    /** Property name constant for the reset. */
    public static final String P_RESET = "reset"; //$NON-NLS-1$

    private IFrameSource source;

    private List frames;

    private int current;

    /**
     * Creates a new frame list with the given source.
     *
     * @param source the frame source
     */
    public FrameList(IFrameSource source) {
        this.source = source;
        init();
    }

    /**
     * Adds a property change listener.
     * Has no effect if an identical listener is already registered.
     *
     * @param listener a property change listener
     */
    public void addPropertyChangeListener(IPropertyChangeListener listener) {
    	addListenerObject(listener);
    }

    /**
     * Moves the frame pointer back by one.
     * Has no effect if there is no frame before the current one.
     * Fires a <code>P_CURRENT_FRAME</code> property change event.
     */
    public void back() {
        if (current > 0) {
            setCurrent(current - 1);
        }
    }

    /**
     * Notifies any property change listeners that a property has changed.
     * Only listeners registered at the time this method is called are notified.
     *
     * @param event the property change event
     *
     * @see IPropertyChangeListener#propertyChange
     */
    protected void firePropertyChange(PropertyChangeEvent event) {
        Object[] listeners = getListeners();
        for (Object listener : listeners) {
            ((IPropertyChangeListener) listener).propertyChange(event);
        }
    }

    /**
     * Moves the frame pointer forward by one.
     * Has no effect if there is no frame after the current one.
     * Fires a <code>P_CURRENT_FRAME</code> property change event.
     */
    public void forward() {
        if (current < frames.size() - 1) {
            setCurrent(current + 1);
        }
    }

    /**
     * Returns the current frame.
     * Returns <code>null</code> if there is no current frame.
     *
     * @return the current frame, or <code>null</code>
     */
    public Frame getCurrentFrame() {
        return getFrame(current);
    }

    /**
     * Returns the index of the current frame.
     *
     * @return the index of the current frame
     */
    public int getCurrentIndex() {
        return current;
    }

    /**
     * Returns the frame at the given index, or <code>null</code>
     * if the index is &le; 0 or &ge; <code>size()</code>.
     *
     * @param index the index of the requested frame
     * @return the frame at the given index or <code>null</code>
     */
    public Frame getFrame(int index) {
        if (index < 0 || index >= frames.size()) {
			return null;
		}
        return (Frame) frames.get(index);
    }

    /**
     * Returns the frame source.
     * @return an IFrameSource
     */
    public IFrameSource getSource() {
        return source;
    }

    /**
     * Adds the given frame after the current frame,
     * and advances the pointer to the new frame.
     * Before doing so, updates the current frame, and removes any frames following the current frame.
     * Fires a <code>P_CURRENT_FRAME</code> property change event.
     *
     * @param frame the frame to add
     */
    public void gotoFrame(Frame frame) {
        for (int i = frames.size(); --i > current;) {
            frames.remove(i);
        }
        frame.setParent(this);
        int index = frames.size();
        frame.setIndex(index);
        frames.add(frame);
        setCurrent(index);
    }

    private void init() {
        Frame frame = source.getFrame(IFrameSource.CURRENT_FRAME, 0);
        frame.setParent(this);
        frame.setIndex(0);
        frames = new ArrayList();
        frames.add(frame);
        current = 0;
    }
    /**
     * Removes a property change listener.
     * Has no effect if an identical listener is not registered.
     *
     * @param listener a property change listener
     */
    public void removePropertyChangeListener(IPropertyChangeListener listener) {
        removeListenerObject(listener);
    }

    /**
     * Reset this frame list to its initial state.
     */
    public void reset() {
    	init();
        firePropertyChange(new PropertyChangeEvent(this, P_RESET,
                null, getFrame(current)));
    }

    /**
     * Sets the current frame to the one with the given index.
     * Updates the old current frame, and fires a <code>P_CURRENT_FRAME</code> property change event
     * if the current frame changes.
     *
     * @param newCurrent the index of the frame
     */
    void setCurrent(int newCurrent) {
        Assert.isTrue(newCurrent >= 0 && newCurrent < frames.size());
        int oldCurrent = this.current;
        if (oldCurrent != newCurrent) {
            updateCurrentFrame();
            this.current = newCurrent;
            firePropertyChange(new PropertyChangeEvent(this, P_CURRENT_FRAME,
                    getFrame(oldCurrent), getFrame(newCurrent)));
        }
    }

    /**
     * Sets the current frame to the frame with the given index.
     * Fires a <code>P_CURRENT_FRAME</code> property change event
     * if the current frame changes.
     * @param index
     */
    public void setCurrentIndex(int index) {
        if (index != -1 && index != current) {
			setCurrent(index);
		}
    }

    /**
     * Returns the number of frames in the frame list.
     * @return the number of frames.
     */
    public int size() {
        return frames.size();
    }

    /**
     * Replaces the current frame in this list with the current frame
     * from the frame source.  No event is fired.
     */
    public void updateCurrentFrame() {
        Assert.isTrue(current >= 0);
        Frame frame = source.getFrame(IFrameSource.CURRENT_FRAME,
                IFrameSource.FULL_CONTEXT);
        frame.setParent(this);
        frame.setIndex(current);
        frames.set(current, frame);
    }

}
