#ifndef EGG_FRUSTUM_H
#define EGG_FRUSTUM_H

#include "nw4r/g3d/g3d_camera.h"

namespace EGG {
class Frustum {
public:
    enum Flag {
        FLAG_DIRTY = (1 << 0),
        FLAG_0x02 = (1 << 1),
        FLAG_0x04 = (1 << 2),
        FLAG_0x08 = (1 << 3),
        FLAG_0x20 = (1 << 5),
        FLAG_0x40 = (1 << 6),
    };

    enum ProjectionType {
        PROJ_ORTHO,
        PROJ_PERSP
    };

    enum CanvasMode {
        CANVASMODE_0,
        CANVASMODE_1,
    };

protected:
    ProjectionType mProjType; // at 0x0
    CanvasMode mCanvasMode;   // at 0x4
    nw4r::math::VEC2 mSize;   // at 0x8
    f32 mFovY;                // at 0x10
    f32 mTanFovY;             // at 0x14
    f32 mNearZ;               // at 0x18
    f32 mFarZ;                // at 0x1C
    nw4r::math::VEC2 mOffset; // at 0x20
    nw4r::math::VEC3 mScale;  // at 0x28
    mutable u16 mFlags;       // at 0x34

public:
    Frustum(ProjectionType, const nw4r::math::VEC2 &, f32, f32, CanvasMode);
    Frustum(const Frustum &);

    virtual ~Frustum() {}                            // at 0x8
    virtual void SetProjectionGX() const;            // at 0xC
    virtual void CopyToG3D(nw4r::g3d::Camera) const; // at 0x10
    void ResetOrthographic(f32, f32, f32, f32, f32, f32);
    void CopyFromAnother(const Frustum &);
    void SetFovy(f32);
    void ConvertToCanvasLU(f32, f32, f32 *, f32 *) const;

    static void SetGlobalScaleOffset(f32, f32, f32, f32);
    static void GetGlobalScaleOffset(f32 *, f32 *, f32 *, f32 *);

    f32 GetScreenSizeToViewSize(f32, f32) const;

    void SetDirty(bool dirty) const {
        if (dirty) {
            mFlags |= FLAG_DIRTY;
        } else {
            mFlags &= ~FLAG_DIRTY;
        }
    }

    ProjectionType GetProjectionType() const {
        return mProjType;
    }
    void SetProjectionType(ProjectionType type) {
        mProjType = type;
    }

    CanvasMode GetCanvasMode() const {
        return mCanvasMode;
    }
    void SetCanvasMode(CanvasMode mode) {
        if (mode != mCanvasMode) {
            SetDirty(true);
            mCanvasMode = mode;
        }
    }

    const nw4r::math::VEC2 &GetSize() const {
        return mSize;
    }

    u16 GetWidth() const {
        return mSize.x;
    }
    f32 GetSizeX() const {
        return mSize.x;
    }
    void SetSizeX(f32 sizeX) {
        SetDirty(true);
        mSize.x = sizeX;
    }

    u16 GetHeight() const {
        return mSize.y;
    }
    f32 GetSizeY() const {
        return mSize.y;
    }
    void SetSizeY(f32 sizeY) {
        SetDirty(true);
        mSize.y = sizeY;
    }

    void SetSize(const nw4r::math::VEC2 &size) {
        SetSizeX(size.x);
        SetSizeY(size.y);
    }

    f32 GetAspect() const {
        return GetSize().x / GetSize().y;
    }

    void SetNearZ(f32 nearZ) {
        mNearZ = nearZ;
    }
    f32 GetNearZ() const {
        return mNearZ;
    }

    void SetFarZ(f32 farZ) {
        mFarZ = farZ;
    }
    f32 GetFarZ() const {
        return mFarZ;
    }

    void SetNearFar(f32 near, f32 far) {
        mNearZ = near;
        mFarZ = far;
    }

    void SetScale(const nw4r::math::VEC3 &scale) {
        mScale = scale;
    }
    void SetOffset(const nw4r::math::VEC2 &offset) {
        mOffset = offset;
    }

    void SetFlag(u32 flag) {
        mFlags |= flag;
    }

    f32 TranslateDepthToViewSpace(f32 depth) const {
        depth = -depth;
        f32 bound = -1e-6f;
        f32 val = depth >= bound ? bound : depth;
        ProjectionType proj = GetProjectionType();
        f32 near = mNearZ;
        f32 far = mFarZ;
        f32 diff = far - near;
        f32 viewDepth;
        if (proj == PROJ_PERSP) {
            viewDepth = ((val * -near) / diff - (far * near) / diff) / -val + 1.0f;
        } else {
            viewDepth = (-val - near) / diff;
        }
        if (viewDepth < 0.0f) {
            return 0.0f;
        } else if (viewDepth > 1.0f) {
            return 1.0f;
        }
        return viewDepth;
    }

private:
    void SetProjectionPerspectiveGX_() const;
    void SetProjectionOrthographicGX_() const;

    void CopyToG3D_Perspective_(nw4r::g3d::Camera) const;
    void CopyToG3D_Orthographic_(nw4r::g3d::Camera) const;

    void CalcMtxPerspective_(nw4r::math::MTX44 *) const;

    void GetOrthographicParam_(f32 *) const;
    void GetPerspectiveParam_(f32 *) const;
    void GetOrthographicParam_(f32 *, f32 *, f32 *, f32 *) const;

private:
    static nw4r::math::VEC2 sGlobalScale;
    static nw4r::math::VEC2 sGlobalOffset;
};
} // namespace EGG

#endif
