// PackLayout.java
// By Ned Etcode
// Copyright 1995, 1996 Netscape Communications Corp. All rights reserved.

package netscape.application;

import netscape.util.*;

/** Object subclass implementing a LayoutManager similar to the TK Packer.
  * See the PackConstraints class for more information about the available
  * settings can be used.<p>
  * <i>Note: Because Views do not call <b>layoutView()</b> whenever subviews are
  * added or removed, an application using a PackLayout must explicitly call
  * the PackLayout View's <b>layoutView()</b> with a zero delta width and delta
  * height.  Calling <b>layoutView()</b> in this manner will cause the
  * LayoutManager to properly position and size the View's subviews.</i>
  *
  * @see PackConstraints
  */
public class PackLayout extends Object implements LayoutManager, Codable {
    Hashtable   viewConstraints;
    Vector      viewVector;

    final static String VIEWCONSTRAINTS_KEY = "viewConstraints",
                        VIEWVECTOR_KEY = "viewVector";

    private static PackConstraints defaultConstraints;

    private PackConstraints defaultConstraints() {
        if (defaultConstraints == null) {
            defaultConstraints = new PackConstraints();
        }

        return defaultConstraints;
    }

    /** Constructs a PackLayout. */
    public PackLayout()     {
        super();
        viewConstraints = new Hashtable();
        viewVector = new Vector();
    }

    /** Returns the PackConstraints object associated with <b>aView</b>.
      */
    public PackConstraints constraintsFor(View aView)   {
        return (PackConstraints)viewConstraints.get(aView);
    }

    /** Adds <b>aView</b> to the PackLayout with default PackConstraints.
      * @see LayoutManager
      * @see PackConstraints
      */
    public void addSubview(View aView)      {
        viewVector.addElementIfAbsent(aView);
    }

    /** Associates the PackConstraints <b>constraints</b> with <b>aView</b>.
      * You usually call this method to associate non-default constraints with
      * a particular View.<p>
      * <i><b>Note:</b> This method adds a clone of <b>constraints</b> to
      * its internal constraint container. You can therefore reconfigure and
      * pass in the PackConstraints same instance on each call without no side
      * effects.</i>
      */
    public void setConstraints(View aView, PackConstraints constraints) {
        viewVector.addElementIfAbsent(aView);
        viewConstraints.put(aView, constraints.clone());
    }

    /** Removes <b>aView</b> from the PackLayout.
      * @see LayoutManager
      */
    public void removeSubview(View aView)   {
        viewConstraints.remove(aView);
        viewVector.removeElement(aView);
    }

