/**
 * $ $ 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 monitor;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.*;

/**
 * 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>
 */
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;

   private static class Constraint {

      protected Object value;

      public Constraint(Object value) {
         this.value = value;
      }
   }

   private static class NumberConstraint extends Constraint {

      public NumberConstraint(double d) {
         this(new Double(d));
      }

      public NumberConstraint(Double d) {
         super(d);
      }

      public int intValue() {
         return ((Double) value).intValue();
      }
   }

   private static class PercentConstraint extends Constraint {

      public PercentConstraint(double d) {
         super(new Double(d));
      }

      public double doubleValue() {
         return ((Double) value).doubleValue();
      }
   }
   private final static Constraint REMAINING_SPACE = new Constraint("*");
   private final static Constraint PREFERRED_SIZE = new Constraint("");
   private int orientation;
   private int gap;
   private HashMap<Component,Object> m_ComponentToConstraint;

   public PercentLayout() {
      this(HORIZONTAL, 0);
   }

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

      m_ComponentToConstraint = new HashMap<Component,Object>();
   }

   public void setOrientation(int orientation) {
      if (orientation != HORIZONTAL && orientation != VERTICAL) {
         throw new IllegalArgumentException("Orientation must be one of HORIZONTAL or VERTICAL");
      }
      this.orientation = orientation;
   }

   public int getOrientation() {
      return orientation;
   }

   public void addLayoutComponent(Component component, Object constraints) {
      if (constraints instanceof Constraint) {
         m_ComponentToConstraint.put(component, constraints);
      } else if (constraints instanceof Number) {
         addLayoutComponent(
                 component,
                 new NumberConstraint(((Number) constraints).doubleValue()));
      } else if ("*".equals(constraints)) {
         addLayoutComponent(component, REMAINING_SPACE);
      } else if ("".equals(constraints)) {
         addLayoutComponent(component, PREFERRED_SIZE);
      } else if (constraints instanceof String) {
         String s = (String) constraints;
         if (s.endsWith("%")) {
            double value =
                    Double.valueOf(s.substring(0, s.length() - 1)).doubleValue() / 100;
            if (value > 1 || value < 0) {
               throw new IllegalArgumentException("percent value must be >= 0 and <= 100");
            }
            addLayoutComponent(component, new PercentConstraint(value));
         } else {
            addLayoutComponent(component, new NumberConstraint(Double.valueOf(s)));
         }
      } else if (constraints == null) {
         // null constraint means preferred size
         addLayoutComponent(component, PREFERRED_SIZE);
      } else {
         throw new IllegalArgumentException("Invalid Constraint");
      }
   }

   /**
    * 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 = 10;
      int height = 10;
      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;

      ArrayList<Integer> remaining = new ArrayList<Integer>();

      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);
            } else if (constraint instanceof NumberConstraint) {
               sizes[i] = ((NumberConstraint) constraint).intValue();
            } else if (constraint instanceof PercentConstraint) {
               sizes[i] =
                       (int) (totalSize * ((PercentConstraint) constraint).doubleValue());
            } else if (constraint == REMAINING_SPACE) {
               remaining.add(i);
               sizes[i] = 0;
            }
            availableSize -= sizes[i];
         }
      }

      if (remaining.size() > 0) {
         int rest = availableSize / remaining.size();
         for(Integer i : remaining) {
            sizes[i] = 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];
         }
      }
   }
}
