package guiDomain;

import java.util.*;
import java.awt.*;
import java.awt.event.*;

/**
 * Class FlexibleGridLayout manages the layout of a set
 * of components whose bounds can be manually changed using the mouse.
 *
 * Inner classes Knob and ComponentInfo are at the end of this listing.
 */

public class FlexibleGridLayout implements LayoutManager
{
    static final int MAX_ROWS = 16; //maximum number of rows for each column
    static final int MAX_COLS = 4; //Maxuimum number of columns

    static final int KNOB_WIDTH = 8; //Width of the component's bottom knob
    static final int KNOB_HEIGHT = 4; //Height of the component's bottom knob
    static final int VERTICAL_KNOB_WIDTH = 4;
                     //Width of the knob for vertical line
    static final int VERTICAL_KNOB_HEIGHT = 8;
                     //Height of the knob for vertical line

    static final int PREFERRED_WIDTH = 250; //Preferred width
    static final int PREFERRED_HEIGHT = 150; //Preferred height
    static final int MIN_WIDTH = 20; //Min width
    static final int MIN_HEIGHT = 10; //Min height

    int numCols; //Number of columns
    int[] rowArray = new int[MAX_COLS];
                 //For each column, the pre-defined number of rows
    int verticalGap = 2; //Vertical gap among displayed windows
    int horizontalGap = 2; //Horizontal gap among displayed windows

    Vector verticalKnobsV = new Vector();
    //Vector of (numCols - 1) knobs associated with  vertical separators

    Vector componentColsV = new Vector();
    //Vector of components columns. Each element of this vector is a vector

    //In resize management it is necessary to identify the column and
    //the ComponentInfo associated with a bottom knob. The following two
    //hashtable simplify coding.

    //knobToInfoH hashtable maps knobs to the associated ComponentInfo.
    //The ComponentInfo instance refers to the window above the horizontal knob
    Hashtable knobToInfoH = new Hashtable();

    //knobToColumnH hashtable maps (vertical) knobs to a Vector containing
    //all the ComponentInfo instances for that column (at the left of the
    //vertical knob)
    Hashtable knobToColumnH = new Hashtable();

    int startMouseX, startMouseY;
                //Used for mouse management

    public FlexibleGridLayout(int cols, int[] rows)
    {
        numCols = cols;
        for (int i = 0; i < numCols; i++)
        {
            rowArray[i] = rows[i];
            componentColsV.addElement(new Vector());
        }

    }

    //LayoutManager interface implementation

    //Lays out managed components
    public void layoutContainer(Container parent)
    {
        Insets insets = parent.getInsets();
        int parentWidth = parent.getSize().width - (insets.left + insets.right);
        int parentHeight = parent.getSize().height - (insets.top + insets.bottom);

        //If too little
        if (parentWidth <= 0 || parentHeight <= 0)
            return;

        //Lay out managed components and associated knobs
        //(for component's bottom line)
        int baseX = insets.left, baseY;
        for (int i = 0; i < numCols; i++)
        {
            baseY = insets.top;
            Vector currColV = (Vector) componentColsV.elementAt(i);
            int numRows = currColV.size();
            for (int j = 0; j < numRows; j++)
            {
                ComponentInfo currInfo = (ComponentInfo) currColV.elementAt(j);
                int currWidth = (int) Math.round(
                                      currInfo.widthFact * parentWidth);
                int currHeight = (int) Math.round(
                                      currInfo.heightFact * parentHeight);
                currInfo.component.setBounds(baseX + horizontalGap / 2,
                                              baseY + verticalGap / 2,
                                              currWidth - horizontalGap,
                                             currHeight - verticalGap);
                if(currInfo.knob != null)
                      //If it is not the last component in the column
                    currInfo.knob.setBounds(baseX + 3 * currWidth / 4,
                                 baseY + currHeight - horizontalGap / 2,
                                            KNOB_WIDTH,
                                            KNOB_HEIGHT);
                baseY += currHeight;
                if (j == numRows - 1)
                    baseX += currWidth;
                      //All the rows in the same column have the same width
            }
        }
        //Lay out knobs for vertical lines
        baseX = insets.left;
        for (int i = 0; i < verticalKnobsV.size(); i++)
        {
            Knob currKnob = (Knob) verticalKnobsV.elementAt(i);
            //Get from the hashtable the corresponding column
            Vector currCol = (Vector)knobToColumnH.get(currKnob);
            //All the components in the same column have the same width factor
            double currWidthFact = ((ComponentInfo)currCol.elementAt(0)).widthFact;
            int currWidth = (int)Math.round(currWidthFact * parentWidth);
            baseX += currWidth;
            currKnob.setBounds(baseX - verticalGap/2, 3 * parentHeight / 4,
                               VERTICAL_KNOB_WIDTH,
                               VERTICAL_KNOB_HEIGHT);
        }
    }