    /** Positions and sizes its View's subviews according to the constraints
      * associated with each subview.
      * @see LayoutManager
      */
    public void layoutView(View aView, int deltaWidth, int deltaHeight) {
        View curView;
        int cntx,max;
        int cavityX = 0, cavityY = 0;
        int cavityWidth = aView.bounds.width;
        int cavityHeight = aView.bounds.height;
        int frameX, frameY, frameWidth, frameHeight;
        int width, height, x, y;
        int side, padx, pady, ipadx, ipady, anchor;
        boolean expand, fillx, filly;
        Vector sViews;
        PackConstraints constraints;

        sViews = aView.subviews();
        max=sViews.count();
        for(cntx=0 ; cntx < max ; cntx++)       {
            curView     = (View)sViews.elementAt(cntx);
            constraints = constraintsFor(curView);
            if(constraints == null)
                constraints = defaultConstraints();
            side        = constraints.side();
            padx        = constraints.padX()*2;
            pady        = constraints.padY()*2;
            ipadx       = constraints.internalPadX();
            ipady       = constraints.internalPadY();
            expand      = constraints.expand();
            fillx       = constraints.fillX();
            filly       = constraints.fillY();
            anchor      = constraints.anchor();

            if ((side == PackConstraints.SIDE_TOP) || (side == PackConstraints.SIDE_BOTTOM)) {
                frameWidth = cavityWidth;
                frameHeight = preferredLayoutSize(curView).height + pady + ipady;
                if (expand)
                    frameHeight += YExpansion(curView, cavityHeight);
                cavityHeight -= frameHeight;
                if (cavityHeight < 0) {
                    frameHeight += cavityHeight;
                    cavityHeight = 0;
                }
                frameX = cavityX;
                if (side == PackConstraints.SIDE_TOP) {
                    frameY = cavityY;
                    cavityY += frameHeight;
                } else {
                    frameY = cavityY + cavityHeight;
                }
            } else {
                frameHeight = cavityHeight;
                frameWidth = preferredLayoutSize(curView).width + padx + ipadx;
                if (expand)
                    frameWidth += XExpansion(curView, cavityWidth);
                cavityWidth -= frameWidth;
                if (cavityWidth < 0) {
                    frameWidth += cavityWidth;
                    cavityWidth = 0;
                }
                frameY = cavityY;
                if (side == PackConstraints.SIDE_LEFT) {
                    frameX = cavityX;
                    cavityX += frameWidth;
                } else {
                    frameX = cavityX + cavityWidth;
                }
            }

            width = preferredLayoutSize(curView).width + ipadx;
            if ((fillx) || (width > (frameWidth - padx)))
                width = frameWidth - padx;

            height = preferredLayoutSize(curView).height + ipady;
            if ((filly) || (height > (frameHeight - pady)))
                height = frameHeight - pady;

            padx /= 2;
            pady /= 2;

            switch (anchor) {
            case PackConstraints.ANCHOR_NORTH:
                x = frameX + (frameWidth - width)/2;
                y = frameY + pady;
                break;
            case PackConstraints.ANCHOR_NORTHEAST:
                x = frameX + frameWidth - width - padx;
                y = frameY + pady;
                break;
            case PackConstraints.ANCHOR_EAST:
                x = frameX + frameWidth - width - padx;
                y = frameY + (frameHeight - height)/2;
                break;
            case PackConstraints.ANCHOR_SOUTHEAST:
                x = frameX + frameWidth - width - padx;
                y = frameY + frameHeight - height - pady;
                break;
            case PackConstraints.ANCHOR_SOUTH:
                x = frameX + (frameWidth - width)/2;
                y = frameY + frameHeight - height - pady;
                break;
            case PackConstraints.ANCHOR_SOUTHWEST:
                x = frameX + padx;
                y = frameY + frameHeight - height - pady;
                break;
            case PackConstraints.ANCHOR_WEST:
                x = frameX + padx;
                y = frameY + (frameHeight - height)/2;
                break;
            case PackConstraints.ANCHOR_NORTHWEST:
                x = frameX + padx;
                y = frameY + pady;
                break;
            case PackConstraints.ANCHOR_CENTER:
            default:
                x = frameX + (frameWidth - width)/2;
                y = frameY + (frameHeight - height)/2;
                break;
            }

            curView.setBounds(x, y, width, height);
        }
        return;
    }

    private int XExpansion(View current, int cavityWidth) {
        PackConstraints constraints;
        int numExpand, minExpand, curExpand, childWidth;
        int padx, ipadx, side;
        boolean expand;
        int x,max;
        View curView;

        minExpand = cavityWidth;
        numExpand = 0;

        max=viewVector.count();
        for(x=viewVector.indexOf(current) ; x < max ; x++)        {
            curView         = (View)viewVector.elementAt(x);
            constraints     = constraintsFor(curView);
            if(constraints == null)
                constraints = defaultConstraints();
            padx            = constraints.padX()*2;
            ipadx           = constraints.internalPadX();
            expand          = constraints.expand();
            side            = constraints.side();

            childWidth      = preferredLayoutSize(curView).width + padx + ipadx;

            if ((side == PackConstraints.SIDE_TOP) || (side == PackConstraints.SIDE_BOTTOM)) {
                curExpand = (cavityWidth - childWidth)/numExpand;
                if (curExpand < minExpand)
                    minExpand = curExpand;
            } else {
                cavityWidth -= childWidth;
                if (expand)
                    numExpand++;
            }
        }

        curExpand = cavityWidth/numExpand;
        if (curExpand < minExpand)
            minExpand = curExpand;

        if (minExpand < 0)
            return 0;
        else
            return minExpand;

    }

