// Revision     1.6
// Commit info     2005-05-19 15:29:27+0000
/**
 * $ $ License.
 *
 * Copyright $ L2FProd.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.l2fprod.common.swing;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * PercentLayout. <BR>Constraint based layout which allow the space to be
 * splitted using percentages. The following are allowed when adding components
 * to container:
 * <ul>
 * <li>container.add(component); <br>in this case, the component will be
 * sized to its preferred size
 * <li>container.add(component, "100"); <br>in this case, the component will
 * have a width (or height) of 100
 * <li>container.add(component, "25%"); <br>in this case, the component will
 * have a width (or height) of 25 % of the container width (or height) <br>
 * <li>container.add(component, "*"); <br>in this case, the component will
 * take the remaining space. if several components use the "*" constraint the
 * space will be divided among the components.
 * </ul>
 * 
 * @javabean.class
 *          name="PercentLayout"
 *          shortDescription="A layout supports constraints expressed in percent."
 */
public class PercentLayout implements LayoutManager2 {

  /**
   * Useful constant to layout the components horizontally (from top to
   * bottom).
   */
  public final static int HORIZONTAL = 0;

  /**
   * Useful constant to layout the components vertically (from left to right).
   */
  public final static int VERTICAL = 1;

  static class Constraint {
    protected Object value;
    private Constraint(Object value) {
      this.value = value;
    }
  }

  static class NumberConstraint extends Constraint {
    public NumberConstraint(int d) {
      this(Integer.valueOf(d));
    }
    public NumberConstraint(Integer d) {
      super(d);
    }
    public int intValue() {
      return ((Integer)value).intValue();
    }
  }

  static class PercentConstraint extends Constraint {
    public PercentConstraint(float d) {
      super(Float.valueOf(d));
    }
    public float floatValue() {
      return ((Float)value).floatValue();
    }
  }

  private final static Constraint REMAINING_SPACE = new Constraint("*");

  private final static Constraint PREFERRED_SIZE = new Constraint("");

  private int orientation;
  private int gap;

  private Hashtable m_ComponentToConstraint;

  /**
   * Creates a new HORIZONTAL PercentLayout with a gap of 0.
   */
  public PercentLayout() {
    this(HORIZONTAL, 0);
  }

  public PercentLayout(int orientation, int gap) {
    setOrientation(orientation);
    this.gap = gap;

    m_ComponentToConstraint = new Hashtable();
  }

  public void setGap(int gap) {
    this.gap = gap;   
  }
  
  /**
   * @javabean.property
   *          bound="true"
   *          preferred="true"
   */
  public int getGap() {
    return gap;
  }
  
  public void setOrientation(int orientation) {
    if (orientation != HORIZONTAL && orientation != VERTICAL) {
      throw new IllegalArgumentException("Orientation must be one of HORIZONTAL or VERTICAL");
    }
    this.orientation = orientation;
  }

  /**
   * @javabean.property
   *          bound="true"
   *          preferred="true"
   */
  public int getOrientation() {
    return orientation;
  }
  
  public Constraint getConstraint(Component component) {
    return (Constraint)m_ComponentToConstraint.get(component);
  }
  
  public void setConstraint(Component component, Object constraints) {
    if (constraints instanceof Constraint) {
      m_ComponentToConstraint.put(component, constraints);
    } else if (constraints instanceof Number) {
      setConstraint(
        component,
        new NumberConstraint(((Number)constraints).intValue()));
    } else if ("*".equals(constraints)) {
      setConstraint(component, REMAINING_SPACE);
    } else if ("".equals(constraints)) {
      setConstraint(component, PREFERRED_SIZE);
    } else if (constraints instanceof String) {
      String s = (String)constraints;
      if (s.endsWith("%")) {
        float value = Float.valueOf(s.substring(0, s.length() - 1))
          .floatValue() / 100;
        if (value > 1 || value < 0)
          throw new IllegalArgumentException("percent value must be >= 0 and <= 100");
        setConstraint(component, new PercentConstraint(value));
      } else {
        setConstraint(component, new NumberConstraint(Integer.valueOf(s)));
      }
    } else if (constraints == null) {
      // null constraint means preferred size
      setConstraint(component, PREFERRED_SIZE);
    } else {
      throw new IllegalArgumentException("Invalid Constraint");
    }    
  }
  
  public void addLayoutComponent(Component component, Object constraints) {
    setConstraint(component, constraints);    
  }

  /**
   * Returns the alignment along the x axis. This specifies how the component
   * would like to be aligned relative to other components. The value should be
   * a number between 0 and 1 where 0 represents alignment along the origin, 1
   * is aligned the furthest away from the origin, 0.5 is centered, etc.
   */
  public float getLayoutAlignmentX(Container target) {
    return 1.0f / 2.0f;
  }

  /**
   * Returns the alignment along the y axis. This specifies how the component
   * would like to be aligned relative to other components. The value should be
   * a number between 0 and 1 where 0 represents alignment along the origin, 1
   * is aligned the furthest away from the origin, 0.5 is centered, etc.
   */
  public float getLayoutAlignmentY(Container target) {
    return 1.0f / 2.0f;
  }

