/* class Container
 *
 * Copyright (C) 2001-2003  R M Pitman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package charva.awt;

import charva.awt.event.KeyEvent;

import java.util.Enumeration;
import java.util.Vector;

/**
 * Container is the abstract superclass of Window and Panel.
 */
public abstract class Container
        extends Component {
    public Container() {
    }

    /**
     * doLayout is intended to be used by subclasses of Container, such
     * as Window, JPanel and JScrollPane.
     */
    public void doLayout() {

        if (_isValid)
            return;

        if (_layoutMgr != null) {
            _layoutMgr.doLayout(this);

            /* Don't set the _isValid flag if the layout manager flag
             * is an instance of LayoutManager2; the doLayout method must
             * be called every time because the parent window may have been
             * resized.
             * Instances of LayoutManager, on the other hand, are not affected
             * be resizing of the parent window.
             */
            if (_layoutMgr instanceof LayoutManager2 == false)
                _isValid = true;
        }
    }

    public Dimension getSize() {
        return new Dimension(_size);
    }

    public int getHeight() {
        return _size.height;
    }

    public int getWidth() {
        return _size.width;
    }

    public void setSize(Dimension size_) {
        _size = new Dimension(size_);
        invalidate();
    }

    public void setSize(int width_, int height_) {
        _size.width = width_;
        _size.height = height_;
        invalidate();
    }

    public void setHeight(int height_) {
        _size.height = height_;
        invalidate();
    }

    public void setWidth(int width_) {
        _size.width = width_;
        invalidate();
    }

    public Dimension minimumSize() {
        if (_layoutMgr == null)
            return _size;

        if (_isValid == false)
            _minimumSize = _layoutMgr.minimumSize(this);
        return _minimumSize;
    }

    /**
     * Returns the component at the specified index.
     */
    public Component getComponent(int n) {
        return (Component) _components.elementAt(n);
    }

    /**
     * Returns the component that contains the specified point, or null
     * if no component contains the point. The x and y coordinates of
     * the point are relative to the origin of this container.
     */
    public Component getComponentAt(Point p) {
        return getComponentAt(p.x, p.y);
    }

    /**
     * Returns the component that contains the specified point, or null
     * if no component contains the point. The x and y coordinates of
     * the point are relative to the origin of this container.
     */
    public Component getComponentAt(int x, int y) {
        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.contains(x, y)) {
                if (c instanceof Container) {
                    // Calculate the coordinates of the point relative
                    // to the origin of the container
                    Point origin = c.getLocation();
                    return ((Container) c).getComponentAt(x - origin.x, y - origin.y);
                } else
                    return c;
            }
        }
        return null;
    }

    /**
     * The contained component will inherit the foreground and background
     * colors of the container if they have not been set yet.
     */
    public Component add(Component component_) {

        _addComponent(component_);
        if (_layoutMgr != null && _layoutMgr instanceof LayoutManager2) {
            if (_layoutMgr instanceof BorderLayout) {
                ((BorderLayout) _layoutMgr).addLayoutComponent(component_, BorderLayout.CENTER);
            } else {
                throw new IllegalArgumentException("LayoutManager2 requires a constraint object");
            }
        }
        invalidate();
        return component_;
    }

    /**
     * Removes the specified component from this container.
     */
    public void remove(Component component_) {
        _components.remove(component_);
        component_.setParent(null);
        if (_currentFocus == component_) {
            _currentFocus = null;
            _currentFocus = getCurrentFocus();
        }
        invalidate();
    }

    private void _addComponent(Component component_) {

        /* Add the specified component to the list of components in this
         * container.
         */
        _components.addElement(component_);

        /* Set this container as the parent of the component.
         */
        component_.setParent(this);
    }

    /**
     * Adds the specified component to the end of this container. Also
     * notifies the layout manager to add this component to the layout
     * using the specified constraint.
     * If the layout manager does not implement the LayoutManager2 interface,
     * i.e. does not know about layout constraints, we silently ignore the
     * constraint (maybe fix this to throw an exception?).
     */
    public void add(Component component_, Object constraint_) {

        _addComponent(component_);    // add to this container.

        if (_layoutMgr != null && _layoutMgr instanceof LayoutManager2) {
            ((LayoutManager2) _layoutMgr).addLayoutComponent(component_, constraint_);
        }
    }

    public void setLayout(LayoutManager mgr_) {
        _layoutMgr = mgr_;
    }

    /**
     * Returns an array of all the components in this container.
     */
    public Component[] getComponents() {
        int arraylen = _components.size();
        Component[] array = new Component[arraylen];
        for (int i = 0; i < arraylen; i++) {
            array[i] = (Component) _components.elementAt(i);
        }
        return array;
    }

    /**
     * Returns the number of components in this Container.
     */
    public int getComponentCount() {
        return _components.size();
    }

    /**
     * Draw all the components in this container.
     */
    public void draw() {

        if (!isVisible())
            return;

        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.isVisible())
                c.draw();
        }
    }

    /**
     * Sets the foreground color of this container and all its
     * contained components that do not yet have their foreground
     * color set.  Overrides the same method in the Component class.
     */
    public void setForeground(Color color_) {
        super.setForeground(color_);

        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.getForeground() == null)
                c.setForeground(color_);
        }
    }

    /**
     * Sets the background color of this container and all its
     * contained components that do not yet have their background
     * color set.  Overrides the same method in the Component class.
     */
    public void setBackground(Color color_) {
        super.setBackground(color_);

        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.getBackground() == null)
                c.setBackground(color_);
        }
    }

    public void processKeyEvent(KeyEvent ke_) {

        /** Invoke all the KeyListener callbacks that may have been registered
         * for this Container.
         */
        super.processKeyEvent(ke_);
        if (ke_.isConsumed())
            return;

        /* Propagate the KeyEvent down to the current focus component
         * inside this container.
         */
        if (_currentFocus != null) {
            _currentFocus.processKeyEvent(ke_);
        }
    }

    public void requestFocus() {
        getCurrentFocus().requestFocus();
    }

    /**
     * Return a reference to the (non-container) component inside this
     * Container that has the keyboard input focus (or would have it,
     * if the focus was inside this container). If no component inside
     * the container has the focus, choose the first FocusTraversable
     * component.
     *
     * @return the Component in this container that would have the focus;
     *         never null.
     * @throws IllegalComponentStateException if there is no
     *                                        focus-traversable component in this container.
     */
    public Component getCurrentFocus() {

        if (_currentFocus == null) {
            /* _currentFocus is not yet set. Try to set it to the first
             * FocusTraversable component contained in this container.
             */
            Enumeration e = _components.elements();
            while (e.hasMoreElements()) {
                Component c = (Component) e.nextElement();
                if (c.isFocusTraversable()) {
                    _currentFocus = c;
                    break;
                }
            }
        }

        if (_currentFocus == null) {
            throw new IllegalComponentStateException("no focus-traversable components inside this Container");
        }
        if (_currentFocus instanceof Container) {
            return ((Container) _currentFocus).getCurrentFocus();
        } else
            return _currentFocus;
    }

    /**
     * Set the _currentFocus to refer to the next focus-traversable component
     * in the list of contained components, and put FocusEvents on the queue,
     * one for the component that is losing the focus and one for the component
     * gaining the focus.
     */
    public void nextFocus() {

        /* Put a FOCUS_LOST event on the queue for the component that is
         * losing the focus.
         * If the current focus is a Container, then this method will have been
         * called by that container (which would already have posted a
         * FOCUS_LOST event for its own contained component that was losing
         * focus).
        if ((_currentFocus instanceof Container) == false) {
            FocusEvent evt = new FocusEvent(AWTEvent.FOCUS_LOST, _currentFocus);
            EventQueue evtQueue =
                Toolkit.getDefaultToolkit().getSystemEventQueue();
            evtQueue.postEvent(evt);
        }
         */

        /* Determine which component should get focus next.
         */
        int index = _components.indexOf(_currentFocus);
        if (index == -1) {
            throw new IllegalComponentStateException("focus component not found in parent");
        }

        Component focusCandidate;

        for (; ;) {
            /* If the focus was owned by the last component in this container,
             * the new focus should go to the next component in the parent
             * container, IF THERE IS A PARENT (this container may be a
             * Window, in which case the parent is null).
             */
            if (++index >= _components.size()) {
                if (getParent() != null) {
                    getParent().nextFocus();
                    return;
                } else {
                    /* Don't need to worry about infinite loops. Worst case, we
                     * should just end up where we started.
                     */
                    index = 0;
                }
            }

            focusCandidate = (Component) _components.elementAt(index);

            /* If the next component will not accept the focus, continue
             * trying until we get one that does.
             */
            if (focusCandidate.isFocusTraversable())
                break;
        }
        if (focusCandidate instanceof Container)
            ((Container) focusCandidate).firstFocus();

        focusCandidate.requestFocus();
    }

    /**
     * Set the _currentFocus to refer to the previous focus-traversable
     * component in the list of contained components, and put FocusEvents on
     * the queue, one for the component that is losing the focus and one for
     * the component gaining the focus.
     */
    public void previousFocus() {

        /* Put a FOCUS_LOST event on the queue for the component that is
         * losing the focus.
         * If the current focus is a Container, then this method will have been
         * called by that container (which would already have posted a
         * FOCUS_LOST event for its own contained component that was losing
         * focus).
        if ((_currentFocus instanceof Container) == false) {
            FocusEvent evt = new FocusEvent(AWTEvent.FOCUS_LOST, _currentFocus);
            EventQueue evtQueue =
                Toolkit.getDefaultToolkit().getSystemEventQueue();
            evtQueue.postEvent(evt);
        }
         */

        /* Determine which component should get focus next.
         */
        int index = _components.indexOf(_currentFocus);
        if (index == -1) {
            throw new IllegalArgumentException("focus component not found in parent");
        }

        Component focusCandidate;

        for (; ;) {
            /* If the focus was owned by the first component in this container,
             * the new focus should go to the previous component in the parent
             * container, IF THERE IS A PARENT (this container may be a
             * Window, in which case the parent is null).
             */
            if (--index < 0) {
                if (getParent() != null) {
                    getParent().previousFocus();
                    return;
                } else {
                    index = _components.size() - 1;
                }
            }

            focusCandidate = (Component) _components.elementAt(index);

            /* If the next component will not accept the focus, continue
             * trying until we get one that does.
             */
            if (focusCandidate.isFocusTraversable())
                break;
        }
        if (focusCandidate instanceof Container)
            ((Container) focusCandidate).lastFocus();

        focusCandidate.requestFocus();
    }

    /**
     * Set this container's current keyboard focus. Called by the
     * requestFocus() method of the contained component.
     */
    public void setFocus(Component focus_) {
        _currentFocus = focus_;
        if (getParent() != null)
            getParent().setFocus(this);
    }

    /**
     * Return true if any of the components within this Container
     * are focus-traversable (i.e. will accept keyboard input focus when
     * TAB or SHIFT-TAB is pressed).
     */
    public boolean isFocusTraversable() {
        if (!super.isFocusTraversable())
            return false;

        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.isFocusTraversable())
                return true;
        }
        return false;
    }

    public Insets getInsets() {
        return _insets;
    }

    /* Default implementation of debug, gets overridden by subclasses.
     */
    public void debug(int level_) {
        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            c.debug(level_ + 1);
        }
    }

    /**
     * Sets the keyboard focus to the first component that is focusTraversable.
     * Called by the nextFocus() method when it runs out of components in
     * the current container to move the focus to.  The nextFocus() method
     * first checks that this container contains a focusTraversable component
     * before calling this.
     */
    private void firstFocus() {

        Enumeration e = _components.elements();
        while (e.hasMoreElements()) {
            Component c = (Component) e.nextElement();
            if (c.isFocusTraversable()) {
                if (c instanceof Container) {
                    ((Container) c).firstFocus();
                }

                _currentFocus = c;
                return;
            }
        }
    }

    /**
     * Sets the keyboard focus to the last component that is focusTraversable.
     * Called by the previousFocus() method when it runs out of components in
     * the current container to move the focus to.  The previousFocus() method
     * first checks that this container contains a focusTraversable component
     * before calling this.
     */
    private void lastFocus() {

        for (int i = _components.size() - 1; i >= 0; i--) {
            Component c = (Component) _components.elementAt(i);
            if (c.isFocusTraversable()) {
                if (c instanceof Container) {
                    ((Container) c).lastFocus();
                }

                _currentFocus = c;
                return;
            }
        }
    }

    /**
     * Validates this container and all of its contained components.
     * The programmer must call validate() on a container to cause it
     * to re-layout its contained components after components have
     * been added, removed or resized.
     */
    public void validate() {
        if (_isValid)
            return;

        /* doLayout sets the validate flag (unless the layout manager is
         * an instance of LayoutManager2).
         */
        doLayout();
    }

    /**
     * Determines whether this component is valid.  A container is valid when
     * it is correctly sized and positioned within its parent container and
     * all its children are also valid.
     */
    public boolean isValid() {
        return _isValid;
    }

    /**
     * Marks the container and all parents above it as needing to be laid out
     * again.
     */
    public void invalidate() {
        _isValid = false;
        super.invalidate();
    }

    //====================================================================
    // INSTANCE VARIABLES

    /**
     * The list of components contained within this Container.
     */
    protected Vector _components = new Vector();

    /**
     * The container's size
     */
    protected Dimension _size = new Dimension(1, 1);

    /**
     * The layout manager that will be used to lay out the components.
     */
    protected LayoutManager _layoutMgr = null;

    /**
     * The component (which may itself be a Container) inside this Container
     * that currently has the input focus (or, if the input focus is
     * currently outside this Container, the component to which focus will
     * return if and when this Container regains focus).
     */
    protected Component _currentFocus = null;

    /**
     * The insets define how much padding to insert inside the Container,
     * to take into account the border frame (if any).
     * For a Window they will be (1,1); for a Panel, they will be (0,0).
     */
    protected Insets _insets = new Insets(0, 0, 0, 0);

    /**
     * A flag that is set to true when the container is laid out, and set to
     * false when a component is added or removed from the container
     * (indicating that it needs to be laid out again).
     */
    protected boolean _isValid = false;

    /**
     * Used for caching the minimum size of this container, so that we don't
     * have to keep recalculating it. This dimension is valid only if _isValid
     * is true.
     */
    protected Dimension _minimumSize;

}
