#ifndef MATRIX4X4_HH
#define MATRIX4X4_HH

#ifdef _WIN32
#define _USE_MATH_DEFINES
#endif
#include <cmath>
#include <cstring>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdouble-promotion"

/*
 * Copyright (c) 2009, Mozilla Corp
 * Copyright (c) 2012, Google, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY <copyright holder> ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <copyright holder> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Based on sample code from the OpenGL(R) ES 2.0 Programming Guide, which carriers
 * the following header:
 *
 * Book:      OpenGL(R) ES 2.0 Programming Guide
 * Authors:   Aaftab Munshi, Dan Ginsburg, Dave Shreiner
 * ISBN-10:   0321502795
 * ISBN-13:   9780321502797
 * Publisher: Addison-Wesley Professional
 * URLs:      http://safari.informit.com/9780321563835
 *            http://www.opengles-book.com
 */

/*
 * Ported from JavaScript to C by Behdad Esfahbod, 2012.
 * Added MultMatrix.  Converting from fixed-function OpenGL matrix
 * operations to these functions should be as simple as renaming the
 * 'gl' prefix to 'm4' and adding the matrix argument to the call.
 *
 * The C version lives at http://code.google.com/p/matrix4x4-c/
 */



/*
 * A simple 4x4 matrix utility implementation
 */


static inline float *
m4LoadIdentity (float *mat) {
  unsigned int i;
  for (i = 0; i < 16; i++)
    mat[i] = 0;
  mat[0*4+0] = 1.0;
  mat[1*4+1] = 1.0;
  mat[2*4+2] = 1.0;
  mat[3*4+3] = 1.0;
  return mat;
}

/* Copies other matrix into mat */
static inline float *
m4Copy (float *mat, const float *other) {
  unsigned int i;
  for (i = 0; i < 16; i++) {
    mat[i] = other[i];
  }
  return mat;
}

static inline float *
m4Multiply (float *mat, const float *right) {
  float tmp[16];
  unsigned int i;

  for (i = 0; i < 4; i++) {
    tmp[i*4+0] =
      (mat[i*4+0] * right[0*4+0]) +
      (mat[i*4+1] * right[1*4+0]) +
      (mat[i*4+2] * right[2*4+0]) +
      (mat[i*4+3] * right[3*4+0]) ;

    tmp[i*4+1] =
      (mat[i*4+0] * right[0*4+1]) +
      (mat[i*4+1] * right[1*4+1]) +
      (mat[i*4+2] * right[2*4+1]) +
      (mat[i*4+3] * right[3*4+1]) ;

    tmp[i*4+2] =
      (mat[i*4+0] * right[0*4+2]) +
      (mat[i*4+1] * right[1*4+2]) +
      (mat[i*4+2] * right[2*4+2]) +
      (mat[i*4+3] * right[3*4+2]) ;

    tmp[i*4+3] =
      (mat[i*4+0] * right[0*4+3]) +
      (mat[i*4+1] * right[1*4+3]) +
      (mat[i*4+2] * right[2*4+3]) +
      (mat[i*4+3] * right[3*4+3]) ;
  }

  return m4Copy (mat, tmp);
}

static inline float
m4Get (float *mat, unsigned int row, unsigned int col) {
  return mat[4*row+col];
}

static inline float *
m4MultMatrix (float *mat, const float *left) {
  float tmp[16];
  return m4Copy (mat, m4Multiply (m4Copy (tmp, left), mat));
}

static inline float *
m4Scale (float *mat, float sx, float sy, float sz) {
  mat[0*4+0] *= sx;
  mat[0*4+1] *= sx;
  mat[0*4+2] *= sx;
  mat[0*4+3] *= sx;

  mat[1*4+0] *= sy;
  mat[1*4+1] *= sy;
  mat[1*4+2] *= sy;
  mat[1*4+3] *= sy;

  mat[2*4+0] *= sz;
  mat[2*4+1] *= sz;
  mat[2*4+2] *= sz;
  mat[2*4+3] *= sz;

  return mat;
}

static inline float *
m4Translate (float *mat, float tx, float ty, float tz) {
  mat[3*4+0] += mat[0*4+0] * tx + mat[1*4+0] * ty + mat[2*4+0] * tz;
  mat[3*4+1] += mat[0*4+1] * tx + mat[1*4+1] * ty + mat[2*4+1] * tz;
  mat[3*4+2] += mat[0*4+2] * tx + mat[1*4+2] * ty + mat[2*4+2] * tz;
  mat[3*4+3] += mat[0*4+3] * tx + mat[1*4+3] * ty + mat[2*4+3] * tz;

  return mat;
}

