package state

import (
	"mvvasilev/last_light/engine"
	"mvvasilev/last_light/game/model"
	"mvvasilev/last_light/game/ui"
	"mvvasilev/last_light/game/world"

	"github.com/gdamore/tcell/v2"
	"github.com/gdamore/tcell/v2/views"
)

type PlayingState struct {
	player  *model.Player
	someNPC *model.NPC

	dungeon *world.Dungeon

	viewport *engine.Viewport

	movePlayerDirection model.Direction
	pauseGame           bool
	openInventory       bool
	pickUpUnderPlayer   bool
	interact            bool
	moveEntities        bool

	nextGameState GameState
}

func BeginPlayingState() *PlayingState {
	s := new(PlayingState)

	mapSize := engine.SizeOf(128, 128)

	s.dungeon = world.CreateDungeon(mapSize.Width(), mapSize.Height(), 1)

	s.player = model.CreatePlayer(s.dungeon.CurrentLevel().PlayerSpawnPoint().XY())

	s.someNPC = model.CreateNPC(s.dungeon.CurrentLevel().NextLevelStaircase())

	s.dungeon.CurrentLevel().AddEntity(s.player, '@', tcell.StyleDefault)
	s.dungeon.CurrentLevel().AddEntity(s.someNPC, 'N', tcell.StyleDefault)

	s.viewport = engine.CreateViewport(
		engine.PositionAt(0, 0),
		s.dungeon.CurrentLevel().PlayerSpawnPoint(),
		engine.SizeOf(80, 24),
		tcell.StyleDefault,
	)

	s.nextGameState = s

	return s
}

func (ps *PlayingState) Pause() {
	ps.pauseGame = true
}

func (ps *PlayingState) Unpause() {
	ps.pauseGame = false
}

func (ps *PlayingState) SetPaused(paused bool) {
	ps.pauseGame = paused
}

func (ps *PlayingState) MovePlayer() {
	if ps.movePlayerDirection == model.DirectionNone {
		return
	}

	newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(ps.movePlayerDirection))

	if ps.dungeon.CurrentLevel().IsTilePassable(newPlayerPos.XY()) {
		dx, dy := model.MovementDirectionOffset(ps.movePlayerDirection)
		ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
		ps.viewport.SetCenter(ps.player.Position())
	}

	ps.movePlayerDirection = model.DirectionNone
}

func (ps *PlayingState) InteractBelowPlayer() {
	playerPos := ps.player.Position()

	if playerPos == ps.dungeon.CurrentLevel().NextLevelStaircase() {
		ps.SwitchToNextLevel()
		return
	}

	if playerPos == ps.dungeon.CurrentLevel().PreviousLevelStaircase() {
		ps.SwitchToPreviousLevel()
		return
	}
}

func (ps *PlayingState) SwitchToNextLevel() {
	if !ps.dungeon.HasNextLevel() {
		ps.nextGameState = CreateDialogState(
			ui.CreateOkDialog(
				"The Unknown Depths",
				"The staircases descent down to the lower levels is seemingly blocked by multiple large boulders. They appear immovable.",
				"Continue",
				40,
				func() {
					ps.nextGameState = ps
				},
			),
			ps,
		)

		return
	}

	ps.dungeon.CurrentLevel().DropEntity(ps.player.UniqueId())

	ps.dungeon.MoveToNextLevel()

	ps.player.MoveTo(ps.dungeon.CurrentLevel().PlayerSpawnPoint())

	ps.viewport = engine.CreateViewport(
		engine.PositionAt(0, 0),
		ps.dungeon.CurrentLevel().PlayerSpawnPoint(),
		engine.SizeOf(80, 24),
		tcell.StyleDefault,
	)

	ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault)
}

