// Begin License:
// Copyright (C) 2006-2008 Tobias Sargeant (tobias.sargeant@gmail.com).
// All rights reserved.
//
// This file is part of the Carve CSG Library (http://carve-csg.com/)
//
// This file may be used under the terms of the GNU General Public
// License version 2.0 as published by the Free Software Foundation
// and appearing in the file LICENSE.GPL2 included in the packaging of
// this file.
//
// This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
// INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE.
// End:


#pragma once

#include <cstring>

#include <carve/carve.hpp>

#include <carve/math.hpp>
#include <carve/vector.hpp>

namespace carve {
  namespace math {

    struct Quaternion {
      double x, y, z, w;

      Quaternion(double _x, double _y, double _z, double _w) : x(_x), y(_y), z(_z), w(_w) {
      }

      Quaternion(double angle, const carve::geom3d::Vector &axis) {
        double s = axis.length();
        if (!carve::math::ZERO(s)) {
          double c = 1.0 / s;
          double omega = -0.5 * angle;
          s = sin(omega);
          x = axis.x * c * s;
          y = axis.y * c * s;
          z = axis.z * c * s;
          w = cos(omega);
          normalize();
        } else {
          x = y = z = 0.0;
          w = 1.0;
        }
      }

      double lengthSquared() const {
        return x * x + y * y + z * z + w * w;
      }

      double length() const {
        return sqrt(lengthSquared());
      }

      Quaternion normalized() const {
        return Quaternion(*this).normalize();
      }

      Quaternion &normalize() {
        double l = length();
        if (l == 0.0) {
          x = 1.0; y = 0.0; z = 0.0; w = 0.0;
        } else {
          x /= l; y /= l; z /= l; w /= l;
        }
        return *this;
      }
    };

    struct Matrix3 {
      // access: .m[col][row], .v[col * 4 + row], ._cr
      union {
        double m[3][3];
        double v[9];
        struct {
          // transposed
          double _11, _12, _13;
          double _21, _22, _23;
          double _31, _32, _33;
        };
      };
      Matrix3(double __11, double __21, double __31,
              double __12, double __22, double __32,
              double __13, double __23, double __33) {
        // nb, args are row major, storage is column major.
        _11 = __11; _12 = __12; _13 = __13;
        _21 = __21; _22 = __22; _23 = __23;
        _31 = __31; _32 = __32; _33 = __33;
      }
      Matrix3(double _m[3][3]) {
        std::memcpy(m, _m, sizeof(m));
      }
      Matrix3(double _v[9]) {
        std::memcpy(v, _v, sizeof(v));
      }
      Matrix3() {
        _11 = 1.00; _12 = 0.00; _13 = 0.00;
        _21 = 0.00; _22 = 1.00; _23 = 0.00;
        _31 = 0.00; _32 = 0.00; _33 = 1.00;
      }
    };

    struct Matrix {
      // access: .m[col][row], .v[col * 4 + row], ._cr
      union {
        double m[4][4];
        double v[16];
        struct {
          // transposed
          double _11, _12, _13, _14;
          double _21, _22, _23, _24;
          double _31, _32, _33, _34;
          double _41, _42 ,_43, _44;
        };
      };
      Matrix(double __11, double __21, double __31, double __41,
             double __12, double __22, double __32, double __42,
             double __13, double __23, double __33, double __43,
             double __14, double __24, double __34, double __44) {
        // nb, args are row major, storage is column major.
        _11 = __11; _12 = __12; _13 = __13; _14 = __14;
        _21 = __21; _22 = __22; _23 = __23; _24 = __24;
        _31 = __31; _32 = __32; _33 = __33; _34 = __34;
        _41 = __41; _42 = __42; _43 = __43; _44 = __44;
      }
      Matrix(double _m[4][4]) {
        std::memcpy(m, _m, sizeof(m));
      }
      Matrix(double _v[16]) {
        std::memcpy(v, _v, sizeof(v));
      }
      Matrix() {
        _11 = 1.00; _12 = 0.00; _13 = 0.00; _14 = 0.00;
        _21 = 0.00; _22 = 1.00; _23 = 0.00; _24 = 0.00;
        _31 = 0.00; _32 = 0.00; _33 = 1.00; _34 = 0.00;
        _41 = 0.00; _42 = 0.00; _43 = 0.00; _44 = 1.00;
      }

