package systems

import (
	"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
	"lab.zaar.be/thefish/alchemyst-go/engine/fov"
	"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
	"lab.zaar.be/thefish/alchemyst-go/engine/types"
	"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
	"sort"
	"time"
)

var fovRecompute = true
var redraw = false

type LevelRenderSystem struct {
	Viewport     *mainwindow.ViewPort
	Controller   *ecs.Controller
	state        *gamestate.GameState
	layer        *mainwindow.Layer
	fov          fov.Fov
	animateTiles *time.Ticker
	TorchRadius  int
}

func NewLevelRenderSystem(
	state *gamestate.GameState,
	controller *ecs.Controller,
	vp *mainwindow.ViewPort,
	layer *mainwindow.Layer,
	fov fov.Fov,
) LevelRenderSystem {

	trs := LevelRenderSystem{
		Viewport:   vp,
		Controller: controller,
		layer:      layer,
		state:      state,
		fov:        fov,
	}

	trs.TorchRadius = 12 //fixme move to sight component

	trs.animateTiles = time.NewTicker(time.Second / 12)

	return trs
}

// fixme add to screens/game Exit()
func (trs LevelRenderSystem) Close() {
	trs.animateTiles.Stop()
	trs.animateTiles = nil //zero pointer to ticker
}

func (trs LevelRenderSystem) Listen() {
	for {
		select {
		case <-trs.state.FovRecompute:
			fovRecompute = true
		case <-trs.state.Redraw:
			redraw = true
		case <-trs.animateTiles.C:
			redraw = true
		}
	}
}

type priorityRenderable struct {
	types.Appearance
	types.Coords
	Priority int //with bigger are rendered last
}

type prioritySorter []priorityRenderable

func (a prioritySorter) Len() int           { return len(a) }
func (a prioritySorter) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a prioritySorter) Less(i, j int) bool { return a[i].Priority < a[j].Priority }

func (trs LevelRenderSystem) Process() {
	playerCoords := trs.state.Controller.GetComponent(trs.state.Player, ecs.CoordsComponent).(types.Coords)

	trs.Viewport.Move(trs.state, playerCoords)

	if fovRecompute {
		trs.layer.ClearRect(trs.Viewport.Rect)
		trs.fov.ComputeFov(trs.state.Level, playerCoords, trs.TorchRadius)
		fovRecompute = false
		redraw = true
	}

	if redraw {
		//terrain
		for y := 0; y < trs.Viewport.H; y++ {
			for x := 0; x < trs.Viewport.W; x++ {
				mapCoords := types.Coords{X: trs.Viewport.CameraCoords.X + x, Y: trs.Viewport.CameraCoords.Y + y}

				if trs.state.Level.InBounds(mapCoords) {
					tile := trs.state.Level.GetTile(mapCoords)
					if tile.Explored || tile.Visible {
						trs.layer.PutToBase(
							x+trs.Viewport.X,
							y+trs.Viewport.Y,
							tile.GetChar(),
							tile.GetRawColor(),
							tile.GetRawBgColor(),
						)
					}
				}
			}
		}

		//mobs
		entToRender := make([]priorityRenderable, 0)
		for e := range trs.Controller.GetEntities() {
			if trs.Controller.HasComponent(e, ecs.CoordsComponent) &&
				trs.Controller.HasComponent(e, ecs.AppearanceComponent) {

				pos := trs.Controller.GetComponent(e, ecs.CoordsComponent).(types.Coords)
				appearance := trs.Controller.GetComponent(e, ecs.AppearanceComponent).(types.Appearance)

				//fixme fov check
				if !trs.state.Level.GetTile(pos).Visible {
					continue
				}

				vpc, err := trs.Viewport.ToVPCoords(pos)
				if err != nil {
					continue //we cant see it? no problem.
				}
				//Костыль для приоритета отрисовки
				p := 0
				if trs.Controller.HasComponent(e, ecs.CarriedComponent) {
					p = 10
				}
				if trs.Controller.HasComponent(e, ecs.MoveableComponent) {
					p = 100
				}

				entToRender = append(entToRender, priorityRenderable{
					Appearance: appearance,
					Coords:     vpc,
					Priority:   p,
				})
			}
		}
		sort.Sort(prioritySorter(entToRender))

		for _, ree := range entToRender {
			trs.layer.WithRawColor(ree.Appearance.ColorSet.Fg.GetColor()).
				Put(ree.X, ree.Y, ree.Appearance.Glyph.GetGlyph())
		}
		entToRender = nil
		redraw = false
	}
}

func (trs LevelRenderSystem) SystemType() string {
	return ecs.TerrainRenderSystem
}
