// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <ostream>

#include "impeller/geometry/vector.h"

namespace impeller {

struct Quaternion {
  union {
    struct {
      Scalar x = 0.0;
      Scalar y = 0.0;
      Scalar z = 0.0;
      Scalar w = 1.0;
    };
    Scalar e[4];
  };

  Quaternion() {}

  Quaternion(Scalar px, Scalar py, Scalar pz, Scalar pw)
      : x(px), y(py), z(pz), w(pw) {}

  Quaternion(const Vector3& axis, Scalar angle) {
    const auto sine = sin(angle * 0.5f);
    x = sine * axis.x;
    y = sine * axis.y;
    z = sine * axis.z;
    w = cos(angle * 0.5f);
  }

  Scalar Dot(const Quaternion& q) const {
    return x * q.x + y * q.y + z * q.z + w * q.w;
  }

  Scalar Length() const { return sqrt(x * x + y * y + z * z + w * w); }

  Quaternion Normalize() const {
    auto m = 1.0f / Length();
    return {x * m, y * m, z * m, w * m};
  }

  Quaternion Invert() const { return {-x, -y, -z, w}; }

  Quaternion Slerp(const Quaternion& to, double time) const;

  Quaternion operator*(const Quaternion& o) const {
    return {
        w * o.x + x * o.w + y * o.z - z * o.y,
        w * o.y + y * o.w + z * o.x - x * o.z,
        w * o.z + z * o.w + x * o.y - y * o.x,
        w * o.w - x * o.x - y * o.y - z * o.z,
    };
  }

  Quaternion operator*(Scalar scale) const {
    return {scale * x, scale * y, scale * z, scale * w};
  }

  Vector3 operator*(Vector3 vector) const {
    Vector3 v(x, y, z);
    return v * v.Dot(vector) * 2 +        //
           vector * (w * w - v.Dot(v)) +  //
           v.Cross(vector) * 2 * w;
  }

  Quaternion operator+(const Quaternion& o) const {
    return {x + o.x, y + o.y, z + o.z, w + o.w};
  }

  Quaternion operator-(const Quaternion& o) const {
    return {x - o.x, y - o.y, z - o.z, w - o.w};
  }

  bool operator==(const Quaternion& o) const {
    return x == o.x && y == o.y && z == o.z && w == o.w;
  }

  bool operator!=(const Quaternion& o) const {
    return x != o.x || y != o.y || z != o.z || w != o.w;
  }
};

}  // namespace impeller

namespace std {

inline std::ostream& operator<<(std::ostream& out,
                                const impeller::Quaternion& q) {
  out << "(" << q.x << ", " << q.y << ", " << q.z << ", " << q.w << ")";
  return out;
}

}  // namespace std
