/*
 * Copyright 2013 The Android Open Source Project
 *
 * 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.
 */

#ifndef UI_MAT4_H
#define UI_MAT4_H

#include <stdint.h>
#include <sys/types.h>

#include <ui/vec4.h>
#include <utils/String8.h>

#define TMAT_IMPLEMENTATION
#include <ui/TMatHelpers.h>

#define PURE __attribute__((pure))

namespace android {
// -------------------------------------------------------------------------------------

template <typename T>
class tmat44 :  public TVecUnaryOperators<tmat44, T>,
                public TVecComparisonOperators<tmat44, T>,
                public TVecAddOperators<tmat44, T>,
                public TMatProductOperators<tmat44, T>,
                public TMatSquareFunctions<tmat44, T>,
                public TMatDebug<tmat44, T>
{
public:
    enum no_init { NO_INIT };
    typedef T value_type;
    typedef T& reference;
    typedef T const& const_reference;
    typedef size_t size_type;
    typedef tvec4<T> col_type;
    typedef tvec4<T> row_type;

    // size of a column (i.e.: number of rows)
    enum { COL_SIZE = col_type::SIZE };
    static inline size_t col_size() { return COL_SIZE; }

    // size of a row (i.e.: number of columns)
    enum { ROW_SIZE = row_type::SIZE };
    static inline size_t row_size() { return ROW_SIZE; }
    static inline size_t size()     { return row_size(); }  // for TVec*<>

private:

    /*
     *  <--  N columns  -->
     *
     *  a00 a10 a20 ... aN0    ^
     *  a01 a11 a21 ... aN1    |
     *  a02 a12 a22 ... aN2  M rows
     *  ...                    |
     *  a0M a1M a2M ... aNM    v
     *
     *  COL_SIZE = M
     *  ROW_SIZE = N
     *  m[0] = [a00 a01 a02 ... a01M]
     */

    col_type mValue[ROW_SIZE];

public:
    // array access
    inline col_type const& operator [] (size_t i) const { return mValue[i]; }
    inline col_type&       operator [] (size_t i)       { return mValue[i]; }

    T const* asArray() const { return &mValue[0][0]; }

    // -----------------------------------------------------------------------
    // we don't provide copy-ctor and operator= on purpose
    // because we want the compiler generated versions

    /*
     *  constructors
     */

    // leaves object uninitialized. use with caution.
    explicit tmat44(no_init) { }

    // initialize to identity
    tmat44();

    // initialize to Identity*scalar.
    template<typename U>
    explicit tmat44(U v);

    // sets the diagonal to the passed vector
    template <typename U>
    explicit tmat44(const tvec4<U>& rhs);

    // construct from another matrix of the same size
    template <typename U>
    explicit tmat44(const tmat44<U>& rhs);

    // construct from 4 column vectors
    template <typename A, typename B, typename C, typename D>
    tmat44(const tvec4<A>& v0, const tvec4<B>& v1, const tvec4<C>& v2, const tvec4<D>& v3);

    // construct from 16 scalars
    template <
        typename A, typename B, typename C, typename D,
        typename E, typename F, typename G, typename H,
        typename I, typename J, typename K, typename L,
        typename M, typename N, typename O, typename P>
    tmat44( A m00, B m01, C m02, D m03,
            E m10, F m11, G m12, H m13,
            I m20, J m21, K m22, L m23,
            M m30, N m31, O m32, P m33);

    // construct from a C array
    template <typename U>
    explicit tmat44(U const* rawArray);

    /*
     *  helpers
     */

    static tmat44 ortho(T left, T right, T bottom, T top, T near, T far);

    static tmat44 frustum(T left, T right, T bottom, T top, T near, T far);

    template <typename A, typename B, typename C>
    static tmat44 lookAt(const tvec3<A>& eye, const tvec3<B>& center, const tvec3<C>& up);

    template <typename A>
    static tmat44 translate(const tvec4<A>& t);

    template <typename A>
    static tmat44 scale(const tvec4<A>& s);

    template <typename A, typename B>
    static tmat44 rotate(A radian, const tvec3<B>& about);
};

// ----------------------------------------------------------------------------------------
// Constructors
// ----------------------------------------------------------------------------------------

/*
 * Since the matrix code could become pretty big quickly, we don't inline most
 * operations.
 */

template <typename T>
tmat44<T>::tmat44() {
    mValue[0] = col_type(1,0,0,0);
    mValue[1] = col_type(0,1,0,0);
    mValue[2] = col_type(0,0,1,0);
    mValue[3] = col_type(0,0,0,1);
}

template <typename T>
template <typename U>
tmat44<T>::tmat44(U v) {
    mValue[0] = col_type(v,0,0,0);
    mValue[1] = col_type(0,v,0,0);
    mValue[2] = col_type(0,0,v,0);
    mValue[3] = col_type(0,0,0,v);
}

template<typename T>
template<typename U>
tmat44<T>::tmat44(const tvec4<U>& v) {
    mValue[0] = col_type(v.x,0,0,0);
    mValue[1] = col_type(0,v.y,0,0);
    mValue[2] = col_type(0,0,v.z,0);
    mValue[3] = col_type(0,0,0,v.w);
}

// construct from 16 scalars
template<typename T>
template <
    typename A, typename B, typename C, typename D,
    typename E, typename F, typename G, typename H,
    typename I, typename J, typename K, typename L,
    typename M, typename N, typename O, typename P>
tmat44<T>::tmat44(  A m00, B m01, C m02, D m03,
                    E m10, F m11, G m12, H m13,
                    I m20, J m21, K m22, L m23,
                    M m30, N m31, O m32, P m33) {
    mValue[0] = col_type(m00, m01, m02, m03);
    mValue[1] = col_type(m10, m11, m12, m13);
    mValue[2] = col_type(m20, m21, m22, m23);
    mValue[3] = col_type(m30, m31, m32, m33);
}

template <typename T>
template <typename U>
tmat44<T>::tmat44(const tmat44<U>& rhs) {
    for (size_t r=0 ; r<row_size() ; r++)
        mValue[r] = rhs[r];
}

template <typename T>
template <typename A, typename B, typename C, typename D>
tmat44<T>::tmat44(const tvec4<A>& v0, const tvec4<B>& v1, const tvec4<C>& v2, const tvec4<D>& v3) {
    mValue[0] = v0;
    mValue[1] = v1;
    mValue[2] = v2;
    mValue[3] = v3;
}

template <typename T>
template <typename U>
tmat44<T>::tmat44(U const* rawArray) {
    for (size_t r=0 ; r<row_size() ; r++)
        for (size_t c=0 ; c<col_size() ; c++)
            mValue[r][c] = *rawArray++;
}

// ----------------------------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------------------------

template <typename T>
tmat44<T> tmat44<T>::ortho(T left, T right, T bottom, T top, T near, T far) {
    tmat44<T> m;
    m[0][0] =  2 / (right - left);
    m[1][1] =  2 / (top   - bottom);
    m[2][2] = -2 / (far   - near);
    m[3][0] = -(right + left)   / (right - left);
    m[3][1] = -(top   + bottom) / (top   - bottom);
    m[3][2] = -(far   + near)   / (far   - near);
    return m;
}

template <typename T>
tmat44<T> tmat44<T>::frustum(T left, T right, T bottom, T top, T near, T far) {
    tmat44<T> m;
    T A = (right + left)   / (right - left);
    T B = (top   + bottom) / (top   - bottom);
    T C = (far   + near)   / (far   - near);
    T D = (2 * far * near) / (far   - near);
    m[0][0] = (2 * near) / (right - left);
    m[1][1] = (2 * near) / (top   - bottom);
    m[2][0] = A;
    m[2][1] = B;
    m[2][2] = C;
    m[2][3] =-1;
    m[3][2] = D;
    m[3][3] = 0;
    return m;
}

template <typename T>
template <typename A, typename B, typename C>
tmat44<T> tmat44<T>::lookAt(const tvec3<A>& eye, const tvec3<B>& center, const tvec3<C>& up) {
    tvec3<T> L(normalize(center - eye));
    tvec3<T> S(normalize( cross(L, up) ));
    tvec3<T> U(cross(S, L));
    return tmat44<T>(
            tvec4<T>( S, 0),
            tvec4<T>( U, 0),
            tvec4<T>(-L, 0),
            tvec4<T>(-eye, 1));
}

template <typename T>
template <typename A>
tmat44<T> tmat44<T>::translate(const tvec4<A>& t) {
    tmat44<T> r;
    r[3] = t;
    return r;
}

template <typename T>
template <typename A>
tmat44<T> tmat44<T>::scale(const tvec4<A>& s) {
    tmat44<T> r;
    r[0][0] = s[0];
    r[1][1] = s[1];
    r[2][2] = s[2];
    r[3][3] = s[3];
    return r;
}

template <typename T>
template <typename A, typename B>
tmat44<T> tmat44<T>::rotate(A radian, const tvec3<B>& about) {
    tmat44<T> rotation;
    T* r = const_cast<T*>(rotation.asArray());
    T c = cos(radian);
    T s = sin(radian);
    if (about.x==1 && about.y==0 && about.z==0) {
        r[5] = c;   r[10]= c;
        r[6] = s;   r[9] = -s;
    } else if (about.x==0 && about.y==1 && about.z==0) {
        r[0] = c;   r[10]= c;
        r[8] = s;   r[2] = -s;
    } else if (about.x==0 && about.y==0 && about.z==1) {
        r[0] = c;   r[5] = c;
        r[1] = s;   r[4] = -s;
    } else {
        tvec3<B> nabout = normalize(about);
        B x = nabout.x;
        B y = nabout.y;
        B z = nabout.z;
        T nc = 1 - c;
        T xy = x * y;
        T yz = y * z;
        T zx = z * x;
        T xs = x * s;
        T ys = y * s;
        T zs = z * s;
        r[ 0] = x*x*nc +  c;    r[ 4] =  xy*nc - zs;    r[ 8] =  zx*nc + ys;
        r[ 1] =  xy*nc + zs;    r[ 5] = y*y*nc +  c;    r[ 9] =  yz*nc - xs;
        r[ 2] =  zx*nc - ys;    r[ 6] =  yz*nc + xs;    r[10] = z*z*nc +  c;
    }
}

// ----------------------------------------------------------------------------------------
// Arithmetic operators outside of class
// ----------------------------------------------------------------------------------------

/* We use non-friend functions here to prevent the compiler from using
 * implicit conversions, for instance of a scalar to a vector. The result would
 * not be what the caller expects.
 *
 * Also note that the order of the arguments in the inner loop is important since
 * it determines the output type (only relevant when T != U).
 */

// matrix * vector, result is a vector of the same type than the input vector
template <typename T, typename U>
typename tmat44<U>::col_type PURE operator *(const tmat44<T>& lv, const tvec4<U>& rv) {
    typename tmat44<U>::col_type result;
    for (size_t r=0 ; r<tmat44<T>::row_size() ; r++)
        result += rv[r]*lv[r];
    return result;
}

// vector * matrix, result is a vector of the same type than the input vector
template <typename T, typename U>
typename tmat44<U>::row_type PURE operator *(const tvec4<U>& rv, const tmat44<T>& lv) {
    typename tmat44<U>::row_type result(tmat44<U>::row_type::NO_INIT);
    for (size_t r=0 ; r<tmat44<T>::row_size() ; r++)
        result[r] = dot(rv, lv[r]);
    return result;
}

// matrix * scalar, result is a matrix of the same type than the input matrix
template <typename T, typename U>
tmat44<T> PURE operator *(const tmat44<T>& lv, U rv) {
    tmat44<T> result(tmat44<T>::NO_INIT);
    for (size_t r=0 ; r<tmat44<T>::row_size() ; r++)
        result[r] = lv[r]*rv;
    return result;
}

// scalar * matrix, result is a matrix of the same type than the input matrix
template <typename T, typename U>
tmat44<T> PURE operator *(U rv, const tmat44<T>& lv) {
    tmat44<T> result(tmat44<T>::NO_INIT);
    for (size_t r=0 ; r<tmat44<T>::row_size() ; r++)
        result[r] = lv[r]*rv;
    return result;
}

// ----------------------------------------------------------------------------------------

/* FIXME: this should go into TMatSquareFunctions<> but for some reason
 * BASE<T>::col_type is not accessible from there (???)
 */
template<typename T>
typename tmat44<T>::col_type PURE diag(const tmat44<T>& m) {
    return matrix::diag(m);
}

// ----------------------------------------------------------------------------------------

typedef tmat44<float> mat4;

// ----------------------------------------------------------------------------------------
}; // namespace android

#undef PURE

#endif /* UI_MAT4_H */
