package rove

import (
	"encoding/json"
	"testing"

	"github.com/mdiluz/rove/pkg/maths"
	"github.com/mdiluz/rove/proto/roveapi"
	"github.com/stretchr/testify/assert"
)

func TestCommand_Invalid(t *testing.T) {
	w := NewWorld(8)
	name, err := w.SpawnRover("")
	assert.NoError(t, err)

	err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_none})
	assert.Error(t, err)
}

func TestCommand_Toggle(t *testing.T) {
	w := NewWorld(8)
	a, err := w.SpawnRover("")
	assert.NoError(t, err)

	r, err := w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)

	err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle})
	assert.NoError(t, err)
	w.Tick()

	r, err = w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.SailPosition_CatchingWind, r.SailPosition)

	err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_toggle})
	assert.NoError(t, err)
	w.Tick()

	r, err = w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)
}

func TestCommand_Turn(t *testing.T) {
	w := NewWorld(8)
	a, err := w.SpawnRover("")
	assert.NoError(t, err)

	err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_turn, Bearing: roveapi.Bearing_NorthWest})
	assert.NoError(t, err)
	w.Tick()

	r, err := w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.Bearing_NorthWest, r.Bearing)
}

func TestCommand_Stash(t *testing.T) {
	w := NewWorld(8)
	name, err := w.SpawnRover("")
	assert.NoError(t, err)

	info, err := w.GetRover(name)
	assert.NoError(t, err)
	assert.Empty(t, info.Inventory)

	// Drop a pickup below us
	w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RockSmall})

	// Try and stash it
	err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_stash})
	assert.NoError(t, err)
	w.Tick()

	// Check we now have it in the inventory
	info, err = w.GetRover(name)
	assert.NoError(t, err)
	assert.Equal(t, 1, len(info.Inventory))
	assert.Equal(t, Object{Type: roveapi.Object_RockSmall}, info.Inventory[0])

	// Check it's no longer on the atlas
	_, obj := w.Atlas.QueryPosition(info.Pos)
	assert.Equal(t, Object{Type: roveapi.Object_ObjectUnknown}, obj)
}

func TestCommand_Repair(t *testing.T) {
	w := NewWorld(8)
	name, err := w.SpawnRover("")
	assert.NoError(t, err)

	info, err := w.GetRover(name)
	assert.NoError(t, err)
	assert.Equal(t, info.MaximumIntegrity, info.Integrity)

	// Put a blocking rock to the north
	w.Atlas.SetObject(info.Pos.Added(maths.Vector{X: 0, Y: 1}), Object{Type: roveapi.Object_RockLarge})

	// Try and move and make sure we're blocked
	newpos, err := w.TryMoveRover(name, roveapi.Bearing_North)
	assert.NoError(t, err)
	assert.Equal(t, info.Pos, newpos)

	// Check we're damaged
	info, err = w.GetRover(name)
	assert.NoError(t, err)
	assert.Equal(t, info.MaximumIntegrity-1, info.Integrity)

	// Stash a repair object
	w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RoverParts})
	obj, err := w.RoverStash(name)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.Object_RoverParts, obj)

	// Enqueue the repair and tick
	err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_repair})
	assert.NoError(t, err)
	w.Tick()

	// Check we're repaired
	info, err = w.GetRover(name)
	assert.NoError(t, err)
	assert.Equal(t, info.MaximumIntegrity, info.Integrity)
	assert.Equal(t, 0, len(info.Inventory))
}

func TestCommand_Broadcast(t *testing.T) {
	w := NewWorld(8)
	name, err := w.SpawnRover("")
	assert.NoError(t, err)

	// Enqueue the broadcast and tick
	err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_broadcast, Data: []byte("ABC")})
	assert.NoError(t, err)
	w.Tick()

	info, err := w.GetRover(name)
	assert.NoError(t, err)
	assert.Contains(t, info.Logs[len(info.Logs)-1].Text, "ABC")
}