    //Add the component in the first available position
    public void addLayoutComponent(String name, Component comp)
    {
        if (comp instanceof Knob)
                //Knobs are of internal usage for this layout manager
            return;
        //Find first available position
        for (int i = 0; i < numCols; i++)
        {
            Vector currCol = (Vector) componentColsV.elementAt(i);
            if (currCol.size() < rowArray[i])
            { //If the actual number of panels for that column is
              //less than the predefined value
                Knob newKnob = null;
                if (currCol.size() < rowArray[i] - 1)
                { //If it is not the last component of the row
                    newKnob = new Knob();
                    comp.getParent().add(newKnob);
                }
                ComponentInfo newInfo = new ComponentInfo
                                            (0., 0., comp, newKnob);
                currCol.addElement(newInfo);
                if (currCol.size() == 1)
                { //If it is the first element in the column
                    resetColumnWidth();
                    if (i < numCols - 1)
                    { //The vertical knob is not added to the last column
                        Knob newVerticalKnob = new Knob();
                        verticalKnobsV.addElement(newVerticalKnob);
                        comp.getParent().add(newVerticalKnob);
                        knobToColumnH.put(newVerticalKnob, currCol);
                    }
                }
                resetColumnRowsHeight(i);
                          //In any case reset height within column
                //Update KnobToInfo hashtables
                if (newKnob != null)
                {
                    knobToInfoH.put(newKnob, newInfo);
                    knobToColumnH.put(newKnob, currCol);
                }
                break; //Stop searching
            }
        }
    }

    //Remove the given component: not supported by this layout manager
    public void removeLayoutComponent(Component comp){}

    //Return the minimum size of the managed container
    public Dimension minimumLayoutSize(Container parent)
    {
        Dimension dim = new Dimension(0, 0);
        int maxComponentsInCol = 0;
        //The number of components cannot be derived from the parent's
        //getComponentCount() as this comprises also the knobs
        for (int i = 0; i < componentColsV.size(); i++)
        {
            Vector currCol = (Vector) componentColsV.elementAt(i);
            int currSize = currCol.size();
            if (currSize > maxComponentsInCol)
                maxComponentsInCol = currSize;
        }
        int minWidth = numCols * horizontalGap;
        int minHeight = maxComponentsInCol * verticalGap;

        //Add the container's insets
        Insets insets = parent.getInsets();
        dim.width = minWidth + insets.left + insets.right;
        dim.height = minHeight + insets.top + insets.bottom;
        return dim;
    }

    //Return the preferred size of the managed container
    public Dimension preferredLayoutSize(Container parent)
    {
        return new Dimension(PREFERRED_WIDTH, PREFERRED_HEIGHT);
    }

    //End LayoutManager Implementation

