package main

import (
	"fmt"
	"log"
	"sort"

	"opslag.de/schobers/allg5"
	"opslag.de/schobers/geom"
	"opslag.de/schobers/krampus19/alui"
)

type playLevel struct {
	alui.ControlBase

	ctx  *Context
	init bool

	menu     *alui.Menu
	end      alui.Control
	showMenu bool

	packID  string
	levelID string
	offset  geom.PointF32
	scale   float32

	state playLevelState
}

type keyPressedState map[allg5.Key]bool

func (s keyPressedState) ArePressed(keys ...allg5.Key) []allg5.Key {
	pressed := make([]allg5.Key, 0, len(keys))
	for _, k := range keys {
		if s[k] {
			pressed = append(pressed, k)
		}
	}
	return pressed
}

func (s keyPressedState) CountPressed(keys ...allg5.Key) int { return len(s.ArePressed(keys...)) }

func (l *playLevel) Enter(ctx *Context) error {
	l.ctx = ctx

	l.menu = alui.NewMenu()
	l.menu.Add("Resume", func() { l.showMenu = false })
	l.menu.Add("Restart", func() { l.ctx.Navigation.PlayLevel(l.packID, l.levelID) })
	l.menu.Add("Select other level", func() { l.ctx.Navigation.SelectLevel(l.packID) })
	l.menu.Add("Quit to main menu", func() { l.ctx.Navigation.ShowMainMenu() })
	l.menu.Add("Quit to desktop", func() { l.ctx.Navigation.Quit() })
	l.menu.OnEscape = func() { l.showMenu = false }

	l.init = true
	l.state.Init(l.ctx, l.packID, l.levelID, l.onComplete)
	return nil
}

func (l *playLevel) Leave() {}

func (l *playLevel) onComplete() {
	menu := alui.NewMenu()
	menu.AddChild(alui.NewMargins(&alui.Label{ControlBase: alui.ControlBase{Font: "header"}, Text: "Congratulations", TextAlign: allg5.AlignCenter}, 3*margin, 0, 2*margin))
	menu.AddChild(alui.NewMargins(&alui.Label{Text: fmt.Sprintf("You've completed the level in %d steps.", l.state.steps), TextAlign: allg5.AlignCenter}, 0, 0, 2*margin))

	progress := l.ctx.Progress.Level(l.packID, l.levelID)
	if l.state.steps < progress.Steps || progress.Steps == 0 {
		var improvement = progress.Steps - l.state.steps
		if improvement > 0 { // first time it is lower than zero (no previous record).
			menu.AddChild(alui.NewMargins(&alui.Label{Text: fmt.Sprintf("This is an improvement of %d steps.", improvement), TextAlign: allg5.AlignCenter}, 0, 0, 2*margin))
		}

		err := l.ctx.Progress.Update(l.packID, l.levelID, func(p *levelProgress) {
			p.Steps = l.state.steps
		})
		if err != nil {
			log.Printf("Unable to store progress; err: %v", err)
		}
	}

	nextID, ok := l.state.pack.FindNext(l.levelID)
	if ok {
		menu.Add("Continue with next", func() { l.ctx.Navigation.PlayLevel(l.packID, nextID) })
	}
	if l.ctx.Levels.MustSelect() {
		menu.Add("Select level pack", func() { l.ctx.Navigation.SelectPack() })
	}
	menu.Add("Select level", func() { l.ctx.Navigation.SelectLevel(l.packID) })
	menu.Add("Go to main menu", func() { l.ctx.Navigation.ShowMainMenu() })

	l.end = menu
}

func (l *playLevel) posToScreenF32(p geom.PointF32, z float32) geom.PointF32 {
	pos := l.posToCabinet(p.Add2D(.5, .5)).Add2D(0, z)
	return pos.Mul(l.scale).Add(l.offset)
}

func (l *playLevel) posToCabinet(p geom.PointF32) geom.PointF32 {
	return geom.PtF32(p.X*100-p.Y*50, p.Y*70)
}

// <-  150->
//   <-100->
//
//   /----/|   ^  ^
//  /    / |   |  70
// /----/  |  170 v  ^
// |    |  |   |     |
// |    | /    |    100
// |----|/     v     v
//
// Offset between horizontal tiles: 100,0
// Offset between vertical tiles: -25,70

