package d2vector

import (
	"fmt"
	"math"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
)

const (
	subTilesPerTile          float64 = 5  // The number of sub tiles that make up one map tile.
	entityDirectionCount     float64 = 64 // The Diablo equivalent of 360 degrees when dealing with entity rotation.
	entityDirectionIncrement float64 = 8  // One 8th of 64. There 8 possible facing directions for entities.
	// Note: there should be 16 facing directions for entities. See Position.DirectionTo()
)

// Position is a vector in world space. The stored value is the sub tile position.
type Position struct {
	Vector
}

// NewPosition returns a Position struct with the given sub tile coordinates where 1 = 1 sub tile, with a fractional
// offset.
func NewPosition(x, y float64) Position {
	p := Position{NewVector(x, y)}
	p.checkValues()

	return p
}

// Set sets this position to the given sub tile coordinates where 1 = 1 sub tile, with a fractional offset.
func (p *Position) Set(x, y float64) {
	p.x, p.y = x, y
	p.checkValues()
}

func (p *Position) checkValues() {
	if math.IsNaN(p.x) || math.IsNaN(p.y) {
		panic(fmt.Sprintf("float value is NaN: %s", p.Vector))
	}

	if math.IsInf(p.x, 0) || math.IsInf(p.y, 0) {
		panic(fmt.Sprintf("float value is Inf: %s", p.Vector))
	}
}

// World is the exact position where 1 = one map tile and 0.2 = one sub tile.
func (p *Position) World() *Vector {
	c := p.Clone()
	return c.DivideScalar(subTilesPerTile)
}

// Tile is the position of the current map tile. It is the floor of World(), always a whole number.
func (p *Position) Tile() *Vector {
	return p.World().Floor()
}

// RenderOffset is the offset in sub tiles from the curren tile, + 1. This places the vector at the bottom vertex of an
// isometric diamond visually representing one sub tile. Sub tile indices increase to the lower right diagonal ('down')
// and to the lower left diagonal ('left') of the isometric grid. This renders the target one index above which visually
// is one tile below.
func (p *Position) RenderOffset() *Vector {
	return p.subTileOffset().AddScalar(1)
}

// SubTileOffset is the offset from the current map tile in sub tiles.
func (p *Position) subTileOffset() *Vector {
	t := p.Tile().Scale(subTilesPerTile)
	c := p.Clone()

	return c.Subtract(t)
}

// DirectionTo returns the entity direction from this vector to the given vector.
func (v *Vector) DirectionTo(target Vector) int {
	direction := target.Clone()
	direction.Subtract(v)

	angle := direction.SignedAngle(VectorRight())
	radiansPerDirection := d2math.RadFull / entityDirectionCount

	// Note: The direction is always one increment out so we must subtract one increment.
	// This might not work when we implement all 16 directions (as of this writing entities can only face one of 8
	// directions). See entityDirectionIncrement.
	newDirection := int((angle / radiansPerDirection) - entityDirectionIncrement)

	return d2math.WrapInt(newDirection, int(entityDirectionCount))
}