    private int YExpansion(View current, int cavityHeight) {
        PackConstraints constraints;
        int numExpand, minExpand, curExpand, childHeight, pady, ipady;
        boolean expand;
        int side;
        int x,max;
        View curView;

        minExpand = cavityHeight;
        numExpand = 0;

        max=viewVector.count();
        for(x=viewVector.indexOf(current) ; x < max ; x++)        {
            curView         = (View)viewVector.elementAt(x);
            constraints  = constraintsFor(curView);
            if(constraints == null)
                constraints = defaultConstraints();
            pady            = constraints.padY()*2;
            ipady           = constraints.internalPadY();
            expand          = constraints.expand();
            side            = constraints.side();

            childHeight     = preferredLayoutSize(curView).height + pady + ipady;

            if ((side == PackConstraints.SIDE_LEFT) || (side == PackConstraints.SIDE_RIGHT)) {
                curExpand = (cavityHeight - childHeight)/numExpand;
                if (curExpand < minExpand)
                    minExpand = curExpand;
            } else {
                cavityHeight -= childHeight;
                if (expand)
                    numExpand++;
            }
        }

        curExpand = cavityHeight/numExpand;
        if (curExpand < minExpand)
            minExpand = curExpand;

        if (minExpand < 0)
            return 0;
        else
            return minExpand;

    }

    private Rect containedRect(View target) {
        int  x, max;
        Rect maxRect = new Rect(0,0,0,0);
        Vector sViews;
        Rect tmpBounds;

        sViews = target.subviews();
        if(sViews == null || sViews.count() < 1)    {
            return new Rect(0,0,target.minSize().width, target.minSize().height);
        }

        target.layoutView(0,0);

        max = sViews.count();
        for(x=0 ; x < max ; x++)  {
            tmpBounds = containedRect((View)sViews.elementAt(x));
            if(tmpBounds.x+tmpBounds.width > maxRect.x+maxRect.width)   {
                maxRect.x = tmpBounds.x;
                maxRect.width = tmpBounds.width;
            }
            if(tmpBounds.y+tmpBounds.height > maxRect.y+maxRect.height) {
                maxRect.y = tmpBounds.y;
                maxRect.height = tmpBounds.height;
            }
        }
        return maxRect;
    }

    private Size preferredLayoutSize(View target) {
        Rect aRect = containedRect(target);
        return new Size(aRect.x+aRect.width, aRect.y+aRect.height);
    }

/// Codable Interface
    /** Describes the PackLayout class' coding information.
      * @see Codable#describeClassInfo
      */
    public void describeClassInfo(ClassInfo info)   {
        info.addClass("netscape.application.PackLayout", 1);
        info.addField(VIEWCONSTRAINTS_KEY, OBJECT_TYPE);
        info.addField(VIEWVECTOR_KEY, OBJECT_TYPE);
    }

    /** Encodes the PackLayout.
      * @see Codable#encode
      */
    public void decode(Decoder decoder) throws CodingException {
        viewConstraints = (Hashtable)decoder.decodeObject(VIEWCONSTRAINTS_KEY);
        viewVector = (Vector)decoder.decodeObject(VIEWVECTOR_KEY);
    }

    /** Decodes the PackLayout.
      * @see Codable#decode
      */
    public void encode(Encoder encoder) throws CodingException {
        encoder.encodeObject(VIEWCONSTRAINTS_KEY, viewConstraints);
        encoder.encodeObject(VIEWVECTOR_KEY, viewVector);
    }

    /** Finishes the PackLayout's decoding.
      * @see Codable#finishDecoding
      */
    public void finishDecoding() throws CodingException {
    }
}
