/****************************************************************************
 Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
 
 http://www.cocos.com
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
****************************************************************************/

#pragma once

#include <algorithm>
#include "base/memory/Memory.h"
#include "base/std/container/vector.h"
#include "core/geometry/Enums.h"
#include "math/Mat3.h"
#include "math/Quaternion.h"
#include "math/Vec3.h"

namespace cc {
namespace geometry {

class Sphere;
class Frustum;
class Plane;

class AABB final : public ShapeBase {
public:
    /**
      * @en
      * create a new AABB
      * @zh
      * 创建一个新的 AABB 实例。
      * @param px - AABB 的原点的 X 坐标。
      * @param py - AABB 的原点的 Y 坐标。
      * @param pz - AABB 的原点的 Z 坐标。
      * @param hw - AABB 宽度的一半。
      * @param hh - AABB 高度的一半。
      * @param hl - AABB 长度的一半。
      * @returns 返回新创建的 AABB 实例。
      */
    static AABB *create(float px, float py, float pz, float hw, float hh, float hl) {
        return ccnew AABB(px, py, pz, hw, hh, hl);
    }

    /**
      * @en
      * Set the components of a AABB to the given values
      * @zh
      * 将 AABB 的属性设置为给定的值。
      * @param {AABB} out 接受操作的 AABB。
      * @param px - AABB 的原点的 X 坐标。
      * @param py - AABB 的原点的 Y 坐标。
      * @param pz - AABB 的原点的 Z 坐标。
      * @param hw - AABB 宽度的一半。
      * @param hh - AABB 高度的一半。
      * @param hl - AABB 长度度的一半。
      * @return {AABB} out 接受操作的 AABB。
      */

    static AABB *set(AABB *out, float px, float py,
                     float pz,
                     float hw,
                     float hh,
                     float hl) {
        out->setCenter(px, py, pz);
        out->setHalfExtents(hw, hh, hl);
        return out;
    }

    /**
      * @en
      * Merge tow AABB.
      * @zh
      * 合并两个 AABB 到 out。
      * @param out 接受操作的 AABB。
      * @param a 输入的 AABB。
      * @param b 输入的 AABB。
      * @returns {AABB} out 接受操作的 AABB。
      */
    static AABB *merge(AABB *out, const AABB &a, const AABB &b);

    /**
      * @en
      * AABB to sphere
      * @zh
      * 包围盒转包围球
      * @param out 接受操作的 sphere。
      * @param a 输入的 AABB。
      */

    static Sphere *toBoundingSphere(Sphere *out, const AABB &a);

    static AABB *fromPoints(const Vec3 &minPos, const Vec3 &maxPos, AABB *dst);
    static void transformExtentM4(Vec3 *out, const Vec3 &extent, const Mat4 &m4);

    AABB(float px, float py, float pz, float hw, float hh, float hl);
    AABB() : ShapeBase(ShapeEnum::SHAPE_AABB) {}
    ~AABB() override = default;

    /**
     * @en
     * aabb-plane intersect detect.
     * @zh
     * 轴对齐包围盒和平面的相交性检测。
     * @param {AABB} aabb 轴对齐包围盒
     * @param {Plane} plane 平面
     * @return {number} inside(back) = -1, outside(front) = 0, intersect = 1
     */
    bool aabbAabb(const AABB &aabb) const;

    /**
     * @en
     * aabb-frustum intersect detect, faster but has false positive corner cases.
     * @zh
     * 轴对齐包围盒和锥台相交性检测，速度快，但有错误情况。
     * @param {AABB} aabb 轴对齐包围盒
     * @param {Frustum} frustum 锥台
     * @return {number} 0 或 非0
     */
    bool aabbFrustum(const Frustum &) const;

    int aabbPlane(const Plane &) const;
    void getBoundary(cc::Vec3 *minPos, cc::Vec3 *maxPos) const;
    void merge(const AABB &aabb);
    void merge(const cc::Vec3 &point);
    void merge(const ccstd::vector<cc::Vec3> &points);
    void merge(const Frustum &frustum);
    void set(const cc::Vec3 &centerVal, const cc::Vec3 &halfExtentVal);
    void transform(const Mat4 &m, AABB *out) const;
    bool contain(const cc::Vec3 &point) const;
    inline void setCenter(float x, float y, float z) { center.set(x, y, z); }
    inline void setCenter(const Vec3 &center) { this->center.set(center); }
    inline void setValid(bool isValid) { _isValid = isValid; }
    inline const Vec3 &getCenter() const { return center; }
    inline bool isValid() const { return _isValid; }
    inline void setHalfExtents(float x, float y, float z) { halfExtents.set(x, y, z); }
    inline void setHalfExtents(const Vec3 &halfExtents) { this->halfExtents.set(halfExtents); }
    inline const Vec3 &getHalfExtents() const { return halfExtents; }

    cc::Vec3 center;
    cc::Vec3 halfExtents{1, 1, 1};

private:
    bool _isValid{true};
};

} // namespace geometry
} // namespace cc