func (ps *PlayingState) SwitchToPreviousLevel() {
	if !ps.dungeon.HasPreviousLevel() {
		ps.nextGameState = CreateDialogState(
			ui.CreateOkDialog(
				"The Surface",
				"You feel the gentle, yet chilling breeze of the surface make its way through the weaving cavern tunnels, the very same you had to make your way through to get where you are. There is nothing above that you need. Find the last light, or die trying.",
				"Continue",
				40,
				func() {
					ps.nextGameState = ps
				},
			),
			ps,
		)

		return
	}

	ps.dungeon.CurrentLevel().DropEntity(ps.player.UniqueId())

	ps.dungeon.MoveToPreviousLevel()

	ps.player.MoveTo(ps.dungeon.CurrentLevel().NextLevelStaircase())

	ps.viewport = engine.CreateViewport(
		engine.PositionAt(0, 0),
		ps.dungeon.CurrentLevel().NextLevelStaircase(),
		engine.SizeOf(80, 24),
		tcell.StyleDefault,
	)

	ps.dungeon.CurrentLevel().AddEntity(ps.player, '@', tcell.StyleDefault)
}

func (ps *PlayingState) PickUpItemUnderPlayer() {
	pos := ps.player.Position()
	item := ps.dungeon.CurrentLevel().RemoveItemAt(pos.XY())

	if item == nil {
		return
	}

	success := ps.player.Inventory().Push(*item)

	if !success {
		ps.dungeon.CurrentLevel().SetItemAt(pos.X(), pos.Y(), *item)
	}
}

func (ps *PlayingState) CalcPathToPlayerAndMove() {
	distanceToPlayer := ps.someNPC.Position().Distance(ps.player.Position())
	if distanceToPlayer > 16 {
		return
	}

	pathToPlayer := engine.FindPath(
		ps.someNPC.Position(),
		ps.player.Position(),
		16,
		func(x, y int) bool {
			if x == ps.player.Position().X() && y == ps.player.Position().Y() {
				return true
			}

			return ps.dungeon.CurrentLevel().IsTilePassable(x, y)
		},
	)

	nextPos, hasNext := pathToPlayer.Next()

	if !hasNext {
		return
	}

	if nextPos.Equals(ps.player.Position()) {
		return
	}

	ps.dungeon.CurrentLevel().MoveEntityTo(ps.someNPC.UniqueId(), nextPos.X(), nextPos.Y())
}

func (ps *PlayingState) OnInput(e *tcell.EventKey) {
	ps.player.Input(e)

	if e.Key() == tcell.KeyEsc {
		ps.pauseGame = true
		return
	}

	if e.Key() == tcell.KeyRune && e.Rune() == 'i' {
		ps.openInventory = true
		return
	}

	if e.Key() == tcell.KeyRune && e.Rune() == 'p' {
		ps.pickUpUnderPlayer = true
		return
	}

	if e.Key() == tcell.KeyRune && e.Rune() == 'e' {
		ps.interact = true
		return
	}

	switch e.Key() {
	case tcell.KeyUp:
		ps.movePlayerDirection = model.DirectionUp
		ps.moveEntities = true
	case tcell.KeyDown:
		ps.movePlayerDirection = model.DirectionDown
		ps.moveEntities = true
	case tcell.KeyLeft:
		ps.movePlayerDirection = model.DirectionLeft
		ps.moveEntities = true
	case tcell.KeyRight:
		ps.movePlayerDirection = model.DirectionRight
		ps.moveEntities = true
	}
}

func (ps *PlayingState) OnTick(dt int64) GameState {
	ps.player.Tick(dt)

	if ps.pauseGame {
		return PauseGame(ps)
	}

	if ps.openInventory {
		ps.openInventory = false
		return CreateInventoryScreenState(ps.player, ps)
	}

	if ps.movePlayerDirection != model.DirectionNone {
		ps.MovePlayer()
	}

	if ps.pickUpUnderPlayer {
		ps.pickUpUnderPlayer = false
		ps.PickUpItemUnderPlayer()
	}

	if ps.interact {
		ps.interact = false
		ps.InteractBelowPlayer()
	}

	if ps.moveEntities {
		ps.moveEntities = false
		ps.CalcPathToPlayerAndMove()
	}

	return ps.nextGameState
}

func (ps *PlayingState) CollectDrawables() []engine.Drawable {
	return engine.Multidraw(engine.CreateDrawingInstructions(func(v views.View) {
		ps.viewport.DrawFromProvider(v, func(x, y int) (rune, tcell.Style) {
			tile := ps.dungeon.CurrentLevel().TileAt(x, y)

			if tile != nil {
				return tile.Presentation()
			}

			return ' ', tcell.StyleDefault
		})
	}))
}