func TestCommand_Salvage(t *testing.T) {
	w := NewWorld(8)
	name, err := w.SpawnRover("")
	assert.NoError(t, err)

	info, err := w.GetRover(name)
	assert.NoError(t, err)

	w.Atlas.SetObject(info.Pos, Object{Type: roveapi.Object_RoverDormant})

	// Enqueue the broadcast and tick
	err = w.Enqueue(name, &roveapi.Command{Command: roveapi.CommandType_salvage})
	assert.NoError(t, err)
	w.Tick()

	// Check we now have some rover parts
	info, err = w.GetRover(name)
	assert.NoError(t, err)
	assert.NotEmpty(t, info.Inventory)
	for _, i := range info.Inventory {
		assert.Equal(t, roveapi.Object_RoverParts, i.Type)
	}

	// Check the dormant rover is gone
	_, obj := w.Atlas.QueryPosition(info.Pos)
	assert.Equal(t, roveapi.Object_ObjectUnknown, obj.Type)
}

func TestCommand_Transfer(t *testing.T) {
	w := NewWorld(8)
	acc, err := w.Accountant.RegisterAccount("tmp")
	assert.NoError(t, err)
	nameA, err := w.SpawnRover(acc.Name)
	assert.NoError(t, err)

	infoA, err := w.GetRover(nameA)
	assert.NoError(t, err)

	// Drop a dormant rover on the current position
	infoB := DefaultRover()
	infoB.Name = "abc"
	infoB.Pos = infoA.Pos
	data, err := json.Marshal(infoB)
	assert.NoError(t, err)
	w.Atlas.SetObject(infoA.Pos, Object{Type: roveapi.Object_RoverDormant, Data: data})

	// Enqueue a transfer as well as a dud command
	err = w.Enqueue(nameA,
		&roveapi.Command{Command: roveapi.CommandType_transfer},
		&roveapi.Command{Command: roveapi.CommandType_broadcast, Data: []byte("xyz")})
	assert.NoError(t, err)
	w.Tick()

	// Ensure both command queues are empty
	assert.Empty(t, w.CommandQueue[nameA])
	assert.Empty(t, w.CommandQueue[infoB.Name])

	// Verify the account now controls the new rover
	accountRover, err := w.Accountant.GetValue(acc.Name, "rover")
	assert.NoError(t, err)
	assert.Equal(t, infoB.Name, accountRover)

	// Verify the position now has a dormant rover
	_, obj := w.Atlas.QueryPosition(infoA.Pos)
	assert.Equal(t, roveapi.Object_RoverDormant, obj.Type)

	// Verify the stored data matches
	var stored Rover
	err = json.Unmarshal(obj.Data, &stored)
	assert.NoError(t, err)
	assert.Equal(t, infoA.Name, stored.Name)

	// Verify the new rover data matches what we put in
	infoB2, err := w.GetRover(infoB.Name)
	assert.NoError(t, err)
	assert.Equal(t, infoB.Name, infoB2.Name)

}

func TestCommand_Wait(t *testing.T) {
	w := NewWorld(8)
	a, err := w.SpawnRover("")
	assert.NoError(t, err)

	r, err := w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)

	err = w.Enqueue(a, &roveapi.Command{Command: roveapi.CommandType_wait, Repeat: 4}, &roveapi.Command{Command: roveapi.CommandType_toggle})
	assert.NoError(t, err)

	// Tick 5 times during the wait (1 normal execute + 4)
	for i := 0; i < 5; i++ {
		w.Tick()

		r, err = w.GetRover(a)
		assert.NoError(t, err)
		assert.Equal(t, roveapi.SailPosition_SolarCharging, r.SailPosition)
	}

	// One last tick to do the toggle
	w.Tick()

	r, err = w.GetRover(a)
	assert.NoError(t, err)
	assert.Equal(t, roveapi.SailPosition_CatchingWind, r.SailPosition)
}