    //Set the same width for all nonempty columns
    private void resetColumnWidth()
    {
        //First count nonempty columns
        int actCols = 0;
        double actWidthFact = 0;
        for (int i = 0; i < componentColsV.size(); i++)
        {
            Vector currCol = (Vector) componentColsV.elementAt(i);
            if (currCol.size() > 0)
                actCols++;
            //Set the width facto for all components
        }
        actWidthFact = 1. / actCols;
        for (int i = 0; i < componentColsV.size(); i++)
        {
            Vector currCol = (Vector) componentColsV.elementAt(i);
            for (int j = 0; j < currCol.size(); j++)
            {
                ComponentInfo currInfo = (ComponentInfo) currCol.
                    elementAt(j);
                currInfo.widthFact = actWidthFact;
            }
        }
    }

    //Set the same width for all nonempty columns
    private void resetColumnRowsHeight(int col)
    {
        //Fisrt count nonempty columns

        Vector currCol = (Vector) componentColsV.elementAt(col);
        if (currCol.size() > 0)
        {
            double actHeightFact = 1. / currCol.size();
            //The width Factor for the first element of the columns is computed
            //when created, and it has to be copied to the other components of the column
            double actWidthFact = ((ComponentInfo)currCol.elementAt(0)).widthFact;
            for (int j = 0; j < currCol.size(); j++)
            {
                ComponentInfo currInfo = (ComponentInfo) currCol.
                    elementAt(j);
                currInfo.heightFact = actHeightFact;
                currInfo.widthFact = actWidthFact;
            }
        }
    }

    void handleKnobMousePressed(Knob knob, MouseEvent me)
    {
        startMouseX = me.getX();
        startMouseY = me.getY();
    }

