/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package dvi;

import java.awt.Rectangle;

// immutable.

public final class DviRect
implements java.io.Serializable
{
  private static final long serialVersionUID = 1112844763203879499L;
  public static final DviRect EMPTY = new DviRect();
  private final int x;
  private final int y;
  private final int width;
  private final int height;

  public DviRect() {
    this.x = 0;
    this.y = 0;
    this.width = 0;
    this.height = 0;
  }

  public DviRect(int x, int y, int width, int height) {
    if (width < 0) {
      this.x = x + width;
      this.width = -width;
    } else {
      this.x = x;
      this.width = width;
    }
    if (height < 0) {
      this.y = y + height;
      this.height = -height;
    } else {
      this.y = y;
      this.height = height;
    }
  }

  public DviRect(DviPoint p, int width, int height) {
    this(p.x, p.y, width, height);
  }

  public DviRect(DviPoint p, DviSize s) {
    this(p.x, p.y, s.width(), s.height());
  }
  
  public DviRect(int x, int y, DviSize s) {
    this(x, y, s.width(), s.height());
  }

  public DviRect(DviRect r) {
    this(r.x(), r.y(), r.width(), r.height());
  }

  public boolean isEmpty() {
    return width == 0 || height == 0;
  }

  public boolean contains(int x, int y) {
    return this.x <= x && x < this.x + width &&
           this.y <= y && y < this.y + height;
  }

  public boolean contains(DviPoint p) {
    return this.x <= p.x && p.x < this.x + width &&
           this.y <= p.y && p.y < this.y + height;
  }

  public DviSize size() {
    return new DviSize(width, height);
  }

  public DviRect shrink(int f) {
    if (f <= 0)
      throw new IllegalArgumentException("shrink factor can't be <= 0.");

    DviRect r = getDviRect(
      flooredDivision(x, f),
      flooredDivision(y, f),
      flooredDivision(right(), f),
      flooredDivision(bottom(), f));

    return r;
  }

  public DviRect magnify(int f) {
    return new DviRect(x * f, y * f, width * f, height * f);
  }

  public static DviRect getDviRect(int sx, int sy, int ex, int ey) {
    return new DviRect(sx, sy, ex - sx + 1, ey - sy + 1);
  }

  public DviRect translate(DviPoint p) {
    return new DviRect(x + p.x, y + p.y, width, height);
  }
  public DviRect translate(int dx, int dy) {
    return new DviRect(x + dx, y + dy, width, height);
  }
  
  public DviRect moveTo(int x, int y) {
    return new DviRect(x, y, width, height);
  }

  public DviRect crop(int top, int left, int bottom, int right)
  {
    return new DviRect(
      x + left,
      y + top,
      width - left - right,
      height - top - bottom
    );
  }

  public DviRect union(DviRect r) {
    if (r == null) r = EMPTY;
    if (isEmpty()) return r;
    if (r.isEmpty()) return this;
    int sx = Math.min(x, r.x);
    int sy = Math.min(y, r.y);
    int ex = Math.max(right(), r.right());
    int ey = Math.max(bottom(), r.bottom());
    return getDviRect(sx, sy, ex, ey);
  }
  
  public static DviRect union(DviRect [] rs) {
    DviRect ret = EMPTY;
    if (rs != null) {
      for (DviRect r : rs) {
        ret = ret.union(r);
      }
    }
    return ret;
  }

  public DviRect intersect(DviRect r) {
    int sx = Math.max(x, r.x);
    int sy = Math.max(y, r.y);
    int ex = Math.min(right(), r.right());
    int ey = Math.min(bottom(), r.bottom());
    return (sx <= ex && sy <= ey) ? getDviRect(sx, sy, ex, ey)
      : new DviRect();
  }

  public boolean intersects(DviRect r) {
    int sx = Math.max(x, r.x);
    int sy = Math.max(y, r.y);
    int ex = Math.min(right(), r.right());
    int ey = Math.min(bottom(), r.bottom());
    return (sx < ex && sy < ey);
  }

  public DviRect addPadding(int by) {
    if (isEmpty()) return this;
    return getDviRect(x - by, y - by, right() + by, bottom() + by);
  }

  private static int flooredDivision(final int a, final int b) {
    return (a < 0) ? (a - b + 1) / b : a / b;
  }

  public int x() { return x; }
  public int y() { return y; }
  public int width() { return width; }
  public int height() { return height; }
  public int left() { return x; }
  public int top() { return y; }
  public int right() { return x + width - 1; }
  public int bottom() { return y + height - 1; }

  public DviPoint topLeft() {
    return new DviPoint(x, y);
  }
  public DviPoint topRight() {
    return new DviPoint(x+width-1, y);
  }
  public DviPoint bottomLeft() {
    return new DviPoint(x, y+height-1);
  }
  public DviPoint bottomRight() {
    return new DviPoint(x+width-1, y+height-1);
  }
  
  public DviRect resize(int width, int height)
  {
    return new DviRect(this.x, this.y, width, height);
  }

  public int hashCode() {
    return (x + 33*(y + 33*(width + 33*height)));
  }

  public boolean equals(Object obj) {
    if (obj instanceof DviRect) {
      DviRect a = (DviRect) obj;
      return isEmpty() ? a.isEmpty() : (
        x == a.x && y == a.y &&
        width == a.width && height == a.height
      );
    } return false;
  }

  public String toString() {
    return getClass().getName()
          +"[x=" + x
          +",y=" + y
          +",width=" + width
          +",height=" + height
          +"]";
  }
  
  public Rectangle toRectangle()
  {
    return new Rectangle(x, y, width - 1, height - 1);
  }
  
  public static DviRect fromRectangle(Rectangle rect)
  {
    if (rect == null) return null;
    return new DviRect(rect.x, rect.y, rect.width + 1, rect.height + 1);
  }

  public DviRect magnify(double hScale, double vScale) {
    return new DviRect((int) (x * hScale + 0.5), (int) (y * vScale + 0.5), size().magnify(hScale, vScale));
  }
  
  public DviRect magnify(double scale) {
    return magnify(scale, scale);
  }

  public DviRect moveTo(DviPoint point) {
    if (point == null) throw new IllegalArgumentException("point can't be null");
    return moveTo(point.x, point.y);
  }
}
