/*
 * vec_mat.h - vector and matrix defination & calculation
 *
 *  Copyright (c) 2017 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author: Zong Wei <wei.zong@intel.com>
 */

#ifndef XCAM_VECTOR_MATRIX_H
#define XCAM_VECTOR_MATRIX_H

#include <xcam_std.h>
#include <cmath>


namespace XCam {

#ifndef FLT_EPSILON
#define FLT_EPSILON 1.19209290e-07F // float
#endif

#ifndef DBL_EPSILON
#define DBL_EPSILON 2.2204460492503131e-16 // double
#endif

#ifndef DEGREE_2_RADIANS
#define DEGREE_2_RADIANS(x) (((x) * XCAM_PI) / 180.0)
#endif

#ifndef RADIANS_2_DEGREE
#define RADIANS_2_DEGREE(x) (((x) * 180.0) / XCAM_PI)
#endif

#define XCAM_VECT2_OPERATOR_VECT2(op)                       \
    Vector2<T> operator op (const Vector2<T>& b) const {    \
        return Vector2<T>(x op b.x, y op b.y);              \
    }                                                       \
    Vector2<T> &operator op##= (const Vector2<T>& b) {      \
        x op##= b.x;  y op##= b.y; return *this;            \
    }

#define XCAM_VECT2_OPERATOR_SCALER(op)                      \
    Vector2<T> operator op (const T& b) const {             \
        return Vector2<T>(x op b, y op b);                  \
    }                                                       \
    Vector2<T> &operator op##= (const T& b) {               \
        x op##= b;  y op##= b; return *this;                \
    }

template<class T>
class Vector2
{
public:

    T x;
    T y;

    Vector2 () : x(0), y(0) {};
    Vector2 (T _x, T _y) : x(_x), y(_y) {};

    template <typename New>
    Vector2<New> convert_to () const {
        Vector2<New> ret((New)(this->x), (New)(this->y));
        return ret;
    }

    Vector2<T>& operator = (const Vector2<T>& rhs)
    {
        x = rhs.x;
        y = rhs.y;
        return *this;
    }

    template <typename Other>
    Vector2<T>& operator = (const Vector2<Other>& rhs)
    {
        x = rhs.x;
        y = rhs.y;
        return *this;
    }

    Vector2<T> operator - () const {
        return Vector2<T>(-x, -y);
    }

    XCAM_VECT2_OPERATOR_VECT2 (+)
    XCAM_VECT2_OPERATOR_VECT2 (-)
    XCAM_VECT2_OPERATOR_VECT2 (*)
    XCAM_VECT2_OPERATOR_VECT2 ( / )
    XCAM_VECT2_OPERATOR_SCALER (+)
    XCAM_VECT2_OPERATOR_SCALER (-)
    XCAM_VECT2_OPERATOR_SCALER (*)
    XCAM_VECT2_OPERATOR_SCALER ( / )

    bool operator == (const Vector2<T>& rhs) const {
        return (x == rhs.x) && (y == rhs.y);
    }

    void reset () {
        this->x = (T) 0;
        this->y = (T) 0;
    }

    void set (T _x, T _y) {
        this->x = _x;
        this->y = _y;
    }

    T magnitude () const {
        return (T) sqrtf(x * x + y * y);
    }

    float distance (const Vector2<T>& vec) const {
        return sqrtf((vec.x - x) * (vec.x - x) + (vec.y - y) * (vec.y - y));
    }

    T dot (const Vector2<T>& vec) const {
        return (x * vec.x + y * vec.y);
    }

    inline Vector2<T> lerp (T weight, const Vector2<T>& vec) const {
        return (*this) + (vec - (*this)) * weight;
    }

};

template<class T, uint32_t N>
class VectorN
{
public:

    VectorN ();
    VectorN (T x);
    VectorN (T x, T y);
    VectorN (T x, T y, T z);
    VectorN (T x, T y, T z, T w);
    VectorN (VectorN<T, 3> vec3, T w);

    inline VectorN<T, N>& operator = (const VectorN<T, N>& rhs);
    inline VectorN<T, N> operator - () const;
    inline bool operator == (const VectorN<T, N>& rhs) const;

    inline T& operator [] (uint32_t index) {
        XCAM_ASSERT(index < N);
        return data[index];
    }
    inline const T& operator [] (uint32_t index) const {
        XCAM_ASSERT(index < N);
        return data[index];
    }

    inline VectorN<T, N> operator + (const T rhs) const;
    inline VectorN<T, N> operator - (const T rhs) const;
    inline VectorN<T, N> operator * (const T rhs) const;
    inline VectorN<T, N> operator / (const T rhs) const;
    inline VectorN<T, N> operator += (const T rhs);
    inline VectorN<T, N> operator -= (const T rhs);
    inline VectorN<T, N> operator *= (const T rhs);
    inline VectorN<T, N> operator /= (const T rhs);

