/**
 * ===========================================
 * LibLayout : a free Java layouting library
 * ===========================================
 *
 * Project Info:  http://reporting.pentaho.org/liblayout/
 *
 * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------
 * $Id: StrictBounds.java 3524 2007-10-16 11:26:31Z tmorgner $
 * ------------
 * (C) Copyright 2006-2007, by Pentaho Corporation.
 */
package org.jfree.layouting.util.geom;

import java.io.Serializable;

/**
 * The StrictBounds class is a replacement for the Rectangle2D classes.
 * This class uses integer mathematics instead of floating point values
 * to achive a higher degree of stability.
 *
 * @author Thomas Morgner
 */
public class StrictBounds implements Serializable, Cloneable
{
  /** The x-coordinate of the upper left corner. */
  private long x;
  /** The y-coordinate of the upper left corner. */
  private long y;
  /** The width of this rectangle. */
  private long width;
  /** The height of this rectangle. */
  private long height;
  /** A flag indicating whether attempts to change this rectangle should trigger Exceptions. */
  private boolean locked;

  /**
   * DefaultConstructor.
   */
  public StrictBounds ()
  {
  }

  /**
   * Creates a StrictBounds object with the given coordinates, width
   * and height.
   *
   * @param x the x-coordinate
   * @param y the y-coordinate
   * @param width the width of the rectangle
   * @param height the height of the rectangle
   */
  public StrictBounds (final long x, final long y,
                       final long width, final long height)
  {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
  }

  /**
   * Checks, whether this bounds object is locked.
   *
   * @return true, if the bounds are locked and therefore immutable, false otherwise.
   */
  public boolean isLocked ()
  {
    return locked;
  }


  /**
   * Returns a copy of this bounds object which cannot be modified anymore.
   *
   * @return a locked copy.
   */
  public StrictBounds getLockedInstance ()
  {
    if (locked)
    {
      return this;
    }

    final StrictBounds retval = (StrictBounds) clone();
    retval.locked = true;
    return retval;
  }

  /**
   * Returns a copy of this bounds object which can be modified later.
   *
   * @return an unlocked copy.
   */
  public StrictBounds getUnlockedInstance ()
  {
    final StrictBounds retval = (StrictBounds) clone();
    retval.locked = false;
    return retval;
  }

  /**
   * Sets the location and size of this <code>StrictBounds</code> to the specified double
   * values.
   *
   * @param x the coordinates to which to set the location of the upper left corner of
   *          this <code>StrictBounds</code>
   * @param y the coordinates to which to set the location of the upper left corner of
   *          this <code>StrictBounds</code>
   * @param w the value to use to set the width of this <code>StrictBounds</code>
   * @param h the value to use to set the height of this <code>StrictBounds</code>
   */
  public void setRect (final long x, final long y,
                       final long w, final long h)
  {
    if (locked)
    {
      throw new IllegalStateException("This object is locked");
    }
    this.x = x;
    this.y = y;
    this.width = w;
    this.height = h;
  }

  /**
   * Returns the height of the framing rectangle in micro points.
   *
   * @return the height of the framing rectangle.
   */
  public long getHeight ()
  {
    return height;
  }

  /**
   * Returns the width of the framing rectangle in micro points.
   *
   * @return the width of the framing rectangle.
   */
  public long getWidth ()
  {
    return width;
  }

  /**
   * Returns the X coordinate of the upper left corner of the framing
   * rectangle in micro points.
   *
   * @return the x coordinate of the upper left corner of the framing rectangle.
   */
  public long getX ()
  {
    return x;
  }

  /**
   * Returns the Y coordinate of the upper left corner of the framing
   * rectangle in micro points.
   *
   * @return the y coordinate of the upper left corner of the framing rectangle.
   */
  public long getY ()
  {
    return y;
  }

  /**
   * Determines whether the <code>RectangularShape</code> is empty. When the
   * <code>RectangularShape</code> is empty, it encloses no area.
   *
   * @return <code>true</code> if the <code>RectangularShape</code> is empty;
   *         <code>false</code> otherwise.
   */
  public boolean isEmpty ()
  {
    return width == 0 || height == 0;
  }

  /**
   * Returns a copy of this bounds object. This method will never throw a
   * 'CloneNotSupportedException'.
   *
   * @return the cloned instance.
   */
  public Object clone ()
  {
    try
    {
      return super.clone();
    }
    catch (CloneNotSupportedException e)
    {
      throw new InternalError("Clone must always be supported.");
    }
  }

  /**
   * Checks, whether this rectangle contains the given point.
   *
   * @param x the x-coordinate of the point.
   * @param y the y-coordinate of the point.
   * @return true, if the point is inside or directly on the border of this
   * rectangle, false otherwise.
   */
  public boolean contains (final long x, final long y)
  {
    if (x < this.x)
    {
      return false;
    }
    if (y < this.y)
    {
      return false;
    }
    if (x > (this.x + this.width))
    {
      return false;
    }
    return y <= (this.y + this.height);
  }


