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

import icy.common.listener.weak.WeakListener;
import icy.gui.frame.IcyFrame;
import icy.gui.frame.IcyFrameAdapter;
import icy.gui.frame.IcyFrameEvent;
import icy.gui.util.WindowPositionSaver;
import icy.main.Icy;
import icy.util.StringUtil;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.HeadlessException;
import java.awt.Point;
import java.util.EventListener;

import javax.swing.JPanel;
import javax.swing.WindowConstants;

/**
 * Externalizable panel component.<br>
 * Basically this is a JPanel you can externalize and display in an IcyFrame.<br>
 * 
 * @author Stephane
 */
public class ExternalizablePanel extends JPanel
{
    public static class WeakStateListener extends WeakListener<StateListener> implements StateListener
    {
        public WeakStateListener(StateListener listener)
        {
            super(listener);
        }

        @Override
        public void removeListener(Object source)
        {
            if (source != null)
                ((ExternalizablePanel) source).removeStateListener(this);
        }

        @Override
        public void stateChanged(ExternalizablePanel source, boolean externalized)
        {
            final StateListener listener = getListener(source);

            if (listener != null)
                listener.stateChanged(source, externalized);
        }
    }

    public static interface StateListener extends EventListener
    {
        public void stateChanged(ExternalizablePanel source, boolean externalized);
    }

    public class Frame extends IcyFrame
    {
        public Frame(String title) throws HeadlessException
        {
            super(title, true, true, true, true);

            setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

            addFrameListener(new IcyFrameAdapter()
            {
                @Override
                public void icyFrameClosing(IcyFrameEvent e)
                {
                    super.icyFrameClosing(e);

                    // ignore the event when frame is manually closed or application is exiting
                    if (!(closed || Icy.isExiting()))
                    {
                        if (ExternalizablePanel.this.isExternalized())
                            ExternalizablePanel.this.internalizeInternal();
                    }
                }
            });

            setLayout(new BorderLayout());
            setSize(400, 400);
        }
    }

    /**
     * 
     */
    private static final long serialVersionUID = -7690543443681714719L;

    /**
     * extern frame
     */
    protected final Frame frame;
    /**
     * parent component
     */
    private Container parent;

    /**
     * internals
     */
    private boolean internalizationAutorized;
    private boolean externalizationAutorized;
    boolean closed;

    // we need to keep reference on it as the object only use weak reference
    final WindowPositionSaver positionSaver;

    /**
     * Create a new externalizable panel.
     * 
     * @param title
     *        title for the associated frame.
     * @param key
     *        save key, used for WindowPositionSaver.<br>
     *        Set to null or empty string disable parameter saving.
     * @param defLoc
     *        the default location for the frame (externalized state)
     * @param defDim
     *        the default dimension for the frame (externalized state)
     */
    public ExternalizablePanel(String title, String key, Point defLoc, Dimension defDim)
    {
        super();

        frame = new Frame(title);
        parent = null;
        internalizationAutorized = true;
        externalizationAutorized = true;
        closed = false;

        // use window position saver with default parameters
        if (!StringUtil.isEmpty(key))
            positionSaver = new WindowPositionSaver(this, "frame/" + key, defLoc, defDim);
        else
            positionSaver = null;
    }

    /**
     * Create a new externalizable panel.
     * 
     * @param title
     *        title for the associated frame.
     * @param key
     *        save key, used for WindowPositionSaver.<br>
     *        Set to null or empty string disable parameter saving.
     */
    public ExternalizablePanel(String title, String key)
    {
        // default location and dimension for extern frame
        this(title, key, new Point(200, 200), new Dimension(400, 300));
    }

    public ExternalizablePanel(String title)
    {
        this(title, null);
    }

    public ExternalizablePanel()
    {
        this("", null);
    }

    @Override
    public void addNotify()
    {
        super.addNotify();

        // set parent on first attachment
        if (parent == null)
        {
            final Container p = getParent();

            if ((p != frame.getInternalFrame().getContentPane()) && (p != frame.getExternalFrame().getContentPane()))
                parent = p;
        }
    }

    /**
     * Close the panel (close and release associated frames and resources).
     */
    public void close()
    {
        closed = true;
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.close();
    }

    /**
     * Manual parent set
     */
    public void setParent(Container value)
    {
        parent = value;
    }

    /**
     * @return the internalizationAutorized
     */
    public boolean isInternalizationAutorized()
    {
        return internalizationAutorized;
    }

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

    /**
     * @return the externalizationAutorized
     */
    public boolean isExternalizationAutorized()
    {
        return externalizationAutorized;
    }

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