    inline VectorN<T, N> operator + (const VectorN<T, N>& rhs) const;
    inline VectorN<T, N> operator - (const VectorN<T, N>& rhs) const;
    inline VectorN<T, N> operator * (const VectorN<T, N>& rhs) const;
    inline VectorN<T, N> operator / (const VectorN<T, N>& rhs) const;
    inline VectorN<T, N> operator += (const VectorN<T, N>& rhs);
    inline VectorN<T, N> operator -= (const VectorN<T, N>& rhs);
    inline VectorN<T, N> operator *= (const VectorN<T, N>& rhs);
    inline VectorN<T, N> operator /= (const VectorN<T, N>& rhs);

    template <typename NEW> inline
    VectorN<NEW, N> convert_to () const;

    inline void zeros ();
    inline void set (T x, T y);
    inline void set (T x, T y, T z);
    inline void set (T x, T y, T z, T w);
    inline T magnitude () const;
    inline float distance (const VectorN<T, N>& vec) const;
    inline T dot (const VectorN<T, N>& vec) const;
    inline VectorN<T, N> lerp (T weight, const VectorN<T, N>& vec) const;

private:
    T data[N];

};


template<class T, uint32_t N> inline
VectorN<T, N>::VectorN ()
{
    for (uint32_t i = 0; i < N; i++) {
        data[i] = 0;
    }
}

template<class T, uint32_t N> inline
VectorN<T, N>::VectorN (T x) {
    data[0] = x;
}

template<class T, uint32_t N> inline
VectorN<T, N>::VectorN (T x, T y) {
    if (N >= 2) {
        data[0] = x;
        data[1] = y;
    }
}

template<class T, uint32_t N> inline
VectorN<T, N>::VectorN (T x, T y, T z) {
    if (N >= 3) {
        data[0] = x;
        data[1] = y;
        data[2] = z;
    }
}

template<class T, uint32_t N> inline
VectorN<T, N>::VectorN (T x, T y, T z, T w) {
    if (N >= 4) {
        data[0] = x;
        data[1] = y;
        data[2] = z;
        data[3] = w;
    }
}

template<class T, uint32_t N> inline
VectorN<T, N>::VectorN (VectorN<T, 3> vec3, T w) {
    if (N >= 4) {
        data[0] = vec3.data[0];
        data[1] = vec3.data[1];
        data[2] = vec3.data[2];
        data[3] = w;
    }
}