  /**
   * Invalidates the layout, indicating that if the layout manager has cached
   * information it should be discarded.
   */
  public void invalidateLayout(Container target) {
  }

  /**
   * Adds the specified component with the specified name to the layout.
   * 
   * @param name the component name
   * @param comp the component to be added
   */
  public void addLayoutComponent(String name, Component comp) {
  }

  /**
   * Removes the specified component from the layout.
   * 
   * @param comp the component ot be removed
   */
  public void removeLayoutComponent(Component comp) {
    m_ComponentToConstraint.remove(comp);
  }

  /**
   * Calculates the minimum size dimensions for the specified panel given the
   * components in the specified parent container.
   * 
   * @param parent the component to be laid out
   * @see #preferredLayoutSize
   */
  public Dimension minimumLayoutSize(Container parent) {
    return preferredLayoutSize(parent);
  }

  /**
   * Returns the maximum size of this component.
   * 
   * @see java.awt.Component#getMinimumSize()
   * @see java.awt.Component#getPreferredSize()
   * @see java.awt.LayoutManager
   */
  public Dimension maximumLayoutSize(Container parent) {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }

  public Dimension preferredLayoutSize(Container parent) {
    Component[] components = parent.getComponents();
    Insets insets = parent.getInsets();
    int width = 0;
    int height = 0;
    Dimension componentPreferredSize;
    boolean firstVisibleComponent = true;
    for (int i = 0, c = components.length; i < c; i++) {
      if (components[i].isVisible()) {
        componentPreferredSize = components[i].getPreferredSize();
        if (orientation == HORIZONTAL) {
          height = Math.max(height, componentPreferredSize.height);
          width += componentPreferredSize.width;
          if (firstVisibleComponent) {
            firstVisibleComponent = false;
          } else {
            width += gap;
          }
        } else {
          height += componentPreferredSize.height;
          width = Math.max(width, componentPreferredSize.width);
          if (firstVisibleComponent) {
            firstVisibleComponent = false;
          } else {
            height += gap;
          }
        }
      }
    }
    return new Dimension(
      width + insets.right + insets.left,
      height + insets.top + insets.bottom);
  }

  public void layoutContainer(Container parent) {
    Insets insets = parent.getInsets();
    Dimension d = parent.getSize();

    // calculate the available sizes
    d.width = d.width - insets.left - insets.right;
    d.height = d.height - insets.top - insets.bottom;

    // pre-calculate the size of each components
    Component[] components = parent.getComponents();
    int[] sizes = new int[components.length];

    // calculate the available size
    int totalSize =
      (HORIZONTAL == orientation ? d.width : d.height)
        - (components.length - 1) * gap;
    int availableSize = totalSize;

    // PENDING(fred): the following code iterates 4 times on the component
    // array, need to find something more efficient!
       
    // give priority to components who want to use their preferred size or who
    // have a predefined size
    for (int i = 0, c = components.length; i < c; i++) {
      if (components[i].isVisible()) {
        Constraint constraint =
          (Constraint)m_ComponentToConstraint.get(components[i]);
        if (constraint == null || constraint == PREFERRED_SIZE) {
          sizes[i] =
            (HORIZONTAL == orientation
              ? components[i].getPreferredSize().width
              : components[i].getPreferredSize().height);
          availableSize -= sizes[i];
        } else if (constraint instanceof NumberConstraint) {
          sizes[i] = ((NumberConstraint)constraint).intValue();
          availableSize -= sizes[i];
        }
      }
    }
    
    // then work with the components who want a percentage of the remaining
    // space
    int remainingSize = availableSize;    
    for (int i = 0, c = components.length; i < c; i++) {
      if (components[i].isVisible()) {
        Constraint constraint =
          (Constraint)m_ComponentToConstraint.get(components[i]);
        if (constraint instanceof PercentConstraint) {
          sizes[i] = (int)(remainingSize * ((PercentConstraint)constraint)
            .floatValue());
          availableSize -= sizes[i];
        }
      }
    }
    
    // finally share the remaining space between the other components    
    ArrayList remaining = new ArrayList();
    for (int i = 0, c = components.length; i < c; i++) {
      if (components[i].isVisible()) {
        Constraint constraint =
          (Constraint)m_ComponentToConstraint.get(components[i]);
        if (constraint == REMAINING_SPACE) {
          remaining.add(Integer.valueOf(i));
          sizes[i] = 0;
        }
      }
    }

    if (remaining.size() > 0) {
      int rest = availableSize / remaining.size();
      for (Iterator iter = remaining.iterator(); iter.hasNext();) {
        sizes[((Integer)iter.next()).intValue()] = rest;
      }
    }

    // all calculations are done, apply the sizes
    int currentOffset = (HORIZONTAL == orientation ? insets.left : insets.top);

    for (int i = 0, c = components.length; i < c; i++) {
      if (components[i].isVisible()) {
        if (HORIZONTAL == orientation) {
          components[i].setBounds(
            currentOffset,
            insets.top,
            sizes[i],
            d.height);
        } else {
          components[i].setBounds(
            insets.left,
            currentOffset,
            d.width,
            sizes[i]);
        }
        currentOffset += gap + sizes[i];
      }
    }
  }

}