    /**
     * Externalize panel in an independent frame
     */
    public void externalize()
    {
        if (isInternalized())
            externalizeInternal();
    }

    /**
     * Internalize panel (remove from independent frame)
     */
    public void internalize()
    {
        if (isExternalized())
            internalizeInternal();
    }

    /**
     * Externalize panel (internal method)
     */
    void externalizeInternal()
    {
        if (!externalizationAutorized)
            return;

        // externalize
        if (parent != null)
        {
            parent.remove(this);
            parent.validate();
        }

        frame.add(this, BorderLayout.CENTER);
        frame.validate();
        frame.addToDesktopPane();
        frame.setVisible(true);

        // notify
        fireStateChange(true);
    }

    /**
     * Internalize panel (internal method)
     */
    void internalizeInternal()
    {
        if (!internalizationAutorized)
            return;

        // internalize
        frame.setVisible(false);
        frame.remove(this);
        frame.validate();
        frame.removeFromMainDesktopPane();

        if (parent != null)
        {
            parent.add(this);
            parent.validate();
        }

        // notify
        fireStateChange(false);
    }

    /**
     * Switch from internalized <--> externalized state and vice versa
     */
    public void switchState()
    {
        if (isExternalized())
            internalizeInternal();
        else
            externalizeInternal();
    }

    public boolean isInternalized()
    {
        return !frame.isVisible();
    }

    public boolean isExternalized()
    {
        return frame.isVisible();
    }

    /**
     * @return the frame
     */
    public IcyFrame getFrame()
    {
        return frame;
    }

    // /**
    // * Implement getMinimumSize method for external frame only
    // */
    // public Dimension getMinimumSizeExternal()
    // {
    // return frame.getMinimumSize();
    // }
    //
    // /**
    // * Implement getMaximumSize method for external frame only
    // */
    // public Dimension getMaximumSizeExternal()
    // {
    // return frame.getMaximumSize();
    // }
    //
    // /**
    // * Implement getPreferredSize method for external frame only
    // */
    // public Dimension getPreferredSizeExternal()
    // {
    // return frame.getPreferredSize();
    // }
    //
    // /**
    // * Implement getSize method for external frame only
    // */
    // public Dimension getSizeExternal()
    // {
    // return frame.getSize();
    // }
    //
    // /**
    // * Implement getHeight method for external frame only
    // */
    // public int getHeightExternal()
    // {
    // return frame.getHeight();
    // }
    //
    // /**
    // * Implement getWidth method for external frame only
    // */
    // public int getWidthExternal()
    // {
    // return frame.getWidth();
    // }
    //
    // /**
    // * Implement getLocation method for external frame only
    // */
    // public Point getLocationExternal()
    // {
    // return frame.getLocation();
    // }
    //
    // /**
    // * Implement getBounds method for external frame only
    // */
    // public Rectangle getBoundsExternal()
    // {
    // return frame.getBounds();
    // }
    //
    // /**
    // * Implement setLocation method for external frame only
    // */
    // public void setLocationExternal(final Point p)
    // {
    // frame.setLocation(p);
    // }
    //
    // /**
    // * Implement setLocation method for external frame only
    // */
    // public void setLocationExternal(final int x, final int y)
    // {
    // frame.setLocation(x, y);
    // }
    //
    // /**
    // * Implement setSize method for external frame only
    // */
    // public void setSizeExternal(final Dimension d)
    // {
    // frame.setSize(d);
    // }
    //
    // /**
    // * Implement setSize method for external frame only
    // */
    // public void setSizeExternal(final int width, final int height)
    // {
    // frame.setSize(width, height);
    // }
    //
    // /**
    // * Implement setPreferredSize method for external frame only
    // */
    // public void setPreferredSizeExternal(final Dimension d)
    // {
    // frame.setPreferredSize(d);
    // }
    //
    // /**
    // * Implement setMinimumSize method for external frame only
    // */
    // public void setMinimumSizeExternal(final Dimension d)
    // {
    // frame.setMinimumSize(d);
    // }
    //
    // /**
    // * Implement setMaximumSize method for external frame only
    // */
    // public void setMaximumSizeExternal(final Dimension d)
    // {
    // frame.setMaximumSize(d);
    // }

    /**
     * Fire state change event
     */
    private void fireStateChange(boolean externalized)
    {
        for (StateListener l : listenerList.getListeners(StateListener.class))
            l.stateChanged(this, externalized);
    }

    /**
     * Implement addFrameListener method
     */
    public void addStateListener(StateListener l)
    {
        listenerList.add(StateListener.class, l);
    }

    /**
     * Implement removeFrameListener method
     */
    public void removeStateListener(StateListener l)
    {
        listenerList.remove(StateListener.class, l);
    }
}