func (l *playLevel) Layout(ctx *alui.Context, bounds geom.RectangleF32) {
	if l.init {
		l.scale = 1
		l.offset = geom.PointF32{}

		level := l.state.Level()
		var contentCenter = l.posToScreenF32(geom.PtF32(.5*float32(level.width), .5*float32(level.height)), 0)
		var content = geom.RectF32(contentCenter.X, contentCenter.Y, contentCenter.X, contentCenter.Y)
		for idx, tile := range l.state.Level().tiles {
			if tile == tileNothing || tile == tileInvalid {
				continue
			}
			pos := level.idxToPos(idx).ToF32()
			bottomLeft := l.posToScreenF32(pos.Add2D(-1.5, 1.5), 100)
			content.Min = geom.MinPtF32(content.Min, bottomLeft)
			content.Max = geom.MaxPtF32(content.Max, bottomLeft)
			topRight := l.posToScreenF32(pos.Add2D(1.5, -1.5), 0)
			content.Min = geom.MinPtF32(content.Min, topRight)
			content.Max = geom.MaxPtF32(content.Max, topRight)
		}
		l.scale = geom.Min32(bounds.Dx()/content.Dx(), bounds.Dy()/content.Dy())

		contentCenter = content.Center().Mul(l.scale)
		center := bounds.Center()
		l.offset = geom.PtF32(center.X-contentCenter.X, center.Y-contentCenter.Y)
		l.init = false
	}

	l.state.Tick(l.ctx.Tick)
}

func (l *playLevel) Handle(e allg5.Event) {
	switch e := e.(type) {
	case *allg5.KeyDownEvent:
		l.state.PressKey(e.KeyCode)
	case *allg5.KeyUpEvent:
		l.state.ReleaseKey(e.KeyCode)
	}

	switch {
	case l.showMenu:
		l.menu.Handle(e)
	case l.state.complete:
		l.end.Handle(e)
	default:
		switch e := e.(type) {
		case *allg5.KeyDownEvent:
			switch e.KeyCode {
			case allg5.KeyEscape:
				l.showMenu = true
				l.menu.Activate(0)
			}
			l.state.TryPlayerMove(e.KeyCode)
		}
	}
}

func (l *playLevel) drawSprite(name, partName string, pos entityLoc) {
	l.ctx.SpriteDrawer.Draw(name, partName, l.posToScreenF32(pos.pos, pos.z), DrawSpriteOptions{Scale: l.scale})
}

func (l *playLevel) drawSpriteAlpha(name, partName string, pos entityLoc, alpha float32) {
	a := byte(alpha * 255)
	c := allg5.NewColorAlpha(a, a, a, a)
	l.ctx.SpriteDrawer.Draw(name, partName, l.posToScreenF32(pos.pos, pos.z), DrawSpriteOptions{Scale: l.scale, Tint: &c})
}

func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
	level := l.state.level

	drawParticles := func(particles []particle) {
		for _, p := range particles {
			l.drawSpriteAlpha("particles", "splash", entityLoc{p.pos, p.z}, p.opaq)
		}
	}

	for i, t := range level.tiles {
		pos := geom.Pt(i%level.width, i/level.width)
		scr := entityLoc{pos.ToF32(), 0}
		switch t {
		case tileBasic:
			if l.state.IsNextToMagma(pos) {
				l.drawSprite("lava_brick", "magma", scr)
			} else {
				l.drawSprite("lava_brick", "lava_brick", scr)
			}
		case tileMagma:
			l.drawSprite("magma", "magma", scr)
			brick := l.state.FindSunkenBrick(pos)
			if brick != nil {
				behind, front := splitParticles(scr.pos.Y, l.state.Particles(pos))
				drawParticles(behind)
				l.drawSprite("brick", "brick", brick.scr)
				l.drawSprite("magma", "sunken_overlay", scr)
				drawParticles(front)
			}
		}
	}

	entities := l.state.Entities()
	sort.Slice(entities, func(i, j int) bool {
		if entities[i].scr.pos.Y == entities[j].scr.pos.Y {
			return entities[i].scr.pos.X < entities[j].scr.pos.X
		}
		return entities[i].scr.pos.Y < entities[j].scr.pos.Y
	})

	for _, e := range entities {
		switch e.typ {
		case entityTypeBrick:
			l.drawSprite("brick", "brick", e.scr)
		case entityTypeCharacter:
			l.drawSprite("dragon", "dragon", e.scr)
		case entityTypeEgg:
			l.drawSprite("egg", "egg", e.scr)
		}
	}

	font := ctx.Fonts.Get("default")
	steps := fmt.Sprintf("STEPS: %d", l.state.Steps())
	ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps)

	switch {
	case l.showMenu:
		allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, allg5.NewColorAlpha(0, 0, 0, 0xaf))
		l.menu.Render(ctx, bounds)
	case l.state.complete:
		allg5.DrawFilledRectangle(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y, allg5.NewColorAlpha(0, 0, 0, 0xaf))
		l.end.Render(ctx, bounds)
	}
}
