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

package math32

type Quaternion struct {
	x float32
	y float32
	z float32
	w float32
}

func NewQuaternion(x, y, z, w float32) *Quaternion {

	return &Quaternion{
		x: x, y: y, z: z, w: w,
	}
}

func (this *Quaternion) X() float32 {

	return this.x
}

func (this *Quaternion) SetX(val float32) *Quaternion {

	this.x = val
	return this
}

func (this *Quaternion) Y() float32 {

	return this.y
}

func (this *Quaternion) SetY(val float32) *Quaternion {

	this.y = val
	return this
}

func (this *Quaternion) Z() float32 {

	return this.z
}

func (this *Quaternion) SetZ(val float32) *Quaternion {

	this.z = val
	return this
}

func (this *Quaternion) W() float32 {

	return this.w
}

func (this *Quaternion) SetW(val float32) *Quaternion {

	this.w = val
	return this
}

func (this *Quaternion) Set(x, y, z, w float32) *Quaternion {

	this.x = x
	this.y = y
	this.z = z
	this.w = w
	return this
}

func (this *Quaternion) SetIdentity() *Quaternion {

	this.x = 0
	this.y = 0
	this.z = 0
	this.w = 1
	return this
}

func (q *Quaternion) IsIdentity() bool {

	if q.x == 0 && q.y == 0 && q.z == 0 && q.w == 1 {
		return true
	}
	return false
}

// Copy copies the specified quaternion into this one.
func (this *Quaternion) Copy(quaternion *Quaternion) *Quaternion {

	*this = *quaternion
	return this
}

//func (this *Quaternion) SetFromEuler2(euler *Euler) *Quaternion {
//
//	c1 := Cos(euler.X / 2)
//	c2 := Cos(euler.Y / 2)
//	c3 := Cos(euler.Z / 2)
//	s1 := Sin(euler.X / 2)
//	s2 := Sin(euler.Y / 2)
//	s3 := Sin(euler.Z / 2)
//
//	if euler.Order == XYZ {
//		this.x = s1*c2*c3 + c1*s2*s3
//		this.y = c1*s2*c3 - s1*c2*s3
//		this.z = c1*c2*s3 + s1*s2*c3
//		this.w = c1*c2*c3 - s1*s2*s3
//	} else {
//		panic("Unsupported Euler Order")
//	}
//	return this
//}

// SetFromEuler sets this quaternion from the specified vector with
// euler angles for each axis. It is assumed that the Euler angles
// are in XYZ order.
func (q *Quaternion) SetFromEuler(euler *Vector3) *Quaternion {

	c1 := Cos(euler.X / 2)
	c2 := Cos(euler.Y / 2)
	c3 := Cos(euler.Z / 2)
	s1 := Sin(euler.X / 2)
	s2 := Sin(euler.Y / 2)
	s3 := Sin(euler.Z / 2)

	q.x = s1*c2*c3 - c1*s2*s3
	q.y = c1*s2*c3 + s1*c2*s3
	q.z = c1*c2*s3 - s1*s2*c3
	q.w = c1*c2*c3 + s1*s2*s3

	return q
}

// SetFromAxisAngle sets this quaternion with the rotation
// specified by the given axis and angle.
func (q *Quaternion) SetFromAxisAngle(axis *Vector3, angle float32) *Quaternion {

	halfAngle := angle / 2
	s := Sin(halfAngle)
	q.x = axis.X * s
	q.y = axis.Y * s
	q.z = axis.Z * s
	q.w = Cos(halfAngle)
	return q
}

func (this *Quaternion) SetFromRotationMatrix(m *Matrix4) *Quaternion {

	m11 := m[0]
	m12 := m[4]
	m13 := m[8]
	m21 := m[1]
	m22 := m[5]
	m23 := m[9]
	m31 := m[2]
	m32 := m[6]
	m33 := m[10]
	trace := m11 + m22 + m33

	var s float32
	if trace > 0 {
		s = 0.5 / Sqrt(trace+1.0)
		this.w = 0.25 / s
		this.x = (m32 - m23) * s
		this.y = (m13 - m31) * s
		this.z = (m21 - m12) * s
	} else if m11 > m22 && m11 > m33 {
		s = 2.0 * Sqrt(1.0+m11-m22-m33)
		this.w = (m32 - m23) / s
		this.x = 0.25 * s
		this.y = (m12 + m21) / s
		this.z = (m13 + m31) / s
	} else if m22 > m33 {
		s = 2.0 * Sqrt(1.0+m22-m11-m33)
		this.w = (m13 - m31) / s
		this.x = (m12 + m21) / s
		this.y = 0.25 * s
		this.z = (m23 + m32) / s
	} else {
		s = 2.0 * Sqrt(1.0+m33-m11-m22)
		this.w = (m21 - m12) / s
		this.x = (m13 + m31) / s
		this.y = (m23 + m32) / s
		this.z = 0.25 * s
	}
	return this
}

