/*
 * @(#)VisibilityFocusTraversalPolicy.java 2/28/2012
 *
 * Copyright 2002 - 2012 JIDE Software Inc. All rights reserved.
 */

package com.jidesoft.swing;

import com.jidesoft.utils.ReflectionUtils;

import javax.swing.*;
import java.util.HashSet;
import java.awt.*;
import java.util.Set;

/**
 * The focus traversal policy to screen out those components that are not actually painted in the target container.
 *
 * @since 3.3.6
 */
public class VisibilityFocusTraversalPolicy extends FocusTraversalPolicy {
    private FocusTraversalPolicy _defaultPolicy;
    private Set<Container> _containers;

    /**
     * The constructor.
     *
     * @param defaultPolicy the default FocusTraversalPolicy
     * @since 3.4.0
     */
    public VisibilityFocusTraversalPolicy(FocusTraversalPolicy defaultPolicy) {
        this(defaultPolicy, null);
    }

    /**
     * The constructor.
     *
     * @param defaultPolicy the default FocusTraversalPolicy
     * @param container     the container to check the visibility of its child components
     */
    public VisibilityFocusTraversalPolicy(FocusTraversalPolicy defaultPolicy, Container container) {
        super();
        if (defaultPolicy != null && ReflectionUtils.isSubClassOf(defaultPolicy, "LegacyGlueFocusTraversalPolicy")) {
            throw new IllegalArgumentException("VisibilityFocusTraversalPolicy can't work well with LegacyGlueFocusTraversalPolicy.");
        }
        _defaultPolicy = defaultPolicy;
        if (container != null) {
            _containers = new HashSet<Container>();
            _containers.add(container);
        }
    }

    /**
     * Adds the container that needs to check the visibility of its child component.
     *
     * @param container the container
     * @since 3.4.0
     */
    public void addContainer(Container container) {
        if (_containers == null) {
            _containers = new HashSet<Container>();
        }
        _containers.add(container);
    }

    /**
     * Removes the container that needs to check the visibility of its child component.
     *
     * @param container the container
     * @since 3.4.0
     */
    public void removeContainer(Container container) {
        if (_containers != null) {
            _containers.remove(container);
        }
    }

    /**
     * Gets all the containers that need to check the visibility of its child component.
     *
     * @return the container array.
     * @since 3.4.0
     */
    public Container[] getContainers() {
        if (_containers == null) {
            return new Container[0];
        }
        else {
            return _containers.toArray(new Container[_containers.size()]);
        }
    }

    /**
     * Gets the default FocusTraversalPolicy
     *
     * @return the default FocusTraversalPolicy.
     */
    public FocusTraversalPolicy getDefaultPolicy() {
        return _defaultPolicy;
    }

    @Override
    public Component getComponentAfter(Container aContainer, Component aComponent) {
        if (_defaultPolicy == null) {
            return null;
        }
        Component component = _defaultPolicy.getComponentAfter(aContainer, aComponent);
        boolean componentChanged = true;
        while (component != null && aComponent != component && componentChanged) {
            componentChanged = false;
            for (Container container : _containers) {
                if (container.isAncestorOf(component)) {
                    Rectangle collapsiblePaneBounds = container.getBounds();
                    Container parent = container.getParent();
                    Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
                    if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
                        return component;
                    }
                    component = _defaultPolicy.getComponentAfter(aContainer, component);
                    componentChanged = true;
                    break;
                }
            }
        }
        return component;
    }

    @Override
    public Component getComponentBefore(Container aContainer, Component aComponent) {
        if (_defaultPolicy == null) {
            return null;
        }
        Component component = _defaultPolicy.getComponentBefore(aContainer, aComponent);
        boolean componentChanged = true;
        while (component != null && aComponent != component && componentChanged) {
            componentChanged = false;
            for (Container container : _containers) {
                if (container.isAncestorOf(component)) {
                    Rectangle collapsiblePaneBounds = container.getBounds();
                    Container parent = container.getParent();
                    Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
                    if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
                        return component;
                    }
                    component = _defaultPolicy.getComponentBefore(aContainer, component);
                    componentChanged = true;
                    break;
                }
            }
        }
        return component;
    }

    @Override
    public Component getFirstComponent(Container aContainer) {
        if (_defaultPolicy == null) {
            return null;
        }
        Component component = _defaultPolicy.getFirstComponent(aContainer);
        Component aComponent = component;
        boolean componentChanged;
        do {
            componentChanged = false;
            for (Container container : _containers) {
                if (container.isAncestorOf(component)) {
                    Rectangle collapsiblePaneBounds = container.getBounds();
                    Container parent = container.getParent();
                    Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
                    if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
                        return component;
                    }
                    component = _defaultPolicy.getComponentAfter(aContainer, component);
                    componentChanged = true;
                    break;
                }
            }
        }
        while (component != null && aComponent != component && componentChanged);
        return component;
    }

    @Override
    public Component getLastComponent(Container aContainer) {
        if (_defaultPolicy == null) {
            return null;
        }
        Component component = _defaultPolicy.getLastComponent(aContainer);
        Component aComponent = component;
        boolean componentChanged;
        do {
            componentChanged = false;
            for (Container container : _containers) {
                if (container.isAncestorOf(component)) {
                    Rectangle collapsiblePaneBounds = container.getBounds();
                    Container parent = container.getParent();
                    Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
                    if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
                        return component;
                    }
                    component = _defaultPolicy.getComponentBefore(aContainer, component);
                    componentChanged = true;
                    break;
                }
            }
        }
        while (component != null && aComponent != component && componentChanged);
        return component;
    }

    @Override
    public Component getDefaultComponent(Container aContainer) {
        if (_defaultPolicy == null) {
            return null;
        }
        Component component = _defaultPolicy.getDefaultComponent(aContainer);
        Component aComponent = component;
        boolean old = _defaultPolicy instanceof SortingFocusTraversalPolicy && ((SortingFocusTraversalPolicy) _defaultPolicy).getImplicitDownCycleTraversal();
        if (_defaultPolicy instanceof SortingFocusTraversalPolicy) {
            ((SortingFocusTraversalPolicy) _defaultPolicy).setImplicitDownCycleTraversal(false);
        }
        try {
            boolean componentChanged;
            do {
                componentChanged = false;
                for (Container container : _containers) {
                    if (container.isAncestorOf(component)) {
                        Rectangle collapsiblePaneBounds = container.getBounds();
                        Container parent = container.getParent();
                        Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
                        if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
                            return component;
                        }
                        component = _defaultPolicy.getComponentAfter(aContainer, component);
                        componentChanged = true;
                        break;
                    }
                }
            }
            while (component != null && aComponent != component && componentChanged);
            return component;
        }
        finally {
            if (_defaultPolicy instanceof SortingFocusTraversalPolicy) {
                ((SortingFocusTraversalPolicy) _defaultPolicy).setImplicitDownCycleTraversal(old);
            }
        }
    }

    @Override
    public Component getInitialComponent(Window window) {
        if (_defaultPolicy == null) {
            return null;
        }
        return _defaultPolicy.getInitialComponent(window);
    }
}