static inline float *
m4Rotate (float *mat, float angle, float x, float y, float z) {
  float mag = sqrt(x*x + y*y + z*z);
  float sinAngle = sin(angle * M_PI / 180.0);
  float cosAngle = cos(angle * M_PI / 180.0);

  float xx, yy, zz, xy, yz, zx, xs, ys, zs;
  float oneMinusCos;

  float rotMat[16];

  if (mag <= 0)
    return mat;

  m4LoadIdentity (rotMat);

  x /= mag;
  y /= mag;
  z /= mag;

  xx = x * x;
  yy = y * y;
  zz = z * z;
  xy = x * y;
  yz = y * z;
  zx = z * x;
  xs = x * sinAngle;
  ys = y * sinAngle;
  zs = z * sinAngle;
  oneMinusCos = 1.0 - cosAngle;

  rotMat[0*4+0] = (oneMinusCos * xx) + cosAngle;
  rotMat[0*4+1] = (oneMinusCos * xy) - zs;
  rotMat[0*4+2] = (oneMinusCos * zx) + ys;
  rotMat[0*4+3] = 0.0;

  rotMat[1*4+0] = (oneMinusCos * xy) + zs;
  rotMat[1*4+1] = (oneMinusCos * yy) + cosAngle;
  rotMat[1*4+2] = (oneMinusCos * yz) - xs;
  rotMat[1*4+3] = 0.0;

  rotMat[2*4+0] = (oneMinusCos * zx) - ys;
  rotMat[2*4+1] = (oneMinusCos * yz) + xs;
  rotMat[2*4+2] = (oneMinusCos * zz) + cosAngle;
  rotMat[2*4+3] = 0.0;

  rotMat[3*4+0] = 0.0;
  rotMat[3*4+1] = 0.0;
  rotMat[3*4+2] = 0.0;
  rotMat[3*4+3] = 1.0;

  return m4Copy (mat, m4Multiply (rotMat, mat));
}

static inline float *
m4Frustum (float *mat, float left, float right, float bottom, float top, float nearZ, float farZ) {
  float deltaX = right - left;
  float deltaY = top - bottom;
  float deltaZ = farZ - nearZ;

  float frust[16];

  if ( (nearZ <= 0.0) || (farZ <= 0.0) ||
       (deltaX <= 0.0) || (deltaY <= 0.0) || (deltaZ <= 0.0) )
       return mat;

  m4LoadIdentity (frust);

  frust[0*4+0] = 2.0 * nearZ / deltaX;
  frust[0*4+1] = frust[0*4+2] = frust[0*4+3] = 0.0;

  frust[1*4+1] = 2.0 * nearZ / deltaY;
  frust[1*4+0] = frust[1*4+2] = frust[1*4+3] = 0.0;

  frust[2*4+0] = (right + left) / deltaX;
  frust[2*4+1] = (top + bottom) / deltaY;
  frust[2*4+2] = -(nearZ + farZ) / deltaZ;
  frust[2*4+3] = -1.0;

  frust[3*4+2] = -2.0 * nearZ * farZ / deltaZ;
  frust[3*4+0] = frust[3*4+1] = frust[3*4+3] = 0.0;

  return m4Copy (mat, m4Multiply (frust, mat));
}

static inline float *
m4Perspective (float *mat, float fovy, float aspect, float nearZ, float farZ) {
  float frustumH = tan(fovy / 360.0 * M_PI) * nearZ;
  float frustumW = frustumH * aspect;

  return m4Frustum(mat, -frustumW, frustumW, -frustumH, frustumH, nearZ, farZ);
}

static inline float *
m4Ortho (float *mat, float left, float right, float bottom, float top, float nearZ, float farZ) {
  float deltaX = right - left;
  float deltaY = top - bottom;
  float deltaZ = farZ - nearZ;

  float ortho[16];

  if ( (deltaX == 0.0) || (deltaY == 0.0) || (deltaZ == 0.0) )
    return mat;

  m4LoadIdentity (ortho);

  ortho[0*4+0] = 2.0 / deltaX;
  ortho[3*4+0] = -(right + left) / deltaX;
  ortho[1*4+1] = 2.0 / deltaY;
  ortho[3*4+1] = -(top + bottom) / deltaY;
  ortho[2*4+2] = -2.0 / deltaZ;
  ortho[3*4+2] = -(nearZ + farZ) / deltaZ;

  return m4Copy (mat, m4Multiply (ortho, mat));
}

