package entity

import (
	"log"
	"math"
	"time"

	"github.com/Edgaru089/gl01/internal/util"
	"github.com/Edgaru089/gl01/internal/util/itype"
	"github.com/Edgaru089/gl01/internal/world"
)

func (e *Entity) worldHitbox(hitbox itype.Vec3d) itype.Boxd {
	return itype.Boxd{
		OffX:  e.pos[0] - hitbox[0]/2,
		OffY:  e.pos[1],
		OffZ:  e.pos[2] - hitbox[2]/2,
		SizeX: hitbox[0],
		SizeY: hitbox[1],
		SizeZ: hitbox[2],
	}
}

// WorldHitbox returns the hitbox of the entity, in world coordinates.
func (e *Entity) WorldHitbox() itype.Boxd {
	return e.worldHitbox(e.b.Hitbox(e.pos, e.ds))
}

// MoveEps is a small value used to indicate a tiny amount more than zero.
const MoveEps = 1e-7

// the move series functions should be called with e.hp already filled

func (e *Entity) moveX(delta float64, hitbox itype.Boxd, w *world.World) {
	if math.Abs(delta) < MoveEps*10 {
		return
	}

	var hit bool = false
	var deltaMin float64 = 10000

	if delta > 1-MoveEps {
		delta = 1 - MoveEps
	}
	if delta < -1+MoveEps {
		delta = -1 + MoveEps
	}

	// X+ / X-
	if pointStuck(p, w) {
		continue
	}

	dest := hitbox.Offsetv(delta, 0, 0)
	blockid := dest.Floor()
	block := w.Block(blockid)

	var deltaDone float64
	if block.Id == 0 {
		deltaDone = delta
	} else { // block.Id!=0
		app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset)
		blockBox := app.Hitbox.Offset(blockid.ToFloat64())
		if !app.NotSolid && blockBox.Contains(dest) { // Hit!
			hit = true
			if delta > 0 { // moving X+, hit on the X- face
				deltaDone = blockBox.OffX - (e.pos[0] + hitbox.SizeX/2) - MoveEps
				//log.Print("Hit X+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone)
			} else { // moving X-, hit on the X+ face
				deltaDone = blockBox.OffX + blockBox.SizeX - (e.pos[0] - hitbox.SizeX/2) + MoveEps
				//log.Print("Hit X-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone)
			}
		} else {
			deltaDone = delta
		}
	}

	deltaMin = util.AbsMind(deltaMin, deltaDone)

	if hit {
		e.speed[0] = 0
	}
	e.pos[0] += deltaMin
}
func (e *Entity) moveY(delta float64, hitbox itype.Boxd, w *world.World) {
	if math.Abs(delta) < MoveEps*10 {
		return
	}

	var hit bool = false
	var deltaMin float64 = 10000

	if delta > 1-MoveEps {
		delta = 1 - MoveEps
	}
	if delta < -1+MoveEps {
		delta = -1 + MoveEps
	}

	// Y+ / Y-
	for _, p := range e.hp {
		if pointStuck(p, w) {
			continue
		}

		dest := p.Addv(0, delta, 0)
		blockid := dest.Floor()
		block := w.Block(blockid)

		var deltaDone float64
		if block.Id == 0 {
			deltaDone = delta
		} else { // block.Id!=0
			app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset)
			blockBox := app.Hitbox.Offset(blockid.ToFloat64())
			if !app.NotSolid && blockBox.Contains(dest) { // Hit!
				hit = true
				if delta > 0 { // moving Y+, hit on the Y- face
					deltaDone = blockBox.OffY - (e.pos[1] + hitbox.SizeY) - MoveEps
					//log.Print("Hit Y+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone)
				} else { // moving Y-, hit on the Y+ face (on the ground)
					deltaDone = blockBox.OffY + blockBox.SizeY - e.pos[1] + MoveEps
					//log.Print("Hit Y-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone)
					if !e.onGround {
						log.Print("onGround = true")
					}
					e.onGround = true
				}
			} else {
				deltaDone = delta
			}
		}

		deltaMin = util.AbsMind(deltaMin, deltaDone)
	}

	if hit {
		e.speed[1] = 0
	}

	if math.Abs(deltaMin) > MoveEps*10 {
		if e.onGround {
			log.Print("onGround = false")
		}
		e.onGround = false
	}
	e.pos[1] += deltaMin
}
func (e *Entity) moveZ(delta float64, hitbox itype.Boxd, w *world.World) {
	if math.Abs(delta) < MoveEps*10 {
		return
	}

	var hit bool = false
	var deltaMin float64 = 10000

	if delta > 1-MoveEps {
		delta = 1 - MoveEps
	}
	if delta < -1+MoveEps {
		delta = -1 + MoveEps
	}

	// Z+ / Z-
	for _, p := range e.hp {
		if pointStuck(p, w) {
			continue
		}

		dest := p.Addv(0, 0, delta)
		blockid := dest.Floor()
		block := w.Block(blockid)

		var deltaDone float64
		if block.Id == 0 {
			deltaDone = delta
		} else { // block.Id!=0
			app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset)
			blockBox := app.Hitbox.Offset(blockid.ToFloat64())
			if !app.NotSolid && blockBox.Contains(dest) { // Hit!
				hit = true
				if delta > 0 { // moving Z+, hit on the Z- face
					deltaDone = util.Maxd(blockBox.OffZ-(e.pos[2]+hitbox.SizeZ/2)-MoveEps, 0)
					//log.Print("Hit Z+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone)
				} else { // moving Z-, hit on the Z+ face
					deltaDone = util.Mind(blockBox.OffZ+blockBox.SizeZ-(e.pos[2]-hitbox.SizeZ/2)+MoveEps, 0)
					//log.Print("Hit Z-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone)
				}
			} else {
				deltaDone = delta
			}
		}

		deltaMin = util.AbsMind(deltaMin, deltaDone)
	}

	if hit {
		e.speed[2] = 0
	}
	e.pos[2] += deltaMin
}

const gravity float64 = 26
const deaclc float64 = 28

func (e *Entity) Update(world *world.World, deltaTime time.Duration) {

	hitbox := e.b.Hitbox(e.pos, e.ds)
	box := e.worldHitbox(hitbox)

	e.hp = e.hp[0:0]
	e.hp = e.boxHitpoints(e.hp, box)

	e.moveX(e.speed[0]*deltaTime.Seconds(), box, world)
	e.moveY(e.speed[1]*deltaTime.Seconds(), box, world)
	e.moveZ(e.speed[2]*deltaTime.Seconds(), box, world)

	speed := math.Sqrt(e.speed[0]*e.speed[0] + e.speed[2]*e.speed[2])
	if speed > MoveEps {
		decrease := deaclc * deltaTime.Seconds()
		if !e.onGround {
			decrease /= 10
		}
		factor := util.Maxd(util.Mind(speed-decrease, 9), 0) / speed

		e.speed[0] *= factor
		e.speed[2] *= factor
	}

	e.speed[1] -= gravity * deltaTime.Seconds()

	e.b.Update(e.pos, e.ds, world, deltaTime)
}