package tins2021

import (
	"math/rand"
	"sort"

	"opslag.de/schobers/geom"
)

type Level struct {
	Player         geom.Point
	Lives          int
	Stars          int
	StarsCollected int
	Tiles          Tiles
	Monsters       Monsters
	MonsterTargets map[geom.Point]geom.Point
	GameOver       bool
	Difficulty     int
	Score          int
	Bounds         geom.Rectangle
}

func NewLevel() *Level {
	const dims = 12
	f := &Level{
		Player:         geom.Pt(1, 1),
		Lives:          3,
		Stars:          0,
		StarsCollected: 0,
		Tiles:          Tiles{},
		Monsters:       Monsters{},
		MonsterTargets: map[geom.Point]geom.Point{},
		Bounds:         geom.Rect(1, 1, dims+1, dims+1),
	}
	for y := 1; y <= dims; y++ {
		endX := dims
		if y%2 == 0 {
			endX--
		}
		for x := 1; x <= endX; x++ {
			f.Tiles[geom.Pt(x, y)] = &Tile{}
		}
	}
	return f
}

func (l Level) CanPlayerMove(dir Direction) (geom.Point, bool) {
	return l.Tiles.CanMove(l.Player, dir)
}

func (l Level) CanMonsterMove(p geom.Point, dir Direction) (geom.Point, bool) {
	q, ok := l.Tiles.CanMove(p, dir)
	if !ok {
		return geom.Point{}, false
	}
	if l.CanMonsterMoveTo(q) {
		return q, true
	}
	return geom.Point{}, false
}

func (l Level) CanMonsterMoveTo(p geom.Point) bool {
	if l.Tiles[p].Occupied() {
		return false
	}
	if _, ok := l.Monsters[p]; ok {
		return false
	}
	for _, target := range l.MonsterTargets {
		if p == target {
			return false
		}
	}
	return true
}

func (l *Level) DecrementLive() {
	l.Lives--
	if l.Lives == 0 {
		l.GameOver = true
	}
}

func (l *Level) DestroyMonster(pos geom.Point) {
	delete(l.MonsterTargets, pos)
	delete(l.Monsters, pos)
}

func (l *Level) MoveMonster(target, source geom.Point) {
	l.Monsters[target] = l.Monsters[source]
	l.DestroyMonster(source)
}

type MonsterHit struct {
	Position geom.Point
	Type     MonsterType
}

func (l *Level) MovePlayer(dir Direction) (bool, *MonsterHit) {
	towards, allowed := l.CanPlayerMove(dir)
	if !allowed {
		return false, nil
	}
	l.Player = towards
	tile := l.Tiles[towards]
	if tile.Heart {
		l.Lives++
		tile.Heart = false
		l.Score += 10
	}
	if tile.Star {
		l.StarsCollected++
		tile.Star = false
		l.Score += 25
	}
	var hit *MonsterHit
	if l.Monsters[towards] != nil {
		hit = &MonsterHit{
			Position: towards,
			Type:     l.Monsters[towards].Type(),
		}
		l.DecrementLive()
		l.DestroyMonster(towards)
		l.Score -= 5
	}
	l.Score -= 1 // for every move
	return true, hit
}

func (l *Level) Randomize(difficulty int, stars int) {
	if difficulty < 0 {
		difficulty = 0
	}
	l.Difficulty = difficulty
	positions := make([]geom.Point, 0, len(l.Tiles))
	for pos := range l.Tiles {
		positions = append(positions, pos)
	}
	distances := l.Tiles.Distances(l.Player)
	sort.Slice(positions, func(i, j int) bool {
		return distances[positions[i]] < distances[positions[j]]
	})
	pick := func(bias int) geom.Point { // bias towards far away [0..100]
		if bias > 100 {
			bias = 100
		}
		if bias < 0 {
			bias = 0
		}
		n := len(positions)
		const min = 4
		from := min + bias*(n-min)/(2*(n-min)) // never pick first
		return positions[from+rand.Intn(n-from)]
	}

	flip := difficulty * len(l.Tiles) / 200
	if flip > len(l.Tiles)/2 {
		flip = len(l.Tiles) / 2
	}
	for ; flip > 0; flip-- {
		for {
			pos := pick(0)
			if l.Tiles[pos].Inversed {
				continue
			}
			l.Tiles[pos].Invert()
			if l.Tiles.AllReachable(l.Player) {
				break
			}
			l.Tiles[pos].Invert()
		}
	}
	l.Stars = stars
	for stars > 0 {
		pos := pick(difficulty)
		if l.Tiles[pos].Occupied() {
			continue
		}
		l.Tiles[pos].Star = true
		stars--
	}
	hearts := rand.Intn(2 + (100-difficulty)/50) // [3..1] (only difficulty 0 has 3 hearts)
	for hearts > 0 {
		pos := pick(difficulty)
		if l.Tiles[pos].Occupied() {
			continue
		}
		l.Tiles[pos].Heart = true
		hearts--
	}
	monsters := 5 + (25 * difficulty / 100)
	minRandomMonster := (100-difficulty)*60/100 + 10 // [70..10]
	minChaserMonster := (100-difficulty)*70/100 + 25 // [95..25]
	for monsters > 0 {
		pos := pick(100 - difficulty)
		curr := l.Monsters[pos]
		if l.Tiles[pos].Occupied() || curr != nil {
			continue
		}
		monster := MonsterTypeStraight
		m := rand.Intn(100)
		if m >= minChaserMonster {
			monster = MonsterTypeChaser
		} else if m >= minRandomMonster {
			monster = MonsterTypeRandom
		}
		switch monster {
		case MonsterTypeStraight:
			l.Monsters[pos] = &StraightWalkingMonster{Direction: RandomDirection()}
		case MonsterTypeRandom:
			l.Monsters[pos] = &RandomWalkingMonster{}
		case MonsterTypeChaser:
			l.Monsters[pos] = &ChasingMonster{}
		default:
			panic("not implemented")
		}
		monsters--
	}
}