package trace

import (
	"github.com/df-mc/dragonfly/server/block/cube"
	"github.com/df-mc/dragonfly/server/block/model"
	"github.com/df-mc/dragonfly/server/world"
	"github.com/go-gl/mathgl/mgl64"
	"math"
)

// BlockResult is the result of a ray trace collision with a block's model.
type BlockResult struct {
	bb   cube.BBox
	pos  mgl64.Vec3
	face cube.Face

	blockPos cube.Pos
}

// BBox returns the BBox that was collided within the block's model.
func (r BlockResult) BBox() cube.BBox {
	return r.bb
}

// Position ...
func (r BlockResult) Position() mgl64.Vec3 {
	return r.pos
}

// Face returns the hit block face.
func (r BlockResult) Face() cube.Face {
	return r.face
}

// BlockPosition returns the block that was collided with.
func (r BlockResult) BlockPosition() cube.Pos {
	return r.blockPos
}

// BlockIntercept performs a ray trace and calculates the point on the block model's edge nearest to the start position
// that the ray collided with.
// BlockIntercept returns a BlockResult with the block collided with and with the colliding vector closest to the start position,
// if no colliding point was found, a zero BlockResult is returned and ok is false.
func BlockIntercept(pos cube.Pos, src world.BlockSource, b world.Block, start, end mgl64.Vec3) (result BlockResult, ok bool) {
	bbs := b.Model().BBox(pos, src)
	if len(bbs) == 0 {
		return
	}

	var (
		hit  Result
		dist = math.MaxFloat64
	)

	for _, bb := range bbs {
		next, ok := BBoxIntercept(bb.Translate(pos.Vec3()), start, end)
		if !ok {
			continue
		}

		nextDist := next.Position().Sub(start).LenSqr()
		if nextDist < dist {
			hit = next
			dist = nextDist
		}
	}

	if hit == nil {
		return result, false
	}

	return BlockResult{bb: hit.BBox(), pos: hit.Position(), face: hit.Face(), blockPos: pos}, true
}

// BlockIntersects checks if the line segment from start to end intersects the block model of b at pos. Unlike
// BlockIntercept, it only reports whether an intersection exists and does not calculate the closest hit position, face,
// or bounding box.
func BlockIntersects(pos cube.Pos, src world.BlockSource, b world.Block, start, end mgl64.Vec3) bool {
	m := b.Model()
	switch m.(type) {
	case model.Empty:
		return false
	case model.Solid:
		return BBoxIntersects(cube.Box(0, 0, 0, 1, 1, 1).Translate(pos.Vec3()), start, end)
	}

	for _, bb := range m.BBox(pos, src) {
		if BBoxIntersects(bb.Translate(pos.Vec3()), start, end) {
			return true
		}
	}
	return false
}