template<class T, uint32_t N> inline
VectorN<T, N>& VectorN<T, N>::operator = (const VectorN<T, N>& rhs) {
    for (uint32_t i = 0; i < N; i++) {
        data[i] = rhs.data[i];
    }

    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator - () const {
    for (uint32_t i = 0; i < N; i++) {
        data[i] = -data[i];
    }

    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator + (const T rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] + rhs;
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator - (const T rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] - rhs;
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator * (const T rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] * rhs;
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator / (const T rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] / rhs;
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator += (const T rhs) {
    for (uint32_t i = 0; i < N; i++) {
        data[i] += rhs;
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator -= (const T rhs) {
    for (uint32_t i = 0; i < N; i++) {
        data[i] -= rhs;
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator *= (const T rhs) {
    for (uint32_t i = 0; i < N; i++) {
        data[i] *= rhs;
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator /= (const T rhs) {
    for (uint32_t i = 0; i < N; i++) {
        data[i] /= rhs;
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator + (const VectorN<T, N>& rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] + rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator - (const VectorN<T, N>& rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] - rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator * (const VectorN<T, N>& rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] * rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator / (const VectorN<T, N>& rhs) const {
    VectorN<T, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result.data[i] = data[i] / rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator += (const VectorN<T, N>& rhs) {

    for (uint32_t i = 0; i < N; i++) {
        data[i] += rhs.data[i];
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator -= (const VectorN<T, N>& rhs) {

    for (uint32_t i = 0; i < N; i++) {
        data[i] -= rhs.data[i];
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator *= (const VectorN<T, N>& rhs) {

    for (uint32_t i = 0; i < N; i++) {
        data[i] *= rhs.data[i];
    }
    return *this;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::operator /= (const VectorN<T, N>& rhs) {

    for (uint32_t i = 0; i < N; i++) {
        data[i] /= rhs.data[i];
    }
    return *this;
}

template<class T, uint32_t N> inline
bool VectorN<T, N>::operator == (const VectorN<T, N>& rhs) const {
    for (uint32_t i = 0; i < N; i++) {
        if (data[i] != rhs[i]) {
            return false;
        }
    }
    return true;
}

template <class T, uint32_t N>
template <typename NEW>
VectorN<NEW, N> VectorN<T, N>::convert_to () const {
    VectorN<NEW, N> result;

    for (uint32_t i = 0; i < N; i++) {
        result[i] = (NEW)(this->data[i]);
    }
    return result;
}

template <class T, uint32_t N> inline
void VectorN<T, N>::zeros () {
    for (uint32_t i = 0; i < N; i++) {
        data[i] = (T)(0);
    }
}

template<class T, uint32_t N> inline
void VectorN<T, N>::set (T x, T y) {
    if (N >= 2) {
        data[0] = x;
        data[1] = y;
    }
}

template<class T, uint32_t N> inline
void VectorN<T, N>::set (T x, T y, T z) {
    if (N >= 3) {
        data[0] = x;
        data[1] = y;
        data[2] = z;
    }
}

template<class T, uint32_t N> inline
void VectorN<T, N>::set (T x, T y, T z, T w) {
    if (N >= 4) {
        data[0] - x;
        data[1] = y;
        data[2] = z;
        data[3] = w;
    }
}

template<class T, uint32_t N> inline
T VectorN<T, N>::magnitude () const {
    T result = 0;

    for (uint32_t i = 0; i < N; i++) {
        result += (data[i] * data[i]);
    }
    return (T) sqrtf(result);
}

template<class T, uint32_t N> inline
float VectorN<T, N>::distance (const VectorN<T, N>& vec) const {
    T result = 0;

    for (uint32_t i = 0; i < N; i++) {
        result += (vec.data[i] - data[i]) * (vec.data[i] - data[i]);
    }
    return sqrtf(result);
}

template<class T, uint32_t N> inline
T VectorN<T, N>::dot (const VectorN<T, N>& vec) const {
    T result = 0;

    for (uint32_t i = 0; i < N; i++) {
        result += (vec.data[i] * data[i]);
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> VectorN<T, N>::lerp (T weight, const VectorN<T, N>& vec) const {
    return (*this) + (vec - (*this)) * weight;
}

// NxN matrix in row major order
template<class T, uint32_t N>
class MatrixN
{
public:
    MatrixN ();
    MatrixN (VectorN<T, 2> a, VectorN<T, 2> b);
    MatrixN (VectorN<T, 3> a, VectorN<T, 3> b, VectorN<T, 3> c);
    MatrixN (VectorN<T, 4> a, VectorN<T, 4> b, VectorN<T, 4> c, VectorN<T, 4> d);

    inline void zeros ();
    inline void eye ();

    inline T& at (uint32_t row, uint32_t col) {
        XCAM_ASSERT(row < N && col < N);
        return data[row * N + col];
    };
    inline const T& at (uint32_t row, uint32_t col) const {
        XCAM_ASSERT(row < N && col < N);
        return data[row * N + col];
    };

    inline T& operator () (uint32_t row, uint32_t col) {
        return at (row, col);
    };
    inline const T& operator () (uint32_t row, uint32_t col) const {
        return at (row, col);
    };

    inline MatrixN<T, N>& operator = (const MatrixN<T, N>& rhs);
    inline MatrixN<T, N> operator - () const;
    inline MatrixN<T, N> operator + (const MatrixN<T, N>& rhs) const;
    inline MatrixN<T, N> operator - (const MatrixN<T, N>& rhs) const;
    inline MatrixN<T, N> operator * (const T a) const;
    inline MatrixN<T, N> operator / (const T a) const;
    inline VectorN<T, N> operator * (const VectorN<T, N>& rhs) const;
    inline MatrixN<T, N> operator * (const MatrixN<T, N>& rhs) const;
    inline MatrixN<T, N> transpose ();
    inline MatrixN<T, N> inverse ();
    inline T trace ();

private:
    inline MatrixN<T, 2> inverse (const MatrixN<T, 2>& mat);
    inline MatrixN<T, 3> inverse (const MatrixN<T, 3>& mat);
    inline MatrixN<T, 4> inverse (const MatrixN<T, 4>& mat);

private:
    T data[N * N];

};

// NxN matrix in row major order
template<class T, uint32_t N>
MatrixN<T, N>::MatrixN () {
    eye ();
}

template<class T, uint32_t N>
MatrixN<T, N>::MatrixN (VectorN<T, 2> a, VectorN<T, 2> b) {
    if (N == 2) {
        data[0] = a[0];
        data[1] = a[1];
        data[2] = b[0];
        data[3] = b[1];
    } else {
        eye ();
    }
}

template<class T, uint32_t N>
MatrixN<T, N>::MatrixN (VectorN<T, 3> a, VectorN<T, 3> b, VectorN<T, 3> c) {
    if (N == 3) {
        data[0]  = a[0];
        data[1] = a[1];
        data[2] = a[2];
        data[3]  = b[0];
        data[4] = b[1];
        data[5] = b[2];
        data[6]  = c[0];
        data[7] = c[1];
        data[8] = c[2];
    } else {
        eye ();
    }
}

template<class T, uint32_t N>
MatrixN<T, N>::MatrixN (VectorN<T, 4> a, VectorN<T, 4> b, VectorN<T, 4> c, VectorN<T, 4> d) {
    if (N == 4) {
        data[0]  = a[0];
        data[1]  = a[1];
        data[2]  = a[2];
        data[3]  = a[3];
        data[4]  = b[0];
        data[5]  = b[1];
        data[6]  = b[2];
        data[7]  = b[3];
        data[8]  = c[0];
        data[9]  = c[1];
        data[10] = c[2];
        data[11] = c[3];
        data[12] = d[0];
        data[13] = d[1];
        data[14] = d[2];
        data[15] = d[3];
    } else {
        eye ();
    }
}

template<class T, uint32_t N> inline
void MatrixN<T, N>::zeros () {
    for (uint32_t i = 0; i < N * N; i++) {
        data[i] = 0;
    }
}

template<class T, uint32_t N> inline
void MatrixN<T, N>::eye () {
    zeros ();
    for (uint32_t i = 0; i < N; i++) {
        data[i * N + i] = 1;
    }
}

template<class T, uint32_t N> inline
MatrixN<T, N>& MatrixN<T, N>::operator = (const MatrixN<T, N>& rhs) {
    for (uint32_t i = 0; i < N * N; i++) {
        data[i] = rhs.data[i];
    }
    return *this;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator - () const {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N * N; i++) {
        result.data[i] = -data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator + (const MatrixN<T, N>& rhs) const {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N * N; i++) {
        result.data[i] = data[i] + rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator - (const MatrixN<T, N>& rhs) const {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N * N; i++) {
        result.data[i] = data[i] - rhs.data[i];
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator * (const T a) const {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N * N; i++) {
        result.data[i] = data[i] * a;
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator / (const T a) const {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N * N; i++) {
        result.data[i] = data[i] / a;
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::operator * (const MatrixN<T, N>& rhs) const {
    MatrixN<T, N> result;
    result.zeros ();

    for (uint32_t i = 0; i < N; i++) {
        for (uint32_t j = 0; j < N; j++) {
            T element = 0;
            for (uint32_t k = 0; k < N; k++) {
                element += at(i, k) * rhs(k, j);
            }
            result(i, j) = element;
        }
    }
    return result;
}

template<class T, uint32_t N> inline
VectorN<T, N> MatrixN<T, N>::operator * (const VectorN<T, N>& rhs) const {
    VectorN<T, N> result;
    for (uint32_t i = 0; i < N; i++) {  // row
        for (uint32_t j = 0; j < N; j++) {  // col
            result.data[i] = data[i * N + j] * rhs.data[j];
        }
    }
    return result;
}

template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::transpose () {
    MatrixN<T, N> result;
    for (uint32_t i = 0; i < N; i++) {
        for (uint32_t j = 0; j <= N; j++) {
            result.data[i * N + j] = data[j * N + i];
        }
    }
    return result;
}

// if the matrix is non-invertible, return identity matrix
template<class T, uint32_t N> inline
MatrixN<T, N> MatrixN<T, N>::inverse () {
    MatrixN<T, N> result;

    result = inverse (*this);
    return result;
}

template<class T, uint32_t N> inline
T MatrixN<T, N>::trace () {
    T t = 0;
    for ( uint32_t i = 0; i < N; i++ ) {
        t += data(i, i);
    }
    return t;
}

template<class T, uint32_t N> inline
MatrixN<T, 2> MatrixN<T, N>::inverse (const MatrixN<T, 2>& mat)
{
    MatrixN<T, 2> result;

    T det = mat(0, 0) * mat(1, 1) - mat(0, 1) * mat(1, 0);

    if (det == (T)0) {
        return result;
    }

    result(0, 0) = mat(1, 1);
    result(0, 1) = -mat(0, 1);
    result(1, 0) = -mat(1, 0);
    result(1, 1) = mat(0, 0);

    return result * (1.0f / det);
}

template<class T, uint32_t N> inline
MatrixN<T, 3> MatrixN<T, N>::inverse (const MatrixN<T, 3>& mat)
{
    MatrixN<T, 3> result;

    T det = mat(0, 0) * mat(1, 1) * mat(2, 2) +
            mat(1, 0) * mat(2, 1) * mat(0, 2) +
            mat(2, 0) * mat(0, 1) * mat(1, 2) -
            mat(0, 0) * mat(2, 1) * mat(1, 2) -
            mat(1, 0) * mat(0, 1) * mat(2, 2) -
            mat(2, 0) * mat(1, 1) * mat(0, 2);

    if (det == (T)0) {
        return result;
    }

    result(0, 0) = mat(1, 1) * mat(2, 2) - mat(1, 2) * mat(2, 1);
    result(1, 0) = mat(1, 2) * mat(2, 0) - mat(1, 0) * mat(2, 2);
    result(2, 0) = mat(1, 0) * mat(2, 1) - mat(1, 1) * mat(2, 0);
    result(0, 1) = mat(0, 2) * mat(2, 1) - mat(0, 1) * mat(2, 2);
    result(1, 1) = mat(0, 0) * mat(2, 2) - mat(0, 2) * mat(2, 0);
    result(2, 1) = mat(0, 1) * mat(2, 0) - mat(0, 0) * mat(2, 1);
    result(0, 2) = mat(0, 1) * mat(1, 2) - mat(0, 2) * mat(1, 1);
    result(1, 2) = mat(0, 2) * mat(1, 0) - mat(0, 0) * mat(1, 2);
    result(2, 2) = mat(0, 0) * mat(1, 1) - mat(0, 1) * mat(1, 0);

    return result * (1.0f / det);
}

template<class T, uint32_t N> inline
MatrixN<T, 4> MatrixN<T, N>::inverse (const MatrixN<T, 4>& mat)
{
    MatrixN<T, 4> result;

    T det =  mat(0, 3) * mat(1, 2) * mat(2, 1) * mat(3, 1) -
             mat(0, 2) * mat(1, 3) * mat(2, 1) * mat(3, 1) -
             mat(0, 3) * mat(1, 1) * mat(2, 2) * mat(3, 1) +
             mat(0, 1) * mat(1, 3) * mat(2, 2) * mat(3, 1) +
             mat(0, 2) * mat(1, 1) * mat(2, 3) * mat(3, 1) -
             mat(0, 1) * mat(1, 2) * mat(2, 3) * mat(3, 1) -
             mat(0, 3) * mat(1, 2) * mat(2, 0) * mat(3, 1) +
             mat(0, 2) * mat(1, 3) * mat(2, 0) * mat(3, 1) +
             mat(0, 3) * mat(1, 0) * mat(2, 2) * mat(3, 1) -
             mat(0, 0) * mat(1, 3) * mat(2, 2) * mat(3, 1) -
             mat(0, 2) * mat(1, 0) * mat(2, 3) * mat(3, 1) +
             mat(0, 0) * mat(1, 2) * mat(2, 3) * mat(3, 1) +
             mat(0, 3) * mat(1, 1) * mat(2, 0) * mat(3, 2) -
             mat(0, 1) * mat(1, 3) * mat(2, 0) * mat(3, 2) -
             mat(0, 3) * mat(1, 0) * mat(2, 1) * mat(3, 2) +
             mat(0, 0) * mat(1, 3) * mat(2, 1) * mat(3, 2) +
             mat(0, 1) * mat(1, 0) * mat(2, 3) * mat(3, 2) -
             mat(0, 0) * mat(1, 1) * mat(2, 3) * mat(3, 2) -
             mat(0, 2) * mat(1, 1) * mat(2, 0) * mat(3, 3) +
             mat(0, 1) * mat(1, 2) * mat(2, 0) * mat(3, 3) +
             mat(0, 2) * mat(1, 0) * mat(2, 1) * mat(3, 3) -
             mat(0, 0) * mat(1, 2) * mat(2, 1) * mat(3, 3) -
             mat(0, 1) * mat(1, 0) * mat(2, 2) * mat(3, 3) +
             mat(0, 0) * mat(1, 1) * mat(2, 2) * mat(3, 3);

    if (det == (T)0) {
        return result;
    }

    result(0, 0) = mat(1, 2) * mat(2, 3) * mat(3, 1) -
                   mat(1, 3) * mat(2, 2) * mat(3, 1) +
                   mat(1, 3) * mat(2, 1) * mat(3, 2) -
                   mat(1, 1) * mat(2, 3) * mat(3, 2) -
                   mat(1, 2) * mat(2, 1) * mat(3, 3) +
                   mat(1, 1) * mat(2, 2) * mat(3, 3);

    result(0, 1) = mat(0, 3) * mat(2, 2) * mat(3, 1) -
                   mat(0, 2) * mat(2, 3) * mat(3, 1) -
                   mat(0, 3) * mat(2, 1) * mat(3, 2) +
                   mat(0, 1) * mat(2, 3) * mat(3, 2) +
                   mat(0, 2) * mat(2, 1) * mat(3, 3) -
                   mat(0, 1) * mat(2, 2) * mat(3, 3);

    result(0, 2) = mat(0, 2) * mat(1, 3) * mat(3, 1) -
                   mat(0, 3) * mat(1, 2) * mat(3, 1) +
                   mat(0, 3) * mat(1, 1) * mat(3, 2) -
                   mat(0, 1) * mat(1, 3) * mat(3, 2) -
                   mat(0, 2) * mat(1, 1) * mat(3, 3) +
                   mat(0, 1) * mat(1, 2) * mat(3, 3);

    result(0, 3) = mat(0, 3) * mat(1, 2) * mat(2, 1) -
                   mat(0, 2) * mat(1, 3) * mat(2, 1) -
                   mat(0, 3) * mat(1, 1) * mat(2, 2) +
                   mat(0, 1) * mat(1, 3) * mat(2, 2) +
                   mat(0, 2) * mat(1, 1) * mat(2, 3) -
                   mat(0, 1) * mat(1, 2) * mat(2, 3);

    result(1, 0) = mat(1, 3) * mat(2, 2) * mat(3, 0) -
                   mat(1, 2) * mat(2, 3) * mat(3, 0) -
                   mat(1, 3) * mat(2, 0) * mat(3, 2) +
                   mat(1, 0) * mat(2, 3) * mat(3, 2) +
                   mat(1, 2) * mat(2, 0) * mat(3, 3) -
                   mat(1, 0) * mat(2, 2) * mat(3, 3);

    result(1, 1) = mat(0, 2) * mat(2, 3) * mat(3, 0) -
                   mat(0, 3) * mat(2, 2) * mat(3, 0) +
                   mat(0, 3) * mat(2, 0) * mat(3, 2) -
                   mat(0, 0) * mat(2, 3) * mat(3, 2) -
                   mat(0, 2) * mat(2, 0) * mat(3, 3) +
                   mat(0, 0) * mat(2, 2) * mat(3, 3);

    result(1, 2) = mat(0, 3) * mat(1, 2) * mat(3, 0) -
                   mat(0, 2) * mat(1, 3) * mat(3, 0) -
                   mat(0, 3) * mat(1, 0) * mat(3, 2) +
                   mat(0, 0) * mat(1, 3) * mat(3, 2) +
                   mat(0, 2) * mat(1, 0) * mat(3, 3) -
                   mat(0, 0) * mat(1, 2) * mat(3, 3);

    result(1, 3) = mat(0, 2) * mat(1, 3) * mat(2, 0) -
                   mat(0, 3) * mat(1, 2) * mat(2, 0) +
                   mat(0, 3) * mat(1, 0) * mat(2, 2) -
                   mat(0, 0) * mat(1, 3) * mat(2, 2) -
                   mat(0, 2) * mat(1, 0) * mat(2, 3) +
                   mat(0, 0) * mat(1, 2) * mat(2, 3);

    result(2, 0) = mat(1, 1) * mat(2, 3) * mat(3, 0) -
                   mat(1, 3) * mat(2, 1) * mat(3, 0) +
                   mat(1, 3) * mat(2, 0) * mat(3, 1) -
                   mat(1, 0) * mat(2, 3) * mat(3, 1) -
                   mat(1, 1) * mat(2, 0) * mat(3, 3) +
                   mat(1, 0) * mat(2, 1) * mat(3, 3);

    result(2, 1) = mat(0, 3) * mat(2, 1) * mat(3, 0) -
                   mat(0, 1) * mat(2, 3) * mat(3, 0) -
                   mat(0, 3) * mat(2, 0) * mat(3, 1) +
                   mat(0, 0) * mat(2, 3) * mat(3, 1) +
                   mat(0, 1) * mat(2, 0) * mat(3, 3) -
                   mat(0, 0) * mat(2, 1) * mat(3, 3);

    result(2, 2) = mat(0, 1) * mat(1, 3) * mat(3, 0) -
                   mat(0, 3) * mat(1, 1) * mat(3, 0) +
                   mat(0, 3) * mat(1, 0) * mat(3, 1) -
                   mat(0, 0) * mat(1, 3) * mat(3, 1) -
                   mat(0, 1) * mat(1, 0) * mat(3, 3) +
                   mat(0, 0) * mat(1, 1) * mat(3, 3);

    result(2, 3) = mat(0, 3) * mat(1, 1) * mat(2, 0) -
                   mat(0, 1) * mat(1, 3) * mat(2, 0) -
                   mat(0, 3) * mat(1, 0) * mat(2, 1) +
                   mat(0, 0) * mat(1, 3) * mat(2, 1) +
                   mat(0, 1) * mat(1, 0) * mat(2, 3) -
                   mat(0, 0) * mat(1, 1) * mat(2, 3);

    result(3, 0) = mat(1, 2) * mat(2, 1) * mat(3, 0) -
                   mat(1, 1) * mat(2, 2) * mat(3, 0) -
                   mat(1, 2) * mat(2, 0) * mat(3, 1) +
                   mat(1, 0) * mat(2, 2) * mat(3, 1) +
                   mat(1, 1) * mat(2, 0) * mat(3, 2) -
                   mat(1, 0) * mat(2, 1) * mat(3, 2);

    result(3, 1) = mat(1, 1) * mat(2, 2) * mat(3, 0) -
                   mat(1, 2) * mat(2, 1) * mat(3, 0) +
                   mat(1, 2) * mat(2, 0) * mat(3, 1) -
                   mat(1, 0) * mat(2, 2) * mat(3, 1) -
                   mat(1, 1) * mat(2, 0) * mat(3, 2) +
                   mat(1, 0) * mat(2, 1) * mat(3, 2);

    result(3, 2) = mat(0, 2) * mat(1, 1) * mat(3, 0) -
                   mat(0, 1) * mat(1, 2) * mat(3, 0) -
                   mat(0, 2) * mat(1, 0) * mat(3, 1) +
                   mat(0, 0) * mat(1, 2) * mat(3, 1) +
                   mat(0, 1) * mat(1, 0) * mat(3, 2) -
                   mat(0, 0) * mat(1, 1) * mat(3, 2);

    result(3, 3) = mat(0, 1) * mat(1, 2) * mat(2, 0) -
                   mat(0, 2) * mat(1, 1) * mat(2, 0) +
                   mat(0, 2) * mat(1, 0) * mat(2, 1) -
                   mat(0, 0) * mat(1, 2) * mat(2, 1) -
                   mat(0, 1) * mat(1, 0) * mat(2, 2) +
                   mat(0, 0) * mat(1, 1) * mat(2, 2);

    return result * (1.0f / det);
}

typedef VectorN<double, 2> Vec2d;
typedef VectorN<double, 3> Vec3d;
typedef VectorN<double, 4> Vec4d;
typedef MatrixN<double, 2> Mat2d;
typedef MatrixN<double, 3> Mat3d;
typedef MatrixN<double, 4> Mat4d;

typedef VectorN<float, 2> Vec2f;
typedef VectorN<float, 3> Vec3f;
typedef VectorN<float, 4> Vec4f;
typedef MatrixN<float, 3> Mat3f;
typedef MatrixN<float, 4> Mat4f;

template<class T>
class Quaternion
{
public:

    Vec3d v;
    T w;

    Quaternion () : v(0, 0, 0), w(0) {};
    Quaternion (const Quaternion<T>& q) : v(q.v), w(q.w) {};

    Quaternion (const Vec3d& vec, T _w) : v(vec), w(_w) {};
    Quaternion (const Vec4d& vec)  : v(vec[0], vec[1], vec[2]), w(vec[3]) {};
    Quaternion (T _x, T _y, T _z, T _w) : v(_x, _y, _z), w(_w) {};

    inline void reset () {
        v.zeros();
        w = (T) 0;
    }

    inline Quaternion<T>& operator = (const Quaternion<T>& rhs) {
        v = rhs.v;
        w = rhs.w;
        return *this;
    }

    inline Quaternion<T> operator + (const Quaternion<T>& rhs) const {
        const Quaternion<T>& lhs = *this;
        return Quaternion<T>(lhs.v + rhs.v, lhs.w + rhs.w);
    }

    inline Quaternion<T> operator - (const Quaternion<T>& rhs) const {
        const Quaternion<T>& lhs = *this;
        return Quaternion<T>(lhs.v - rhs.v, lhs.w - rhs.w);
    }

    inline Quaternion<T> operator * (T rhs) const {
        return Quaternion<T>(v * rhs, w * rhs);
    }

    inline Quaternion<T> operator * (const Quaternion<T>& rhs) const {
        const Quaternion<T>& lhs = *this;
        return Quaternion<T>(lhs.w * rhs.v[0] + lhs.v[0] * rhs.w + lhs.v[1] * rhs.v[2] - lhs.v[2] * rhs.v[1],
                             lhs.w * rhs.v[1] - lhs.v[0] * rhs.v[2] + lhs.v[1] * rhs.w + lhs.v[2] * rhs.v[0],
                             lhs.w * rhs.v[2] + lhs.v[0] * rhs.v[1] - lhs.v[1] * rhs.v[0] + lhs.v[2] * rhs.w,
                             lhs.w * rhs.w - lhs.v[0] * rhs.v[0] - lhs.v[1] * rhs.v[1] - lhs.v[2] * rhs.v[2]);
    }

    /*
                   --------
                  /    --
        |Qr| =  \/  Qr.Qr
    */
    inline T magnitude () const {
        return (T) sqrtf(w * w + v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    }

    inline void normalize ()
    {
        T length = magnitude ();
        w = w / length;
        v = v / length;
    }

    inline Quaternion<T> conjugate (const Quaternion<T>& quat) const {
        return Quaternion<T>(-quat.v, quat.w);
    }

    inline Quaternion<T> inverse (const Quaternion<T>& quat) const {
        return conjugate(quat) * ( 1.0f / magnitude(quat));
    }

    inline Quaternion<T> lerp (T weight, const Quaternion<T>& quat) const {
        return Quaternion<T>(v.lerp(weight, quat.v), (1 - weight) * w + weight * quat.w);
    }

    inline Quaternion<T> slerp(T r, const Quaternion<T>& quat) const {
        Quaternion<T> ret;
        T cos_theta = w * quat.w + v[0] * quat.v[0] + v[1] * quat.v[1] + v[2] * quat.v[2];
        T theta = (T) acos(cos_theta);
        if (fabs(theta) < FLT_EPSILON)
        {
            ret = *this;
        }
        else
        {
            T sin_theta = (T) sqrt(1.0 - cos_theta * cos_theta);
            if (fabs(sin_theta) < FLT_EPSILON)
            {
                ret.w = 0.5 * w + 0.5 * quat.w;
                ret.v = v.lerp(0.5, quat.v);
            }
            else
            {
                T r0 = (T) sin((1.0 - r) * theta) / sin_theta;
                T r1 = (T) sin(r * theta) / sin_theta;

                ret.w = w * r0 + quat.w * r1;
                ret.v[0] = v[0] * r0 + quat.v[0] * r1;
                ret.v[1] = v[1] * r0 + quat.v[1] * r1;
                ret.v[2] = v[2] * r0 + quat.v[2] * r1;
            }
        }
        return ret;
    }

    static Quaternion<T> create_quaternion (Vec3d axis, T angle_rad) {
        T theta_over_two = angle_rad / (T) 2.0;
        T sin_theta_over_two = std::sin(theta_over_two);
        T cos_theta_over_two = std::cos(theta_over_two);
        return Quaternion<T>(axis * sin_theta_over_two, cos_theta_over_two);
    }

    static Quaternion<T> create_quaternion (Vec3d euler) {
        return create_quaternion(Vec3d(1, 0, 0), euler[0]) *
               create_quaternion(Vec3d(0, 1, 0), euler[1]) *
               create_quaternion(Vec3d(0, 0, 1), euler[2]);
    }

    static Quaternion<T> create_quaternion (const Mat3d& mat) {
        Quaternion<T> q;

        T trace, s;
        T diag1 = mat(0, 0);
        T diag2 = mat(1, 1);
        T diag3 = mat(2, 2);

        trace = diag1 + diag2 + diag3;

        if (trace >= FLT_EPSILON)
        {
            s = 2.0 * (T) sqrt(trace + 1.0);
            q.w = 0.25 * s;
            q.v[0] = (mat(2, 1) - mat(1, 2)) / s;
            q.v[1] = (mat(0, 2) - mat(2, 0)) / s;
            q.v[2] = (mat(1, 0) - mat(0, 1)) / s;
        }
        else
        {
            char max_diag = (diag1 > diag2) ? ((diag1 > diag3) ? 1 : 3) : ((diag2 > diag3) ? 2 : 3);

            if (max_diag == 1)
            {
                s = 2.0 * (T) sqrt(1.0 + mat(0, 0) - mat(1, 1) - mat(2, 2));
                q.w = (mat(2, 1) - mat(1, 2)) / s;
                q.v[0] = 0.25 * s;
                q.v[1] = (mat(0, 1) + mat(1, 0)) / s;
                q.v[2] = (mat(0, 2) + mat(2, 0)) / s;
            }
            else if (max_diag == 2)
            {
                s = 2.0 * (T) sqrt(1.0 + mat(1, 1) - mat(0, 0) - mat(2, 2));
                q.w = (mat(0, 2) - mat(2, 0)) / s;
                q.v[0] = (mat(0, 1) + mat(1, 0)) / s;
                q.v[1] = 0.25 * s;
                q.v[2] = (mat(1, 2) + mat(2, 1)) / s;
            }
            else
            {
                s = 2.0 * (T) sqrt(1.0 + mat(2, 2) - mat(0, 0) - mat(1, 1));
                q.w = (mat(1, 0) - mat(0, 1)) / s;
                q.v[0] = (mat(0, 2) + mat(2, 0)) / s;
                q.v[1] = (mat(1, 2) + mat(2, 1)) / s;
                q.v[2] = 0.25 * s;
            }
        }

        return q;
    }

    inline Vec4d rotation_axis () {
        Vec4d rot_axis;

        T cos_theta_over_two = w;
        rot_axis[4] = (T) std::acos( cos_theta_over_two ) * 2.0f;

        T sin_theta_over_two = (T) sqrt( 1.0 - cos_theta_over_two * cos_theta_over_two );
        if ( fabs( sin_theta_over_two ) < 0.0005 ) sin_theta_over_two = 1;
        rot_axis[0] = v[0] / sin_theta_over_two;
        rot_axis[1] = v[1] / sin_theta_over_two;
        rot_axis[2] = v[2] / sin_theta_over_two;

        return rot_axis;
    }

    /*
        psi=atan2(2.*(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2));
        theta=asin(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)));
        phi=atan2(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2));
    */
    inline Vec3d euler_angles () {
        Vec3d euler;

        // atan2(2*(qx*qw-qy*qz) , qw2-qx2-qy2+qz2)
        euler[0] = atan2(2 * (v[0] * w - v[1] * v[2]),
                         w * w - v[0] * v[0] - v[1] * v[1] + v[2] * v[2]);

        // asin(2*(qx*qz + qy*qw)
        euler[1] = asin(2 * (v[0] * v[2] + v[1] * w));

        // atan2(2*(qz*qw- qx*qy) , qw2 + qx2 - qy2 - qz2)
        euler[2] = atan2(2 * (v[2] * w - v[0] * v[1]),
                         w * w + v[0] * v[0] - v[1] * v[1] - v[2] * v[2]);

        return euler;
    }

    inline Mat3d rotation_matrix () {
        Mat3d mat;

        T xx = v[0] * v[0];
        T xy = v[0] * v[1];
        T xz = v[0] * v[2];
        T xw = v[0] * w;

        T yy = v[1] * v[1];
        T yz = v[1] * v[2];
        T yw = v[1] * w;

        T zz = v[2] * v[2];
        T zw = v[2] * w;

        mat(0, 0) = 1 - 2 * (yy + zz);
        mat(0, 1) = 2 * (xy - zw);
        mat(0, 2) = 2 * (xz + yw);
        mat(1, 0) = 2 * (xy + zw);
        mat(1, 1) = 1 - 2 * (xx + zz);
        mat(1, 2) = 2 * (yz - xw);
        mat(2, 0) = 2 * (xz - yw);
        mat(2, 1) = 2 * (yz + xw);
        mat(2, 2) = 1 - 2 * (xx + yy);

        return mat;
    }
};


typedef Quaternion<double> Quaternd;

}

#endif //XCAM_VECTOR_MATRIX_H
