package client

import (
	"fmt"
	"galaxy/client/widget/calculator"
	"galaxy/client/world"
	mc "galaxy/model/client"
	"galaxy/model/report"
	"slices"

	"fyne.io/fyne/v2"
)

func (e *client) initLatestReport() {
	e.stateMu.Lock()
	if e.state.ActiveGameID != nil {
		if stateIdx := slices.IndexFunc(e.state.GameState, func(gs mc.GameState) bool { return gs.ID == *e.state.ActiveGameID }); stateIdx >= 0 {
			e.initReportAsync(*e.state.ActiveGameID, e.state.GameState[stateIdx].ActiveTurn)
		}
	}
	e.stateMu.Unlock()
}

func (e *client) initReportAsync(gid mc.GameID, t uint) {
	e.s.ReportExistsAsync(gid, t, func(b bool, err error) { e.reportAtStorageExists(gid, t, b, err) })
}

func (e *client) reportAtStorageExists(gid mc.GameID, t uint, exists bool, err error) {
	if err != nil {
		e.handlerError(err)
		return
	}
	if exists {
		e.s.LoadReportAsync(gid, t, func(r report.Report, err error) { e.loadReportHandler(gid, r, err) })
		return
	}
	e.conn.FetchReport(gid, t, func(r report.Report, err error) { e.fetchReportHandler(gid, r, err) })
}

func (e *client) fetchReportHandler(gid mc.GameID, r report.Report, err error) {
	if err != nil {
		e.handlerError(err)
		return
	}

	e.s.SaveReportAsync(gid, r.Turn, r, func(err error) { e.loadReportHandler(gid, r, err) })
}

func (e *client) loadReportHandler(gid mc.GameID, r report.Report, err error) {
	if err != nil {
		e.handlerError(err)
		return
	}

	e.stateMu.Lock()
	needSaveState := false
	stateIdx := slices.IndexFunc(e.state.GameState, func(gs mc.GameState) bool { return gs.ID == gid })
	if stateIdx < 0 {
		e.state.GameState = append(e.state.GameState, mc.GameState{ID: gid, LastTurn: r.Turn, ActiveTurn: r.Turn})
		stateIdx = len(e.state.GameState) - 1
		needSaveState = true
	}
	if e.state.ActiveGameID == nil {
		e.state.ActiveGameID = new(gid)
		needSaveState = true
	}
	if e.state.GameState[stateIdx].LastTurn < r.Turn {
		e.state.GameState[stateIdx].LastTurn = r.Turn
		e.state.GameState[stateIdx].ActiveTurn = r.Turn
		needSaveState = true
	}
	if needSaveState {
		if err := e.s.SaveState(*e.state); err != nil {
			e.handlerError(err)
			return
		}
	}
	e.stateMu.Unlock()

	e.setReport(r)
}

func (e *client) setReport(r report.Report) {
	w := world.NewWorld(int(r.Width), int(r.Height))
	e.reg.clear(&r)
	for i := range r.LocalPlanet {
		p := r.LocalPlanet[i]
		id, err := w.AddCircle(p.X.F(), p.Y.F(), p.Size.F(), world.CircleWithClass(world.CircleClassLocalPlanet))
		if err != nil {
			e.handlerError(err)
			return
		}
		e.reg.registerLocalPlanet(id, i)
	}
	for i := range r.UnidentifiedPlanet {
		p := r.UnidentifiedPlanet[i]
		id, err := w.AddPoint(p.X.F(), p.Y.F(), world.PointWithClass(world.PointClassTrackIncoming))
		if err != nil {
			e.handlerError(err)
			return
		}
		e.reg.registerUnidentifiedPlanet(id, i)
	}
	e.loadWorld(w)

	selfIdx := slices.IndexFunc(r.Player, func(p report.Player) bool { return p.Name == r.Race })
	if selfIdx >= 0 {
		fyne.Do(func() {
			e.calculator.Init(
				calculator.WithPlayerDrives(r.Player[selfIdx].Drive.F()),
				calculator.WithPlayerWeapons(r.Player[selfIdx].Weapons.F()),
				calculator.WithPlayerShields(r.Player[selfIdx].Shields.F()),
				calculator.WithPlayerCargo(r.Player[selfIdx].Cargo.F()),
			)
		})
	} else {
		e.OnServiceError(fmt.Errorf("race %q not found at report players list", r.Race))
	}
}
