import { AABB } from './AABB'
import { Ray } from './Ray'
import { Vec3 } from '../math/Vec3'
import { Quaternion } from '../math/Quaternion'
import { Transform } from '../math/Transform'

describe('AABB', () => {
  test('construct', () => {
    const a = new AABB()
    expect(a).toBeDefined()
  })

  test('setFromPoints', () => {
    const a = new AABB()
    const points = [
      new Vec3(7, 0, 1),
      new Vec3(2, -1, 5),
      new Vec3(-1, -7, 0),
      new Vec3(4, 5, -9),
      new Vec3(-5, 8, 2),
      new Vec3(-3, -2, -1),
      new Vec3(5, 3, 6),
    ]

    expect(a.setFromPoints(points)).toBe(a)
    expect(a).toMatchObject(
      new AABB({
        lowerBound: new Vec3(-5, -7, -9),
        upperBound: new Vec3(7, 8, 6),
      })
    )

    const position = new Vec3(1, 2, 3)
    a.setFromPoints(points, position)
    expect(a).toMatchObject(
      new AABB({
        lowerBound: new Vec3(-4, -5, -6),
        upperBound: new Vec3(8, 10, 9),
      })
    )

    const quaternion = new Quaternion().setFromAxisAngle(new Vec3(1, 0, 0), Math.PI)
    a.setFromPoints(points, position, quaternion)
    expect(a).toMatchObject(
      new AABB({
        lowerBound: new Vec3(-4, -6, -3),
        upperBound: new Vec3(8, 9, 12),
      })
    )

    const skinSize = 1
    a.setFromPoints(points, position, quaternion, skinSize)
    expect(a).toMatchObject(
      new AABB({
        lowerBound: new Vec3(-5, -7, -4),
        upperBound: new Vec3(9, 10, 13),
      })
    )
  })

  test('copy', () => {
    const a = new AABB()
    const b = new AABB()
    a.upperBound.set(1, 2, 3)
    a.lowerBound.set(4, 5, 6)
    expect(b.copy(a)).toBe(b)
    expect(b).toMatchObject(a)
    expect(b).not.toBe(a)
  })

  test('clone', () => {
    const a = new AABB({
      lowerBound: new Vec3(-1, -2, -3),
      upperBound: new Vec3(1, 2, 3),
    })
    const b = a.clone()
    expect(a).not.toBe(b)
    expect(b).toMatchObject(a)
  })

  test('extend', () => {
    const a = new AABB({
      lowerBound: new Vec3(-1, -1, -1),
      upperBound: new Vec3(1, 1, 1),
    })
    const b = new AABB({
      lowerBound: new Vec3(-2, -2, -2),
      upperBound: new Vec3(2, 2, 2),
    })
    a.extend(b)
    expect(a).toMatchObject(b)

    const c = new AABB({
      lowerBound: new Vec3(-1, -1, -1),
      upperBound: new Vec3(1, 1, 1),
    })
    const d = new AABB({
      lowerBound: new Vec3(-2, -2, -2),
      upperBound: new Vec3(2, 2, 2),
    })
    d.extend(c)
    expect(d.lowerBound).toMatchObject(new Vec3(-2, -2, -2))
    expect(d.upperBound).toMatchObject(new Vec3(2, 2, 2))

    const e = new AABB({
      lowerBound: new Vec3(-2, -1, -1),
      upperBound: new Vec3(2, 1, 1),
    })
    const f = new AABB({
      lowerBound: new Vec3(-1, -1, -1),
      upperBound: new Vec3(1, 1, 1),
    })
    f.extend(e)
    expect(e.lowerBound).toMatchObject(new Vec3(-2, -1, -1))
    expect(e.upperBound).toMatchObject(new Vec3(2, 1, 1))

    const g = new AABB({
      lowerBound: new Vec3(-5, -3, -2),
      upperBound: new Vec3(-3, -1, -1),
    })
    const h = new AABB({
      lowerBound: new Vec3(-2, -5, -1.5),
      upperBound: new Vec3(2, -2, 5),
    })
    g.extend(h)
    expect(g.lowerBound).toMatchObject(new Vec3(-5, -5, -2))
    expect(g.upperBound).toMatchObject(new Vec3(2, -1, 5))
  })

  test('overlaps', () => {
    const a = new AABB()
    const b = new AABB()

    //Same aabb
    a.lowerBound.set(-1, -1, 0)
    a.upperBound.set(1, 1, 0)
    b.lowerBound.set(-1, -1, 0)
    b.upperBound.set(1, 1, 0)
    expect(a.overlaps(b)).toBe(true)

    //Corner overlaps
    b.lowerBound.set(1, 1, 0)
    b.upperBound.set(2, 2, 0)
    expect(a.overlaps(b)).toBe(true)

    //Separate
    b.lowerBound.set(1.1, 1.1, 0)
    expect(a.overlaps(b)).toBe(false)

    //fully inside
    b.lowerBound.set(-0.5, -0.5, 0)
    b.upperBound.set(0.5, 0.5, 0)
    expect(a.overlaps(b)).toBe(true)
    b.lowerBound.set(-1.5, -1.5, 0)
    b.upperBound.set(1.5, 1.5, 0)
    expect(a.overlaps(b)).toBe(true)

    //Translated
    b.lowerBound.set(-3, -0.5, 0)
    b.upperBound.set(-2, 0.5, 0)
    expect(a.overlaps(b)).toBe(false)
  })

  test('volume', () => {
    const a = new AABB({
      lowerBound: new Vec3(-1, -2, -3),
      upperBound: new Vec3(1, 2, 3),
    })
    expect(a.volume()).toBe(2 * 4 * 6)
  })

  test('contains', () => {
    const a = new AABB()
    const b = new AABB()

    a.lowerBound.set(-1, -1, -1)
    a.upperBound.set(1, 1, 1)
    b.lowerBound.set(-1, -1, -1)
    b.upperBound.set(1, 1, 1)

    expect(a.contains(b)).toBe(true)

    a.lowerBound.set(-2, -2, -2)
    a.upperBound.set(2, 2, 2)

    expect(a.contains(b)).toBe(true)

    b.lowerBound.set(-3, -3, -3)
    b.upperBound.set(3, 3, 3)

    expect(a.contains(b)).toBe(false)

    a.lowerBound.set(0, 0, 0)
    a.upperBound.set(2, 2, 2)
    b.lowerBound.set(-1, -1, -1)
    b.upperBound.set(1, 1, 1)

    expect(a.contains(b)).toBe(false)
  })

  test('toLocalFrame', () => {
    const worldAABB = new AABB()
    const localAABB = new AABB()
    const frame = new Transform()

    worldAABB.lowerBound.set(-1, -1, -1)
    worldAABB.upperBound.set(1, 1, 1)

    //No transform - should stay the same
    worldAABB.toLocalFrame(frame, localAABB)
    expect(localAABB).toMatchObject(worldAABB)

    //Some translation
    frame.position.set(-1, 0, 0)
    worldAABB.toLocalFrame(frame, localAABB)
    expect(localAABB).toMatchObject(
      new AABB({
        lowerBound: new Vec3(0, -1, -1),
        upperBound: new Vec3(2, 1, 1),
      })
    )
  })

  test('toWorldFrame', () => {
    const localAABB = new AABB()
    const worldAABB = new AABB()
    const frame = new Transform()

    localAABB.lowerBound.set(-1, -1, -1)
    localAABB.upperBound.set(1, 1, 1)

    //No transform - should stay the same
    localAABB.toLocalFrame(frame, worldAABB)
    expect(localAABB).toMatchObject(worldAABB)

    //Some translation on the frame
    frame.position.set(1, 0, 0)
    localAABB.toWorldFrame(frame, worldAABB)
    expect(worldAABB).toMatchObject(
      new AABB({
        lowerBound: new Vec3(0, -1, -1),
        upperBound: new Vec3(2, 1, 1),
      })
    )
  })
})