  /**
   * Checks, whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2
   * has a height or width of zero!).
   *
   * @param rect1 the first rectangle.
   * @param rect2 the second rectangle.
   * @return true, if the rectangles intersect each other, false otherwise.
   */
  public static boolean intersects (final StrictBounds rect1,
                                    final StrictBounds rect2)
  {

    final double x0 = rect1.getX();
    final double y0 = rect1.getY();

    final double x = rect2.getX();
    final double width = rect2.getWidth();
    final double y = rect2.getY();
    final double height = rect2.getHeight();
    return (x + width >= x0 &&
            y + height >= y0 &&
            x <= x0 + rect1.getWidth() &&
            y <= y0 + rect1.getHeight());
  }

  /**
   * Adds the given bounds to this bounds instance. The resulting rectangle
   * will fully contain both rectangles.
   *
   * @param bounds the rectangle that should be added.
   */
  public void add (final StrictBounds bounds)
  {
    if (locked)
    {
      throw new IllegalStateException("This object is locked");
    }

    final long x1 = Math.min(getX(), bounds.getX());
    final long y1 = Math.min(getY(), bounds.getY());
    final long x2 = Math.max(getX() + getWidth(), bounds.getX() + bounds.getWidth());
    final long y2 = Math.max(getY() + getHeight(), bounds.getY() + bounds.getHeight());
    setRect(x1, y1, Math.max (0, x2 - x1), Math.max (0, y2 - y1));
  }

  /**
   * Intersects this rectangle with the given bounds. The resulting rectangle
   * will cover the space, that is occupied by both rectangles at the same time.
   *
   * @param bounds the other rectangle.
   * @return the resulting intersection.
   */
  public StrictBounds createIntersection (final StrictBounds bounds)
  {
    final long x1 = Math.max(getX(), bounds.getX());
    final long y1 = Math.max(getY(), bounds.getY());
    final long x2 = Math.min(getX() + getWidth(), bounds.getX() + bounds.getWidth());
    final long y2 = Math.min(getY() + getHeight(), bounds.getY() + bounds.getHeight());

    return new StrictBounds(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
  }


  /**
   * Checks, whether the given rectangle1 fully contains rectangle 2 (even if rectangle 2
   * has a height or width of zero!).
   *
   * @param rect1 the first rectangle.
   * @param rect2 the second rectangle.
   * @return A boolean.
   */
  public static boolean contains (final StrictBounds rect1,
                                  final StrictBounds rect2)
  {

    final long x0 = rect1.getX();
    final long y0 = rect1.getY();
    final long x = rect2.getX();
    final long y = rect2.getY();
    final long w = rect2.getWidth();
    final long h = rect2.getHeight();

    return ((x >= x0) && (y >= y0) &&
            ((x + w) <= (x0 + rect1.getWidth())) &&
            ((y + h) <= (y0 + rect1.getHeight())));

  }

  /**
   * Checks, whether the given object is a StrictBounds instance convering the
   * same area as these bounds.
   *
   * @param o the other object.
   * @return true, if the other object is equal to this object, false otherwise.
   */
  public boolean equals (final Object o)
  {
    if (this == o)
    {
      return true;
    }
    if (!(o instanceof StrictBounds))
    {
      return false;
    }

    final StrictBounds strictBounds = (StrictBounds) o;

    if (height != strictBounds.height)
    {
      return false;
    }
    if (width != strictBounds.width)
    {
      return false;
    }
    if (x != strictBounds.x)
    {
      return false;
    }
    return y == strictBounds.y;

  }

  /**
   * Computes the hashcode for this rectangle.
   *
   * @return the computed hashcode.
   */
  public int hashCode ()
  {
    int result = (int) (x ^ (x >>> 32));
    result = 29 * result + (int) (y ^ (y >>> 32));
    result = 29 * result + (int) (width ^ (width >>> 32));
    result = 29 * result + (int) (height ^ (height >>> 32));
    return result;
  }


  /**
   * Returns a string representation of these bounds.
   *
   * @return the string representing this object.
   */
  public String toString ()
  {
    return new StringBuffer().append("org.jfree.report.util.geom.StrictBounds{").append("x=")
            .append(x)
            .append(", y=")
            .append(y)
            .append(", width=")
            .append(width)
            .append(", height=")
            .append(height)
            .append('}')
            .toString();
  }

  /**
   * Creates a union from this and the given rectangle. This is similiar to
   * calling 'add'. Calling this method does not modify the original and there
   * are no guarantees, that the resulting rectangle has a positive width or
   * height.
   *
   * @param bg the other rectangle.
   * @return the resulting union rectangle.
   */
  public StrictBounds createUnion (final StrictBounds bg)
  {
    final long x = Math.min(getX(), bg.getX());
    final long y = Math.min(getY(), bg.getY());
    final long w = Math.max(getX() + getWidth(), bg.getX() + bg.getWidth()) - x;
    final long h = Math.max(getY() + getHeight(), bg.getY() + bg.getHeight()) - y;
    return new StrictBounds(x, y, w, h);
  }
}
