package ocelot.desktop.geometry

import java.nio.ByteBuffer

object Transform3D {
  def apply(basis: Basis3D): Transform3D = Transform3D(basis, Vector3D.Zero)

  def identity: Transform3D = Transform3D(Basis3D.identity)

  def rotate(q: Quaternion): Transform3D = Transform3D(Basis3D.rotate(q))

  def rotate(axis: Vector3D, angle: Float): Transform3D = Transform3D(Basis3D.rotate(axis, angle))

  def scale(v: Vector3D): Transform3D = Transform3D(Basis3D.scale(v))

  def scale(s: Float): Transform3D = Transform3D(Basis3D.scale(s))

  def translate(v: Vector3D): Transform3D = Transform3D(Basis3D.identity, v)

  def translate(x: Float, y: Float, z: Float): Transform3D = Transform3D(Basis3D.identity, Vector3D(x, y, z))

  def lookingAt(origin: Vector3D, target: Vector3D, up: Vector3D = Vector3D.Up): Transform3D = {
    val basis = Basis3D.lookingAt(origin - target, up)
    Transform3D(basis, origin)
  }
}

case class Transform3D(basis: Basis3D, origin: Vector3D) {
  // format: off
  def array: Array[Float] = Array(
    basis.x.x, basis.y.x, basis.z.x, origin.x,
    basis.x.y, basis.y.y, basis.z.y, origin.y,
    basis.x.z, basis.y.z, basis.z.z, origin.z,
  )

  def *(rhs: Vector3D): Vector3D = basis * rhs + origin

  def *(rhs: Transform3D): Transform3D = Transform3D(basis * rhs.basis, this * rhs.origin)

  def inverse: Transform3D = {
    val b = basis.inverse
    Transform3D(b, b * (-origin))
  }

  override def toString: String = s"Transform3D [${basis.x}, ${basis.y}, ${basis.z}, $origin]"

  def put(buffer: ByteBuffer): Unit = {
    buffer.putFloat(basis.x.x); buffer.putFloat(basis.y.x); buffer.putFloat(basis.z.x); buffer.putFloat(origin.x)
    buffer.putFloat(basis.x.y); buffer.putFloat(basis.y.y); buffer.putFloat(basis.z.y); buffer.putFloat(origin.y)
    buffer.putFloat(basis.x.z); buffer.putFloat(basis.y.z); buffer.putFloat(basis.z.z); buffer.putFloat(origin.z)
  }
}
