/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.android.ide.common.api;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.google.common.annotations.Beta;


/**
 * Mutable rectangle bounds.
 * <p>
 * To be valid, w &ge; 1 and h &ge; 1.
 * By definition:
 * - right side = x + w - 1.
 * - bottom side = y + h - 1.
 * <p>
 * <b>NOTE: This is not a public or final API; if you rely on this be prepared
 * to adjust your code for the next tools release.</b>
 * </p>
 */
@Beta
public class Rect {
    public int x, y, w, h;

    /** Initialize an invalid rectangle. */
    public Rect() {
    }

    /** Initialize rectangle to the given values. They can be invalid. */
    public Rect(int x, int y, int w, int h) {
        set(x, y, w, h);
    }

    /** Initialize rectangle to the given values. They can be invalid. */
    public Rect(@NonNull Rect r) {
        set(r);
    }

    /** Initialize rectangle to the given values. They can be invalid. */
    @NonNull
    public Rect set(int x, int y, int w, int h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        return this;
    }

    /** Initialize rectangle to match the given one. */
    @NonNull
    public Rect set(@NonNull Rect r) {
        set(r.x, r.y, r.w, r.h);
        return this;
    }

    /** Returns a new instance of a rectangle with the same values. */
    @NonNull
    public Rect copy() {
        return new Rect(x, y, w, h);
    }

    /** Returns true if the rectangle has valid bounds, i.e. w&gt;0 and h&gt;0. */
    public boolean isValid() {
        return w > 0 && h > 0;
    }

    /** Returns true if the rectangle contains the x,y coordinates, borders included. */
    public boolean contains(int x, int y) {
        return isValid()
                && x >= this.x
                && y >= this.y
                && x < (this.x + this.w)
                && y < (this.y + this.h);
    }

    /**
     * Returns true if this rectangle intersects the given rectangle.
     * Two rectangles intersect if they overlap.
     * @param other the other rectangle to test
     * @return true if the two rectangles overlap
     */
    public boolean intersects(@Nullable Rect other) {
        if (other == null) {
            return false;
        }
        if (x2() <= other.x
                || other.x2() <= x
                || y2() <= other.y
                || other.y2() <= y) {
            return false;
        }

        return true;
    }

    /** Returns true if the rectangle fully contains the given rectangle */
    public boolean contains(@Nullable Rect rect) {
        return rect != null && x <= rect.x
                && y <= rect.y
                && x2() >= rect.x2()
                && y2() >= rect.y2();
    }

    /** Returns true if the rectangle contains the x,y coordinates, borders included. */
    public boolean contains(@Nullable Point p) {
        return p != null && contains(p.x, p.y);
    }

    /**
     * Moves this rectangle by setting it's x,y coordinates to the new values.
     * @return Returns self, for chaining.
     */
    @NonNull
    public Rect moveTo(int x, int y) {
        this.x = x;
        this.y = y;
        return this;
    }

    /**
     * Offsets this rectangle by adding the given x,y deltas to the x,y coordinates.
     * @return Returns self, for chaining.
     */
    @NonNull
    public Rect offsetBy(int x, int y) {
        this.x += x;
        this.y += y;
        return this;
    }

    @NonNull
    public Point getCenter() {
        return new Point(x + (w > 0 ? w / 2 : 0),
                         y + (h > 0 ? h / 2 : 0));
    }

    @NonNull
    public Point getTopLeft() {
        return new Point(x, y);
    }

    @NonNull
    public Point getBottomLeft() {
        return new Point(x,
                         y + (h > 0 ? h : 0));
    }

    @NonNull
    public Point getTopRight() {
        return new Point(x + (w > 0 ? w : 0),
                         y);
    }

    @NonNull
    public Point getBottomRight() {
        return new Point(x + (w > 0 ? w : 0),
                         y + (h > 0 ? h : 0));
    }

    /**
     * Returns the X coordinate of the right hand side of the rectangle
     *
     * @return the X coordinate of the right hand side of the rectangle
     */
    public int x2() {
        return x + w;
    }

    /**
     * Returns the Y coordinate of the bottom of the rectangle
     *
     * @return the Y coordinate of the bottom of the rectangle
     */
    public int y2() {
        return y + h;
    }

    /**
     * Returns the X coordinate of the center of the rectangle
     *
     * @return the X coordinate of the center of the rectangle
     */
    public int centerX() {
        return x + w / 2;
    }

    /**
     * Returns the Y coordinate of the center of the rectangle
     *
     * @return the Y coordinate of the center of the rectangle
     */
    public int centerY() {
        return y + h / 2;
    }

    @Override
    public String toString() {
        return String.format("Rect [(%d,%d)-(%d,%d): %dx%d]", x, y, x + w, y + h, w, h);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Rect) {
            Rect rhs = (Rect) obj;
            // validity must be equal on both sides.
            if (isValid() != rhs.isValid()) {
                return false;
            }
            // an invalid rect is equal to any other invalid rect regardless of coordinates
            if (!isValid() && !rhs.isValid()) {
                return true;
            }

            return this.x == rhs.x && this.y == rhs.y && this.w == rhs.w && this.h == rhs.h;
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hc = x;
        hc ^= ((y >>  8) & 0x0FFFFFF) | ((y & 0x00000FF) << 24);
        hc ^= ((w >> 16) & 0x000FFFF) | ((w & 0x000FFFF) << 16);
        hc ^= ((h >> 24) & 0x00000FF) | ((h & 0x0FFFFFF) <<  8);
        return hc;
    }

    /**
     * Returns the center point in the rectangle
     *
     * @return the center point in the rectangle
     */
    @NonNull
    public Point center() {
        return new Point(x + w / 2, y + h / 2);
    }
}
