/*
 * Copyright 2018 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkRectPriv_DEFINED
#define SkRectPriv_DEFINED

#include "include/core/SkRect.h"
#include "src/base/SkMathPriv.h"
#include "src/base/SkVx.h"

class SkM44;
class SkMatrix;

class SkRectPriv {
public:
    // Returns an irect that is very large, and can be safely round-trip with SkRect and still
    // be considered non-empty (i.e. width/height > 0) even if we round-out the SkRect.
    static SkIRect MakeILarge() {
        // SK_MaxS32 >> 1 seemed better, but it did not survive round-trip with SkRect and rounding.
        // Also, 1 << 29 can be perfectly represented in float, while SK_MaxS32 >> 1 cannot.
        const int32_t large = 1 << 29;
        return { -large, -large, large, large };
    }

    static SkIRect MakeILargestInverted() {
        return { SK_MaxS32, SK_MaxS32, SK_MinS32, SK_MinS32 };
    }

    static SkRect MakeLargeS32() {
        SkRect r;
        r.set(MakeILarge());
        return r;
    }

    static SkRect MakeLargest() {
        return { SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax };
    }

    static constexpr SkRect MakeLargestInverted() {
        return { SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin };
    }

    static void GrowToInclude(SkRect* r, const SkPoint& pt) {
        r->fLeft  =  std::min(pt.fX, r->fLeft);
        r->fRight =  std::max(pt.fX, r->fRight);
        r->fTop    = std::min(pt.fY, r->fTop);
        r->fBottom = std::max(pt.fY, r->fBottom);
    }

    // Conservative check if r can be expressed in fixed-point.
    // Will return false for very large values that might have fit
    static bool FitsInFixed(const SkRect& r) {
        return SkFitsInFixed(r.fLeft) && SkFitsInFixed(r.fTop) &&
               SkFitsInFixed(r.fRight) && SkFitsInFixed(r.fBottom);
    }

    // Returns r.width()/2 but divides first to avoid width() overflowing.
    static constexpr float HalfWidth(const SkRect& r) {
        return sk_float_midpoint(-r.fLeft, r.fRight);
    }
    // Returns r.height()/2 but divides first to avoid height() overflowing.
    static constexpr float HalfHeight(const SkRect& r) {
        return sk_float_midpoint(-r.fTop, r.fBottom);
    }

    // Evaluate A-B. If the difference shape cannot be represented as a rectangle then false is
    // returned and 'out' is set to the largest rectangle contained in said shape. If true is
    // returned then A-B is representable as a rectangle, which is stored in 'out'.
    static bool Subtract(const SkRect& a, const SkRect& b, SkRect* out);
    static bool Subtract(const SkIRect& a, const SkIRect& b, SkIRect* out);

    // Evaluate A-B, and return the largest rectangle contained in that shape (since the difference
    // may not be representable as rectangle). The returned rectangle will not intersect B.
    static SkRect Subtract(const SkRect& a, const SkRect& b) {
        SkRect diff;
        Subtract(a, b, &diff);
        return diff;
    }
    static SkIRect Subtract(const SkIRect& a, const SkIRect& b) {
        SkIRect diff;
        Subtract(a, b, &diff);
        return diff;
    }

    // Returns true if the quadrilateral formed by transforming the four corners of 'a' contains 'b'
    // 'tol' is in the same coordinate space as 'b', to treat 'b' as 'tol' units inset.
    static bool QuadContainsRect(const SkMatrix& m,
                                 const SkIRect& a,
                                 const SkIRect& b,
                                 float tol=0.f);
    static bool QuadContainsRect(const SkM44& m, const SkRect& a, const SkRect& b, float tol=0.f);
    // Like QuadContainsRect() but returns the edge test masks ordered T, R, B, L.
    static skvx::int4 QuadContainsRectMask(const SkM44& m, const SkRect& a, const SkRect& b,
                                           float tol=0.f);

    // Assuming 'src' does not intersect 'dst', returns the edge or corner of 'src' that is closest
    // to 'dst', e.g. the pixels that would be sampled from 'src' when clamp-tiled into 'dst'.
    //
    // The returned rectangle will not be empty if 'src' is not empty and 'dst' is not empty.
    // At least one of its width or height will be equal to 1 (possibly both if a corner is closest)
    //
    // Returns src.intersect(dst) if they do actually intersect.
    static SkIRect ClosestDisjointEdge(const SkIRect& src, const SkIRect& dst);
};


#endif