      static Matrix ROT(const Quaternion &q) {
        const double w = q.w;
        const double x = q.x;
        const double y = q.y;
        const double z = q.z;
        return Matrix(1 - 2*y*y - 2*z*z,   2*x*y - 2*z*w,       2*x*z + 2*y*w,       0.0,  
                      2*x*y + 2*z*w,       1 - 2*x*x - 2*z*z,   2*y*z - 2*x*w,       0.0,  
                      2*x*z - 2*y*w,       2*y*z + 2*x*w,       1 - 2*x*x - 2*y*y,   0.0,  
                      0.0,                 0.0,                 0.0,                 1.0);
      }
      static Matrix ROT(double angle, const carve::geom3d::Vector &axis) {
        return ROT(Quaternion(angle, axis));
      }
      static Matrix ROT(double angle, double x, double y, double z) {
        return ROT(Quaternion(angle, carve::geom::VECTOR(x, y, z)));
      }
      static Matrix TRANS(double x, double y, double z) {
        return Matrix(1.0,   0.0,   0.0,   x,
                      0.0,   1.0,   0.0,   y,
                      0.0,   0.0,   1.0,   z,
                      0.0,   0.0,   0.0,   1.0);
      }
      static Matrix TRANS(const carve::geom3d::Vector &v) {
        return TRANS(v.x, v.y, v.z);
      }
      static Matrix SCALE(double x, double y, double z) {
        return Matrix(x,     0.0,   0.0,   0.0,
                      0.0,   y,     0.0,   0.0,
                      0.0,   0.0,   z,     0.0,
                      0.0,   0.0,   0.0,   1.0);
      }
      static Matrix SCALE(const carve::geom3d::Vector &v) {
        return SCALE(v.x, v.y, v.z);
      }
      static Matrix IDENT() {
        return Matrix(1.0,   0.0,   0.0,   0.0,
                      0.0,   1.0,   0.0,   0.0,
                      0.0,   0.0,   1.0,   0.0,
                      0.0,   0.0,   0.0,   1.0);
      }
    };

    static inline bool operator==(const Matrix &A, const Matrix &B) {
      for (size_t i = 0; i < 16; ++i) if (A.v[i] != B.v[i]) return false;
      return true;
    }
    static inline bool operator!=(const Matrix &A, const Matrix &B) {
      return !(A == B);
    }
    static inline carve::geom3d::Vector operator*(const Matrix &A, const carve::geom3d::Vector &b) {
      return carve::geom::VECTOR(
                    A._11 * b.x + A._21 * b.y + A._31 * b.z + A._41,
                    A._12 * b.x + A._22 * b.y + A._32 * b.z + A._42,
                    A._13 * b.x + A._23 * b.y + A._33 * b.z + A._43
                    );
    }

    static inline carve::geom3d::Vector &operator*=(carve::geom3d::Vector &b, const Matrix &A) {
      b = A * b;
      return b;
    }

    static inline carve::geom3d::Vector operator*(const Matrix3 &A, const carve::geom3d::Vector &b) {
      return carve::geom::VECTOR(
                    A._11 * b.x + A._21 * b.y + A._31 * b.z,
                    A._12 * b.x + A._22 * b.y + A._32 * b.z,
                    A._13 * b.x + A._23 * b.y + A._33 * b.z
                    );
    }

    static inline carve::geom3d::Vector &operator*=(carve::geom3d::Vector &b, const Matrix3 &A) {
      b = A * b;
      return b;
    }

    static inline Matrix operator*(const Matrix &A, const Matrix &B) {
      Matrix c;
      for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
          c.m[i][j] = 0.0;
          for (int k = 0; k < 4; k++) {
            c.m[i][j] += A.m[k][j] * B.m[i][k];
          }
        }
      }
      return c;
    }

    static inline Matrix3 operator*(const Matrix3 &A, const Matrix3 &B) {
      Matrix3 c;
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
          c.m[i][j] = 0.0;
          for (int k = 0; k < 3; k++) {
            c.m[i][j] += A.m[k][j] * B.m[i][k];
          }
        }
      }
      return c;
    }

  }
}