    //General resize policy is handled here
    void handleKnobMouseMoved(Knob knob, MouseEvent me)
    {
        int parentWidth = knob.getParent().getBounds().width;
        int parentHeight = knob.getParent().getBounds().height;
        ComponentInfo currInfo = (ComponentInfo) knobToInfoH.get(knob);
        if (currInfo != null)
        { //If the knob is associated with a managed component
            int deltaY = me.getY() - startMouseY;
            int currHeight = (int)Math.round(
                               currInfo.heightFact * parentHeight);
            int newHeight = currHeight + deltaY;
            //Get ComponentInfo instance and column from hashtables
            Vector currCol = (Vector) knobToColumnH.get(knob);

            int rowIdx = currCol.indexOf(currInfo);
            if (deltaY > 0)
            {
                //Check if the resizing can be done
                int numBelow = currCol.size() - rowIdx - 1;
                if (deltaY > numBelow * (MIN_WIDTH + verticalGap))
                    return; //No room: we cannot do the resize
                int remainingHeight = deltaY;
                for (int i = rowIdx + 1; i < currCol.size() &
                              remainingHeight > 0; i++)
                {
                    ComponentInfo belowInfo = (ComponentInfo)
                                          currCol.elementAt(i);
                    int belowHeight = (int)Math.round(
                                    belowInfo.heightFact * parentHeight);
                    int newBelowHeight = belowHeight - remainingHeight;
                    //Ceck whether we force a component below its minimum width
                    if (newBelowHeight < MIN_HEIGHT + verticalGap)
                    {
                        newBelowHeight = MIN_HEIGHT + verticalGap;
                        remainingHeight -= belowHeight -
                            (MIN_HEIGHT + verticalGap);
                    }
                    else
                        remainingHeight = 0;
                    belowInfo.heightFact = newBelowHeight /
                        (double) parentHeight;
                }
                if(remainingHeight > 0)
                        //If Not all required resizing can be accomplished
                     newHeight -= remainingHeight;
                 currInfo.heightFact = newHeight/(double)parentHeight;
            }
            else
            { //Do the same for upper components in the column
                //Ceck if the resizing can be done
                int numUpper = rowIdx + 1;
                if ( -deltaY > numUpper * (MIN_WIDTH + verticalGap))
                    return; //No Room: we cannot do resizing
                int remainingHeight = -deltaY;
                for (int i = rowIdx; i >= 0 & remainingHeight > 0; i--)
                {
                    ComponentInfo upperInfo = (ComponentInfo)
                                  currCol.elementAt(i);
                    int upperHeight = (int)Math.round(
                                  upperInfo.heightFact * parentHeight);
                    int newUpperHeight = upperHeight - remainingHeight;
                    if (newUpperHeight < MIN_HEIGHT + verticalGap)
                                    //If we shrink too much
                    {
                        newUpperHeight = MIN_HEIGHT + verticalGap;
                        remainingHeight -= upperHeight -
                            (MIN_HEIGHT + verticalGap);
                    }
                    else
                        remainingHeight = 0;
                    upperInfo.heightFact = newUpperHeight /
                        (double) parentHeight;
                }
                //We need to enlarge the bottom window
                //Note that a bottom component is always present
                ComponentInfo bottomInfo = (ComponentInfo)
                                            currCol.elementAt(rowIdx + 1);
                int bottomHeight = (int)Math.round(
                                        bottomInfo.heightFact * parentHeight);
                int newBottomHeight = bottomHeight - deltaY - remainingHeight;
                bottomInfo.heightFact = newBottomHeight/ (double) parentHeight;
            }
        }
        else
        { //If the knob is associated with a vertical line
            int colIdx = verticalKnobsV.indexOf(knob);
            Vector currCol = (Vector) componentColsV.elementAt(colIdx);
            //At least one element is always present,
            //if the knob has been created
            ComponentInfo firstInfo = (ComponentInfo) currCol.elementAt(0);
            int currWidth = (int)Math.round(firstInfo.widthFact * parentWidth);
            int deltaX = me.getX() - startMouseX;
            int newWidth = currWidth + deltaX;
            if (deltaX > 0)
            { //resize columns to the right
                //Chech if resizing can be done
                int remainingCols = numCols - colIdx - 1;
                if (deltaX > remainingCols * horizontalGap * MIN_WIDTH)
                    return; //No room for resizing

                int remainingWidth = deltaX;
                for (int i = colIdx + 1; i<numCols && remainingWidth > 0; i++)
                {
                    Vector rightCol = (Vector) componentColsV.elementAt(i);
                    ComponentInfo rightInfo = (ComponentInfo) rightCol.
                        elementAt(0);
                    int rightWidth = (int)
                             Math.round(rightInfo.widthFact * parentWidth);
                    int newRightWidth = rightWidth - remainingWidth;
                    if (newRightWidth < MIN_WIDTH + horizontalGap)
                    {
                        newRightWidth = MIN_WIDTH + horizontalGap;
                        remainingWidth -= rightWidth -
                            (MIN_WIDTH + horizontalGap);
                    }
                    else
                        remainingWidth = 0;
                        //Update width for all components in the same column
                    double newRightWidthFact = newRightWidth/
                                                 (double) parentWidth;
                    for (int j = 0; j < rightCol.size(); j++)
                        ( (ComponentInfo) rightCol.elementAt(j)).widthFact =
                            newRightWidthFact;
                }
                newWidth -= remainingWidth;
                //Handle the case in which not all required resizing
                //can be accomplished
                for (int i = 0; i < currCol.size(); i++)
                    ( (ComponentInfo) currCol.elementAt(i)).widthFact =
                        newWidth / (double) parentWidth;

             }
            else
            { //resize columns to the left
                //Chech if resizing can be done
                int remainingCols = colIdx + 1;
                if ( -deltaX > remainingCols * horizontalGap * MIN_WIDTH)
                    return; //No action at all
                int remainingWidth = -deltaX;
                for (int i = colIdx; i >= 0 && remainingWidth > 0; i--)
                {
                    Vector leftCol = (Vector) componentColsV.elementAt(i);
                    ComponentInfo leftInfo = (ComponentInfo) leftCol.
                        elementAt(0);
                    int leftWidth = (int)Math.round(
                                      leftInfo.widthFact * parentWidth);
                    int newRightWidth = leftWidth - remainingWidth;
                    if (newRightWidth < MIN_WIDTH + horizontalGap)
                    {
                        newRightWidth = MIN_WIDTH + horizontalGap;
                        remainingWidth -= leftWidth -
                            (MIN_WIDTH + horizontalGap);
                    }
                    else
                        remainingWidth = 0;
                        //Update width for all components in the same column
                    double newWidthFact = newRightWidth / (double) parentWidth;
                    for (int j = 0; j < leftCol.size(); j++)
                        ( (ComponentInfo) leftCol.elementAt(j)).widthFact =
                            newWidthFact;
                }
                //we need now to enlarge the column to the right.
                //Note that a right column is always present
                Vector rightCol = (Vector)componentColsV.elementAt(colIdx + 1);
                double rightWidthFact = ((ComponentInfo)
                                      rightCol.elementAt(0)).widthFact;
                int rightWidth = (int)Math.round(rightWidthFact * parentWidth);
                int newRightWidth = rightWidth -deltaX - remainingWidth;
                double newRightWidthFact = newRightWidth/ (double) parentWidth;
                for (int j = 0; j < rightCol.size(); j++)
                    ( (ComponentInfo) rightCol.elementAt(j)).widthFact =
                        newRightWidthFact;
            }
        }
        //If execution arrives so far, the new factors have been
        //computed and we need to update
        layoutContainer(knob.getParent());
    }

