/*
 * Created on Apr 21, 2005
 */
package org.flexdock.dockbar.layout;

import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Rectangle;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.swing.JComponent;
import javax.swing.border.Border;

import org.flexdock.dockbar.Dockbar;
import org.flexdock.dockbar.DockbarManager;
import org.flexdock.dockbar.ViewPane;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.props.DockablePropertySet;
import org.flexdock.docking.state.MinimizationManager;
import org.flexdock.util.Utilities;

/**
 * @author Christopher Butler
 */
public class DockbarLayout {
    public static final int MINIMUM_VIEW_SIZE = 20;
    private static final int[] EDGES = {
        MinimizationManager.LEFT, MinimizationManager.RIGHT,
        MinimizationManager.BOTTOM
    };

    private DockbarManager manager;
    private JComponent leftEdgeGuide;
    private JComponent rightEdgeGuide;
    private JComponent bottomEdgeGuide;

    public DockbarLayout(DockbarManager mgr) {
        manager = mgr;
    }



    public void layout() {
        // makes sure our current insets are up to date before trying to layout
        // the dockbars.  otherwise, they will be layed out with improper bounds.
        updateInsets();

        Rectangle rect = DockbarLayoutManager.getManager().getLayoutArea(manager);
        int rightX = rect.x + rect.width;
        int bottomY = rect.y + rect.height;

        Dockbar leftBar = manager.getLeftBar();
        Dockbar rightBar = manager.getRightBar();
        Dockbar bottomBar = manager.getBottomBar();

        Dimension leftPref = leftBar.getPreferredSize();
        Dimension rightPref = rightBar.getPreferredSize();
        Dimension bottomPref = bottomBar.getPreferredSize();

        // set the dockbar bounds
        leftBar.setBounds(rect.x, rect.y, leftPref.width, rect.height-bottomPref.height);
        rightBar.setBounds(rightX-rightPref.width, rect.y, rightPref.width, rect.height-bottomPref.height);
        // use complete window width for proper statusbar support
        bottomBar.setBounds(rect.x, bottomY-bottomPref.height, rect.width, bottomPref.height);
        layoutViewpane();
    }


    public int getDesiredViewpaneSize() {
        Dockable dockable = manager.getActiveDockable();
        if(dockable==null)
            return 0;

        Rectangle rect = DockbarLayoutManager.getManager().getViewArea(manager, dockable);
        DockablePropertySet props = dockable.getDockingProperties();

        // determine what percentage of the viewable area we want the viewpane to take up
        float viewSize = props.getPreviewSize().floatValue();
        int edge = manager.getActiveEdge();
        if(edge==MinimizationManager.LEFT || edge==MinimizationManager.RIGHT) {
            return (int)(((float)rect.width)*viewSize);
        }
        return (int)(((float)rect.height)*viewSize);
    }

    protected void layoutViewpane() {
        ViewPane viewPane = manager.getViewPane();
        Dockable dockable = manager.getActiveDockable();
        if(dockable==null) {
            viewPane.setBounds(0, 0, 0, 0);
            return;
        }

        int edge = manager.getActiveEdge();
        int viewpaneSize = viewPane.getPrefSize();
        if(viewpaneSize==ViewPane.UNSPECIFIED_PREFERRED_SIZE)
            viewpaneSize = getDesiredViewpaneSize();

        Rectangle rect = DockbarLayoutManager.getManager().getViewArea(manager, dockable);
        if(edge==MinimizationManager.LEFT || edge==MinimizationManager.RIGHT) {
            if(edge==MinimizationManager.RIGHT) {
                rect.x = rect.x + rect.width - viewpaneSize;
            }
            rect.width = viewpaneSize;
        } else {
            if(edge==MinimizationManager.BOTTOM) {
                rect.y = rect.y + rect.height - viewpaneSize;
            }
            rect.height = viewpaneSize;
        }
        viewPane.setBounds(rect);
    }


    private void updateInsets() {
        Insets emptyInsets = getEmptyInsets();
        boolean changed = resetGuideBorders();

        HashSet borderSet = new HashSet(3);
        add(borderSet, getInsetBorder(MinimizationManager.LEFT));
        add(borderSet, getInsetBorder(MinimizationManager.RIGHT));
        add(borderSet, getInsetBorder(MinimizationManager.BOTTOM));

        HashSet guideSet = new HashSet(3);
        add(guideSet, getCurrentEdgeGuide(MinimizationManager.LEFT));
        add(guideSet, getCurrentEdgeGuide(MinimizationManager.RIGHT));
        add(guideSet, getCurrentEdgeGuide(MinimizationManager.BOTTOM));

        for(Iterator it=borderSet.iterator(); it.hasNext();) {
            InsetBorder border = (InsetBorder)it.next();
            changed = border.setEmptyInsets(emptyInsets) || changed;
        }

        if(changed) {
            for(Iterator it=guideSet.iterator(); it.hasNext();) {
                JComponent guide = (JComponent)it.next();
                guide.revalidate();
            }
        }
    }