/* In-place inversion */
static inline float *
m4Invert (float *mat) {
  float tmp_0 = m4Get(mat,2,2) * m4Get(mat,3,3);
  float tmp_1 = m4Get(mat,3,2) * m4Get(mat,2,3);
  float tmp_2 = m4Get(mat,1,2) * m4Get(mat,3,3);
  float tmp_3 = m4Get(mat,3,2) * m4Get(mat,1,3);
  float tmp_4 = m4Get(mat,1,2) * m4Get(mat,2,3);
  float tmp_5 = m4Get(mat,2,2) * m4Get(mat,1,3);
  float tmp_6 = m4Get(mat,0,2) * m4Get(mat,3,3);
  float tmp_7 = m4Get(mat,3,2) * m4Get(mat,0,3);
  float tmp_8 = m4Get(mat,0,2) * m4Get(mat,2,3);
  float tmp_9 = m4Get(mat,2,2) * m4Get(mat,0,3);
  float tmp_10 = m4Get(mat,0,2) * m4Get(mat,1,3);
  float tmp_11 = m4Get(mat,1,2) * m4Get(mat,0,3);
  float tmp_12 = m4Get(mat,2,0) * m4Get(mat,3,1);
  float tmp_13 = m4Get(mat,3,0) * m4Get(mat,2,1);
  float tmp_14 = m4Get(mat,1,0) * m4Get(mat,3,1);
  float tmp_15 = m4Get(mat,3,0) * m4Get(mat,1,1);
  float tmp_16 = m4Get(mat,1,0) * m4Get(mat,2,1);
  float tmp_17 = m4Get(mat,2,0) * m4Get(mat,1,1);
  float tmp_18 = m4Get(mat,0,0) * m4Get(mat,3,1);
  float tmp_19 = m4Get(mat,3,0) * m4Get(mat,0,1);
  float tmp_20 = m4Get(mat,0,0) * m4Get(mat,2,1);
  float tmp_21 = m4Get(mat,2,0) * m4Get(mat,0,1);
  float tmp_22 = m4Get(mat,0,0) * m4Get(mat,1,1);
  float tmp_23 = m4Get(mat,1,0) * m4Get(mat,0,1);

  float t0 = ((tmp_0 * m4Get(mat,1,1) + tmp_3 * m4Get(mat,2,1) + tmp_4 * m4Get(mat,3,1)) -
            (tmp_1 * m4Get(mat,1,1) + tmp_2 * m4Get(mat,2,1) + tmp_5 * m4Get(mat,3,1)));
  float t1 = ((tmp_1 * m4Get(mat,0,1) + tmp_6 * m4Get(mat,2,1) + tmp_9 * m4Get(mat,3,1)) -
            (tmp_0 * m4Get(mat,0,1) + tmp_7 * m4Get(mat,2,1) + tmp_8 * m4Get(mat,3,1)));
  float t2 = ((tmp_2 * m4Get(mat,0,1) + tmp_7 * m4Get(mat,1,1) + tmp_10 * m4Get(mat,3,1)) -
            (tmp_3 * m4Get(mat,0,1) + tmp_6 * m4Get(mat,1,1) + tmp_11 * m4Get(mat,3,1)));
  float t3 = ((tmp_5 * m4Get(mat,0,1) + tmp_8 * m4Get(mat,1,1) + tmp_11 * m4Get(mat,2,1)) -
            (tmp_4 * m4Get(mat,0,1) + tmp_9 * m4Get(mat,1,1) + tmp_10 * m4Get(mat,2,1)));

  float d = 1.0 / (m4Get(mat,0,0) * t0 + m4Get(mat,1,0) * t1 + m4Get(mat,2,0) * t2 + m4Get(mat,3,0) * t3);

  float out_00 = d * t0;
  float out_01 = d * t1;
  float out_02 = d * t2;
  float out_03 = d * t3;

  float out_10 = d * ((tmp_1 * m4Get(mat,1,0) + tmp_2 * m4Get(mat,2,0) + tmp_5 * m4Get(mat,3,0)) -
                    (tmp_0 * m4Get(mat,1,0) + tmp_3 * m4Get(mat,2,0) + tmp_4 * m4Get(mat,3,0)));
  float out_11 = d * ((tmp_0 * m4Get(mat,0,0) + tmp_7 * m4Get(mat,2,0) + tmp_8 * m4Get(mat,3,0)) -
                    (tmp_1 * m4Get(mat,0,0) + tmp_6 * m4Get(mat,2,0) + tmp_9 * m4Get(mat,3,0)));
  float out_12 = d * ((tmp_3 * m4Get(mat,0,0) + tmp_6 * m4Get(mat,1,0) + tmp_11 * m4Get(mat,3,0)) -
                    (tmp_2 * m4Get(mat,0,0) + tmp_7 * m4Get(mat,1,0) + tmp_10 * m4Get(mat,3,0)));
  float out_13 = d * ((tmp_4 * m4Get(mat,0,0) + tmp_9 * m4Get(mat,1,0) + tmp_10 * m4Get(mat,2,0)) -
                    (tmp_5 * m4Get(mat,0,0) + tmp_8 * m4Get(mat,1,0) + tmp_11 * m4Get(mat,2,0)));

  float out_20 = d * ((tmp_12 * m4Get(mat,1,3) + tmp_15 * m4Get(mat,2,3) + tmp_16 * m4Get(mat,3,3)) -
                    (tmp_13 * m4Get(mat,1,3) + tmp_14 * m4Get(mat,2,3) + tmp_17 * m4Get(mat,3,3)));
  float out_21 = d * ((tmp_13 * m4Get(mat,0,3) + tmp_18 * m4Get(mat,2,3) + tmp_21 * m4Get(mat,3,3)) -
                    (tmp_12 * m4Get(mat,0,3) + tmp_19 * m4Get(mat,2,3) + tmp_20 * m4Get(mat,3,3)));
  float out_22 = d * ((tmp_14 * m4Get(mat,0,3) + tmp_19 * m4Get(mat,1,3) + tmp_22 * m4Get(mat,3,3)) -
                    (tmp_15 * m4Get(mat,0,3) + tmp_18 * m4Get(mat,1,3) + tmp_23 * m4Get(mat,3,3)));
  float out_23 = d * ((tmp_17 * m4Get(mat,0,3) + tmp_20 * m4Get(mat,1,3) + tmp_23 * m4Get(mat,2,3)) -
                    (tmp_16 * m4Get(mat,0,3) + tmp_21 * m4Get(mat,1,3) + tmp_22 * m4Get(mat,2,3)));

  float out_30 = d * ((tmp_14 * m4Get(mat,2,2) + tmp_17 * m4Get(mat,3,2) + tmp_13 * m4Get(mat,1,2)) -
                    (tmp_16 * m4Get(mat,3,2) + tmp_12 * m4Get(mat,1,2) + tmp_15 * m4Get(mat,2,2)));
  float out_31 = d * ((tmp_20 * m4Get(mat,3,2) + tmp_12 * m4Get(mat,0,2) + tmp_19 * m4Get(mat,2,2)) -
                    (tmp_18 * m4Get(mat,2,2) + tmp_21 * m4Get(mat,3,2) + tmp_13 * m4Get(mat,0,2)));
  float out_32 = d * ((tmp_18 * m4Get(mat,1,2) + tmp_23 * m4Get(mat,3,2) + tmp_15 * m4Get(mat,0,2)) -
                    (tmp_22 * m4Get(mat,3,2) + tmp_14 * m4Get(mat,0,2) + tmp_19 * m4Get(mat,1,2)));
  float out_33 = d * ((tmp_22 * m4Get(mat,2,2) + tmp_16 * m4Get(mat,0,2) + tmp_21 * m4Get(mat,1,2)) -
                    (tmp_20 * m4Get(mat,1,2) + tmp_23 * m4Get(mat,2,2) + tmp_17 * m4Get(mat,0,2)));

  mat[0*4+0] = out_00;
  mat[0*4+1] = out_01;
  mat[0*4+2] = out_02;
  mat[0*4+3] = out_03;
  mat[1*4+0] = out_10;
  mat[1*4+1] = out_11;
  mat[1*4+2] = out_12;
  mat[1*4+3] = out_13;
  mat[2*4+0] = out_20;
  mat[2*4+1] = out_21;
  mat[2*4+2] = out_22;
  mat[2*4+3] = out_23;
  mat[3*4+0] = out_30;
  mat[3*4+1] = out_31;
  mat[3*4+2] = out_32;
  mat[3*4+3] = out_33;
  return mat;
}

/* Puts the inverse of other matrix into mat */
static inline float *
m4Inverse (float *mat, const float *other) {
  m4Copy (mat, other);
  m4Invert (mat);
  return mat;
}

/* In-place transpose */
static inline float *
m4Transpose (float *mat) {
  float tmp = mat[0*4+1];
  mat[0*4+1] = mat[1*4+0];
  mat[1*4+0] = tmp;

  tmp = mat[0*4+2];
  mat[0*4+2] = mat[2*4+0];
  mat[2*4+0] = tmp;

  tmp = mat[0*4+3];
  mat[0*4+3] = mat[3*4+0];
  mat[3*4+0] = tmp;

  tmp = mat[1*4+2];
  mat[1*4+2] = mat[2*4+1];
  mat[2*4+1] = tmp;

  tmp = mat[1*4+3];
  mat[1*4+3] = mat[3*4+1];
  mat[3*4+1] = tmp;

  tmp = mat[2*4+3];
  mat[2*4+3] = mat[3*4+2];
  mat[3*4+2] = tmp;

  return mat;
}

#pragma GCC diagnostic pop

#endif /* MATRIX4X4_HH */