    //Handle MB3 button clock for column or row size reset
    public void handleKnobMb2Clicked(Knob knob, MouseEvent me)
    {
        //Check whether the knob is associated with a vertical line
        if (verticalKnobsV.indexOf(knob) >= 0)
            resetColumnWidth();
        else
        {
            int colIdx = componentColsV.indexOf(knobToColumnH.get(knob));
            resetColumnRowsHeight(colIdx);
        }
        layoutContainer(knob.getParent());
    }


    //Inner class Knob

    //A knob is associated with the bottom line of every managed component
    //(e.g. a panel). A knob is associated with every vertical separator
    //between components.
    //Knobs do not take any action in response to mouse interaction, but
    //delegate to handler methods in the outer class.
    class Knob extends Component
    {
        //Constructor
        //Here anonymous inner classes are used to define the mouse behaviour.
        //They delegate to methods of the outer class.
        Knob()
        {
            addMouseListener(new MouseAdapter()
            {
                public void mousePressed(MouseEvent me)
                {
                    handleKnobMousePressed(Knob.this, me);
                }
                public void mouseClicked(MouseEvent me)
                {
                    if(me.getButton() == MouseEvent.BUTTON2)
                        handleKnobMb2Clicked(Knob.this, me);
                }
            });
            addMouseMotionListener(new MouseMotionAdapter()
            {
                public void mouseDragged(MouseEvent me)
                {
                    handleKnobMouseMoved(Knob.this, me);
                }
            });
        }

        //Draw the knob
        public void paint(Graphics g)
        {
            Rectangle d = getBounds();
            if (d.width > d.height)
                setCursor(new Cursor(Cursor.N_RESIZE_CURSOR));
            else
                setCursor(new Cursor(Cursor.W_RESIZE_CURSOR));
            g.draw3DRect(0, 0, d.width-1, d.height-1, true);
        }
    } //End inner class knob

    //static inner class ComponentInfo keeps component-related information
    //It acts as a structure definition + constructor
    static class ComponentInfo
    {
        double widthFact;
              //Fraction of container's width occupied by this component
        double heightFact;
              //Fraction of container's height occupied by this component
        Component component; //The component (window)
        Knob knob; //The knob associated with the bottom border
        ComponentInfo(double widthFact, double heightFactor,
                      Component component, Knob knob)
        {
            this.widthFact = widthFact;
            this.heightFact = heightFact;
            this.component = component;
            this.knob = knob;
        }
    }
    //End static inner class ComponentInfo
 }