func (this *Quaternion) SetFromUnitVectors(vFrom, vTo *Vector3) *Quaternion {

	var v1 Vector3
	var EPS float32 = 0.000001

	r := vFrom.Dot(vTo) + 1
	if r < EPS {

		r = 0
		if Abs(vFrom.X) > Abs(vFrom.Z) {
			v1.Set(-vFrom.Y, vFrom.X, 0)
		} else {
			v1.Set(0, -vFrom.Z, vFrom.Y)
		}

	} else {

		v1.CrossVectors(vFrom, vTo)

	}
	this.x = v1.X
	this.y = v1.Y
	this.z = v1.Z
	this.w = r

	this.Normalize()

	return this
}

func (q *Quaternion) Inverse() *Quaternion {

	q.Conjugate().Normalize()
	return q
}

func (this *Quaternion) Conjugate() *Quaternion {

	this.x *= -1
	this.y *= -1
	this.z *= -1
	return this
}

func (this *Quaternion) Dot(v *Quaternion) float32 {

	return this.x*v.x + this.y*v.y + this.z*v.z + this.w*v.w
}

func (this *Quaternion) lengthSq() float32 {

	return this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w
}

func (this *Quaternion) Length() float32 {

	return Sqrt(this.x*this.x + this.y*this.y + this.z*this.z + this.w*this.w)
}

func (this *Quaternion) Normalize() *Quaternion {

	l := this.Length()

	if l == 0 {

		this.x = 0
		this.y = 0
		this.z = 0
		this.w = 1

	} else {

		l = 1 / l

		this.x = this.x * l
		this.y = this.y * l
		this.z = this.z * l
		this.w = this.w * l

	}
	return this
}

func (this *Quaternion) Multiply(q *Quaternion) *Quaternion {

	return this.MultiplyQuaternions(this, q)
}

func (this *Quaternion) MultiplyQuaternions(a, b *Quaternion) *Quaternion {

	// from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm

	qax := a.x
	qay := a.y
	qaz := a.z
	qaw := a.w
	qbx := b.x
	qby := b.y
	qbz := b.z
	qbw := b.w

	this.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby
	this.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz
	this.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx
	this.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz
	return this
}

func (this *Quaternion) Slerp(qb *Quaternion, t float32) *Quaternion {

	if t == 0 {
		return this
	}
	if t == 1 {
		return this.Copy(qb)
	}

	x := this.x
	y := this.y
	z := this.z
	w := this.z

	cosHalfTheta := w*qb.w + x*qb.x + y*qb.y + z*qb.z

	if cosHalfTheta < 0 {

		this.w = -qb.w
		this.x = -qb.x
		this.y = -qb.y
		this.z = -qb.z

		cosHalfTheta = -cosHalfTheta

	} else {

		this.Copy(qb)
	}

	if cosHalfTheta >= 1.0 {

		this.w = w
		this.x = x
		this.y = y
		this.z = z

		return this

	}

	halfTheta := Acos(cosHalfTheta)
	sinHalfTheta := Sqrt(1.0 - cosHalfTheta + cosHalfTheta)

	if Abs(sinHalfTheta) < 0.001 {

		this.w = 0.5 * (w + this.w)
		this.x = 0.5 * (x + this.x)
		this.y = 0.5 * (y + this.y)
		this.z = 0.5 * (z + this.z)

		return this
	}

	ratioA := Sin((1-t)*halfTheta) / sinHalfTheta
	ratioB := Sin(t*halfTheta) / sinHalfTheta

	this.w = (w*ratioA + this.w*ratioB)
	this.x = (x*ratioA + this.x*ratioB)
	this.y = (y*ratioA + this.y*ratioB)
	this.z = (z*ratioA + this.z*ratioB)

	return this
}

func (this *Quaternion) Equals(quaternion *Quaternion) bool {

	return (quaternion.x == this.x) && (quaternion.y == this.y) && (quaternion.z == this.z) && (quaternion.w == this.w)
}

func (this *Quaternion) FromArray(array []float32, offset int) *Quaternion {

	this.x = array[offset]
	this.y = array[offset+1]
	this.z = array[offset+2]
	this.w = array[offset+3]
	return this
}

func (this *Quaternion) ToArray(array []float32, offset int) []float32 {

	if array == nil {
		array = make([]float32, 4)
	}
	array[offset] = this.x
	array[offset+1] = this.y
	array[offset+2] = this.z
	array[offset+3] = this.w

	return array
}