    private boolean resetGuideBorders() {
        boolean changed = resetGuide(MinimizationManager.LEFT);
        changed = resetGuide(MinimizationManager.RIGHT) || changed ;
        changed = resetGuide(MinimizationManager.BOTTOM) || changed ;

        toggleInsetBorder(MinimizationManager.LEFT);
        toggleInsetBorder(MinimizationManager.RIGHT);
        toggleInsetBorder(MinimizationManager.BOTTOM);

        return changed;
    }

    private boolean resetGuide(int edge) {
        JComponent currGuide = getCurrentEdgeGuide(edge);
        JComponent newGuide = queryEdgeGuide(edge);
        boolean changed = false;

        if(Utilities.isChanged(currGuide, newGuide)) {
            changed = true;
            // if the edge-guide has changed, then we're going to try to remove the old
            // InsetBorder from the previous edge-guide
            Border b = currGuide==null? null: currGuide.getBorder();
            InsetBorder fakeBorder = b instanceof InsetBorder? (InsetBorder)b: null;
            // check to see if the previous edge-guide already had an InsetBorder
            if(fakeBorder!=null) {
                // replace the InsetBorder with the old edge-guide's real border
                Border realBorder = fakeBorder.getWrappedBorder();
                currGuide.setBorder(realBorder);
                fakeBorder.toggleEdge(edge, false);
            }
        }

        // if there is no new edge-guide, then we can't set an InsetBorder for it
        if(newGuide==null)
            return setCurrentEdgeGuide(edge, newGuide) || changed;

        Border border = newGuide.getBorder();
        // if the new edge-guide doesn't have an InsetBorder, then install one
        if(!(border instanceof InsetBorder)) {
            changed = true;
            InsetBorder insetBorder = InsetBorder.createBorder(border, true, new Insets(-1, -1, -1, -1));
            newGuide.setBorder(insetBorder);
        }

        // make sure our edge tracking on the InsetBorder is cleared out.
        // we will rebuild it later on
        InsetBorder insetBorder = (InsetBorder)newGuide.getBorder();
        insetBorder.clearEdges();

        // update the edge-guide reference
        return setCurrentEdgeGuide(edge, newGuide) || changed;
    }

    private void toggleInsetBorder(int edge) {
        InsetBorder border = getInsetBorder(edge);
        if(border!=null) {
            ((InsetBorder)border).toggleEdge(edge, true);
        }
    }

    private InsetBorder getInsetBorder(int edge) {
        JComponent comp = getCurrentEdgeGuide(edge);
        Border border = comp==null? null: comp.getBorder();
        return border instanceof InsetBorder? (InsetBorder)border: null;
    }


    private Insets getEmptyInsets() {
        return new Insets(0, getLeftInset(), getBottomInset(), getRightInset());
    }

    private int getLeftInset() {
        return getDockbarInset(manager.getLeftBar());
    }

    private int getRightInset() {
        return getDockbarInset(manager.getRightBar());
    }

    private int getBottomInset() {
        return getDockbarInset(manager.getBottomBar());
    }

    private int getDockbarInset(Dockbar dockbar) {
        boolean visible = dockbar.isVisible();
        if(!visible)
            return 0;

        Dimension dim = dockbar.getPreferredSize();
        if(dockbar==manager.getLeftBar() || dockbar==manager.getRightBar())
            return dim.width;
        return dim.height;
    }

    private JComponent queryEdgeGuide(int constraint) {
        return DockbarLayoutManager.getManager().getEdgeGuide(manager, constraint);
    }

    private JComponent getCurrentEdgeGuide(int constraint) {
        switch(constraint) {
        case MinimizationManager.LEFT:
            return leftEdgeGuide;
        case MinimizationManager.RIGHT:
            return rightEdgeGuide;
        case MinimizationManager.BOTTOM:
            return bottomEdgeGuide;
        }
        return null;
    }

    private boolean setCurrentEdgeGuide(int constraint, JComponent comp) {
        boolean changed = getCurrentEdgeGuide(constraint)==comp;
        switch(constraint) {
        case MinimizationManager.LEFT:
            leftEdgeGuide = comp;
            break;
        case MinimizationManager.RIGHT:
            rightEdgeGuide = comp;
            break;
        case MinimizationManager.BOTTOM:
            bottomEdgeGuide = comp;
            break;
        }
        return changed;
    }

    private void add(Set set, Object obj) {
        if(obj!=null)
            set.add(obj);
    }
}
