/*
Matrix4.h
Written by Matthew Fisher

a 4x4 transformation matrix structure.
*/

class Matrix4
{
public:
    //
    // Initalization
    //
    Matrix4();
    Matrix4(const Matrix4 &M);
    Matrix4(const Vec3f &V0, const Vec3f &V1, const Vec3f &V2);

#ifdef USE_D3D
    Matrix4(const D3DXMATRIX &M);
#endif

    //
    // Assignment
    //
    Matrix4& operator = (const Matrix4 &M);

    //
    // Math properties
    //
    float Determinant() const;
    Matrix4 Transpose() const;
    Matrix4 Inverse() const;

    //
    // Vector transforms
    //
    __forceinline Vec3f TransformPoint(const Vec3f &point) const
    {
        float w = point.x * _Entries[0][3] + point.y * _Entries[1][3] + point.z * _Entries[2][3] + _Entries[3][3];
        if(w)
        {
            const float invW = 1.0f / w;
            return Vec3f( (point.x * _Entries[0][0] + point.y * _Entries[1][0] + point.z * _Entries[2][0] + _Entries[3][0]) * invW,
                          (point.x * _Entries[0][1] + point.y * _Entries[1][1] + point.z * _Entries[2][1] + _Entries[3][1]) * invW,
                          (point.x * _Entries[0][2] + point.y * _Entries[1][2] + point.z * _Entries[2][2] + _Entries[3][2]) * invW);
        }
        else
        {
            return Vec3f::Origin;
        }
    }
    
    __forceinline Vec3f TransformNormal(const Vec3f &normal) const
    {
        return Vec3f(normal.x * _Entries[0][0] + normal.y * _Entries[1][0] + normal.z * _Entries[2][0],
                     normal.x * _Entries[0][1] + normal.y * _Entries[1][1] + normal.z * _Entries[2][1],
                     normal.x * _Entries[0][2] + normal.y * _Entries[1][2] + normal.z * _Entries[2][2]);
    }

    Plane TransformPlane(const Plane &P) const;
    
    //
    // Formatting
    //
    String CommaSeparatedString() const;
    String CommaSeparatedStringSingleLine() const;
    String SpaceSeperatedStringSingleLine() const;
    String TabSeperatedString() const;

#ifdef USE_D3D
    operator D3DXMATRIX() const;
#endif
    
    //
    // Accessors
    //
    __forceinline float* operator [] (int Row)
    {
        return _Entries[Row];
    }
    __forceinline const float* operator [] (int Row) const
    {
        return _Entries[Row];
    }
    __forceinline void SetColumn(UINT Column, const Vec4f &Values)
    {
        _Entries[0][Column] = Values.x;
        _Entries[1][Column] = Values.y;
        _Entries[2][Column] = Values.z;
        _Entries[3][Column] = Values.w;
    }
    __forceinline void SetRow(UINT Row, const Vec4f &Values)
    {
        _Entries[Row][0] = Values.x;
        _Entries[Row][1] = Values.y;
        _Entries[Row][2] = Values.z;
        _Entries[Row][3] = Values.w;
    }
    __forceinline Vec4f GetColumn(UINT Column)
    {
        Vec4f Result;
        Result.x = _Entries[0][Column];
        Result.y = _Entries[1][Column];
        Result.z = _Entries[2][Column];
        Result.w = _Entries[3][Column];
        return Result;
    }
    __forceinline Vec4f GetRow(UINT Row)
    {
        Vec4f Result;
        Result.x = _Entries[Row][0];
        Result.y = _Entries[Row][1];
        Result.z = _Entries[Row][2];
        Result.w = _Entries[Row][3];
        return Result;
    }

    //
    // Transformation matrices
    //
    static Matrix4 Identity();
    static Matrix4 Scaling(const Vec3f &ScaleFactors);
    static Matrix4 Scaling(float ScaleFactor)
    {
        return Scaling(Vec3f(ScaleFactor, ScaleFactor, ScaleFactor));
    }
    static Matrix4 Translation(const Vec3f &Pos);
    static Matrix4 Rotation(const Vec3f &Axis, float Angle, const Vec3f &Center);
    static Matrix4 Rotation(const Vec3f &Axis, float Angle);
    static Matrix4 Rotation(float Yaw, float Pitch, float Roll);
    static Matrix4 Rotation(const Vec3f &Basis1, const Vec3f &Basis2, const Vec3f &Basis3);
    static Matrix4 RotationX(float Theta);
    static Matrix4 RotationY(float Theta);
    static Matrix4 RotationZ(float Theta);
    static Matrix4 Camera(const Vec3f &Eye, const Vec3f &Look, const Vec3f &Up, const Vec3f &Right);
    static Matrix4 LookAt(const Vec3f &Eye, const Vec3f &At, const Vec3f &Up);
    static Matrix4 Orthogonal(float Width, float Height, float ZNear, float ZFar);
    static Matrix4 Perspective(float Width, float Height, float ZNear, float ZFar);
    static Matrix4 PerspectiveFov(float FOV, float Aspect, float ZNear, float ZFar);
    static Matrix4 PerspectiveMultiFov(float FovX, float FovY, float ZNear, float ZFar);
    static Matrix4 Face(const Vec3f &V0, const Vec3f &V1);
    static Matrix4 Viewport(float Width, float Height);
    static Matrix4 ChangeOfBasis(const Vec3f &Source0, const Vec3f &Source1, const Vec3f &Source2, const Vec3f &SourceOrigin, 
                                 const Vec3f &Target0, const Vec3f &Target1, const Vec3f &Target2, const Vec3f &TargetOrigin);
    static float CompareMatrices(const Matrix4 &Left, const Matrix4 &Right);

private:
    float _Entries[4][4];
};

//
// Overloaded operators
//
ostream& operator << (ostream &os, const Matrix4 &m);
istream& operator >> (istream &os, Matrix4 &m);

Matrix4 operator * (const Matrix4 &Left, const Matrix4 &Right);
Matrix4 operator * (const Matrix4 &Left, float &Right);
Matrix4 operator * (float &Left, const Matrix4 &Right);
Matrix4 operator + (const Matrix4 &Left, const Matrix4 &Right);
Matrix4 operator - (const Matrix4 &Left, const Matrix4 &Right);

__forceinline Vec4f operator * (const Vec4f &Right, const Matrix4 &Left)
{
    return Vec4f(Right.x * Left[0][0] + Right.y * Left[1][0] + Right.z * Left[2][0] + Right.w * Left[3][0],
                Right.x * Left[0][1] + Right.y * Left[1][1] + Right.z * Left[2][1] + Right.w * Left[3][1],
                Right.x * Left[0][2] + Right.y * Left[1][2] + Right.z * Left[2][2] + Right.w * Left[3][2],
                Right.x * Left[0][3] + Right.y * Left[1][3] + Right.z * Left[2][3] + Right.w * Left[3][3]);
}