#ifndef _RIVE_MAT2D_HPP_
#define _RIVE_MAT2D_HPP_

#include "rive/math/aabb.hpp"
#include "rive/math/vec2d.hpp"
#include <array>
#include <cstddef>

namespace rive
{
class TransformComponents;
class Mat2D
{
public:
    constexpr Mat2D() : m_buffer{{1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f}} {}
    constexpr Mat2D(float x1,
                    float y1,
                    float x2,
                    float y2,
                    float tx,
                    float ty) :
        m_buffer{{x1, y1, x2, y2, tx, ty}}
    {}

    inline const float* values() const { return &m_buffer[0]; }

    float& operator[](std::size_t idx) { return m_buffer[idx]; }
    const float& operator[](std::size_t idx) const { return m_buffer[idx]; }

    static Mat2D fromRotation(float rad);
    static Mat2D fromScale(float sx, float sy) { return {sx, 0, 0, sy, 0, 0}; }
    static Mat2D fromTranslate(float tx, float ty)
    {
        return {1, 0, 0, 1, tx, ty};
    }
    static Mat2D fromTranslation(Vec2D translation)
    {
        return {1, 0, 0, 1, translation.x, translation.y};
    }
    static Mat2D fromScaleAndTranslation(float sx, float sy, float tx, float ty)
    {
        return {sx, 0, 0, sy, tx, ty};
    }

    void scaleByValues(float sx, float sy);

    Mat2D& operator*=(const Mat2D& rhs)
    {
        *this = Mat2D::multiply(*this, rhs);
        return *this;
    }

    // Sets dst[i] = M * pts[i] for i in 0..n-1.
    void mapPoints(Vec2D dst[], const Vec2D pts[], size_t n) const;

    // Computes a bounding box that would tightly contain the given points if
    // they were to all be transformed by this matrix, ignoring NaN values.
    // Returns {0, 0, 0, 0} if the given points are empty or all NaN.
    AABB mapBoundingBox(const Vec2D pts[], size_t n) const;
    AABB mapBoundingBox(const AABB&) const;

    // If returns true, result holds the inverse.
    // If returns false, result is unchnaged.
    bool invert(Mat2D* result) const;

    Mat2D invertOrIdentity() const
    {
        Mat2D inverse;          // initialized to identity
        (void)invert(&inverse); // inverse is unchanged if invert() fails
        return inverse;
    }

    TransformComponents decompose() const;
    static Mat2D compose(const TransformComponents&);
    float findMaxScale() const;
    Mat2D scale(Vec2D) const;
    Mat2D translate(Vec2D) const;

    static Mat2D multiply(const Mat2D& a, const Mat2D& b);

    float xx() const { return m_buffer[0]; }
    float xy() const { return m_buffer[1]; }
    float yx() const { return m_buffer[2]; }
    float yy() const { return m_buffer[3]; }
    float tx() const { return m_buffer[4]; }
    float ty() const { return m_buffer[5]; }

    Vec2D translation() const { return {m_buffer[4], m_buffer[5]}; }

    void xx(float value) { m_buffer[0] = value; }
    void xy(float value) { m_buffer[1] = value; }
    void yx(float value) { m_buffer[2] = value; }
    void yy(float value) { m_buffer[3] = value; }
    void tx(float value) { m_buffer[4] = value; }
    void ty(float value) { m_buffer[5] = value; }

    float determinant() const
    {
        return m_buffer[0] * m_buffer[3] - m_buffer[2] * m_buffer[1];
    }

private:
    std::array<float, 6> m_buffer;
};
static_assert(std::is_trivially_destructible<Mat2D>::value,
              "Mat2D must be trivially destructible");

inline Vec2D operator*(const Mat2D& m, Vec2D v)
{
    return {
        m[0] * v.x + m[2] * v.y + m[4],
        m[1] * v.x + m[3] * v.y + m[5],
    };
}

inline Mat2D operator*(const Mat2D& a, const Mat2D& b)
{
    return Mat2D::multiply(a, b);
}

inline bool operator==(const Mat2D& a, const Mat2D& b)
{
    return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3] &&
           a[4] == b[4] && a[5] == b[5];
}

inline bool operator!=(const Mat2D& a, const Mat2D& b) { return !(a == b); }

} // namespace rive
#endif
