package engine

import "core:testing"

make_test_character :: proc(allocator := context.allocator) -> Character_Data {
	data: Character_Data
	data.def.clips = make(map[string]Clip_Def, allocator)

	idle_frames := make([]Frame_Def, 3, allocator)
	idle_frames[0] = {
		rect = {0, 0, 10, 10},
	}
	idle_frames[1] = {
		rect = {10, 0, 10, 10},
	}
	idle_frames[2] = {
		rect = {20, 0, 10, 10},
	}
	data.def.clips["idle"] = Clip_Def {
		loop   = true,
		fps    = 10,
		frames = idle_frames,
	}

	once_frames := make([]Frame_Def, 3, allocator)
	once_frames[0] = {
		rect = {0, 10, 10, 10},
	}
	once_frames[1] = {
		rect = {10, 10, 10, 10},
	}
	once_frames[2] = {
		rect = {20, 10, 10, 10},
	}
	data.def.clips["once"] = Clip_Def {
		loop   = false,
		fps    = 10,
		frames = once_frames,
	}

	hold_frames := make([]Frame_Def, 2, allocator)
	hold_frames[0] = {
		rect = {0, 20, 10, 10},
	}
	hold_frames[1] = {
		rect = {10, 20, 10, 10},
	}
	data.def.clips["hold"] = Clip_Def {
		loop   = true,
		fps    = 0,
		frames = hold_frames,
	}

	return data
}

destroy_test_character :: proc(data: ^Character_Data) {
	if data == nil do return
	keys := make([dynamic]string, context.temp_allocator)
	for key, clip in data.def.clips {
		delete(clip.frames)
		append(&keys, key)
	}
	for key in keys {
		delete_key(&data.def.clips, key)
	}
	delete(data.def.clips)
	data^ = {}
}

@(test)
character_clip_found_and_missing :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	clip, ok := character_clip(&data, "idle")
	testing.expect(t, ok, "character_clip should find idle")
	testing.expect_value(t, len(clip.frames), 3)
	testing.expect(t, clip.loop, "idle clip should loop")
	testing.expect_value(t, clip.fps, f32(10))

	_, ok = character_clip(&data, "missing")
	testing.expect(t, !ok, "character_clip should miss unknown name")

	data.def.clips["empty"] = Clip_Def {
		loop   = true,
		fps    = 10,
		frames = nil,
	}
	_, ok = character_clip(&data, "empty")
	testing.expect(t, !ok, "character_clip should reject empty frames")
}

@(test)
set_sprite_clip_switch_and_guards :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	sprite := spawn_sprite(&data, {0, 0}, "idle", 0)
	testing.expect_value(t, sprite.clip, "idle")

	sprite.frame = 2
	sprite.time = 0.05
	set_sprite_clip(&sprite, "once")
	testing.expect_value(t, sprite.clip, "once")
	testing.expect_value(t, sprite.frame, 0)
	testing.expect_value(t, sprite.time, f32(0))

	sprite.frame = 1
	sprite.time = 0.09
	set_sprite_clip(&sprite, "once")
	testing.expect_value(t, sprite.frame, 1)
	testing.expect_value(t, sprite.time, f32(0.09))

	set_sprite_clip(&sprite, "nope")
	testing.expect_value(t, sprite.clip, "once")
	testing.expect_value(t, sprite.frame, 1)
	testing.expect_value(t, sprite.time, f32(0.09))
}

@(test)
set_sprite_clip_caches_clip_def :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	sprite := spawn_sprite(&data, {}, "idle", 0)
	testing.expect(t, sprite.has_clip, "spawn should cache a valid clip")
	testing.expect_value(t, sprite.clip, "idle")
	testing.expect(t, sprite.clip_def.loop, "idle clip loops")
	testing.expect_value(t, sprite.clip_def.fps, f32(10))
	testing.expect_value(t, len(sprite.clip_def.frames), 3)

	set_sprite_clip(&sprite, "once")
	testing.expect(t, sprite.has_clip, "switch should refresh cache")
	testing.expect_value(t, sprite.clip, "once")
	testing.expect(t, !sprite.clip_def.loop, "once clip does not loop")
	testing.expect_value(t, sprite.clip_def.fps, f32(10))
	testing.expect_value(t, len(sprite.clip_def.frames), 3)

	set_sprite_clip(&sprite, "nope")
	testing.expect(t, sprite.has_clip, "bad clip must leave cache intact")
	testing.expect_value(t, sprite.clip, "once")
	testing.expect(t, !sprite.clip_def.loop, "cached once clip preserved")
	testing.expect_value(t, len(sprite.clip_def.frames), 3)
}

@(test)
set_sprite_clip_def_applies_and_guards :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	sprite := spawn_sprite(&data, {}, "idle", 0)
	sprite.frame = 2
	sprite.time = 0.05

	once := data.def.clips["once"]
	set_sprite_clip_def(&sprite, "once", once)
	testing.expect_value(t, sprite.clip, "once")
	testing.expect(t, sprite.has_clip, "def apply should mark clip present")
	testing.expect_value(t, sprite.frame, 0)
	testing.expect_value(t, sprite.time, f32(0))
	testing.expect(t, !sprite.clip_def.loop, "once clip does not loop")
	testing.expect_value(t, len(sprite.clip_def.frames), 3)

	sprite.frame = 1
	sprite.time = 0.09
	set_sprite_clip_def(&sprite, "once", once)
	testing.expect_value(t, sprite.frame, 1)
	testing.expect_value(t, sprite.time, f32(0.09))

	set_sprite_clip_def(&sprite, "empty", Clip_Def{loop = true, fps = 10, frames = nil})
	testing.expect_value(t, sprite.clip, "once")
	testing.expect_value(t, sprite.frame, 1)

	set_sprite_clip_def(nil, "once", once)
}

@(test)
spawn_sprite_valid_and_invalid :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {1, 2}, "idle", 0)
	testing.expect_value(t, s.clip, "idle")
	testing.expect_value(t, s.frame, 0)
	testing.expect_value(t, s.time, f32(0))
	testing.expect_value(t, s.position, Vec2{1, 2})

	s2 := spawn_sprite(&data, {}, "idle", 2)
	testing.expect_value(t, s2.clip, "idle")
	testing.expect_value(t, s2.frame, 2)

	s3 := spawn_sprite(&data, {}, "idle", 99)
	testing.expect_value(t, s3.frame, 2)

	bad := spawn_sprite(&data, {}, "missing", 0)
	testing.expect_value(t, bad.clip, "")
	testing.expect_value(t, bad.frame, 0)
	testing.expect_value(t, bad.time, f32(0))
}

@(test)
update_sprite_advances_one_frame :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "idle", 0)
	update_sprite(&s, 0.1)
	testing.expect_value(t, s.frame, 1)
	testing.expect_value(t, s.time, f32(0))
}

@(test)
update_sprite_advances_multiple_frames :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "idle", 0)
	update_sprite(&s, 0.25)
	testing.expect_value(t, s.frame, 2)
	testing.expect(t, s.time > 0.049 && s.time < 0.051, "leftover time after multi-frame advance should be ~0.05")
}

@(test)
update_sprite_loops :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "idle", 2)
	update_sprite(&s, 0.1)
	testing.expect_value(t, s.frame, 0)
}

@(test)
update_sprite_non_loop_holds_last :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "once", 2)
	s.time = 0.05
	update_sprite(&s, 0.1)
	testing.expect_value(t, s.frame, 2)
	testing.expect_value(t, s.time, f32(0))
}

@(test)
update_sprite_fps_zero_holds :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "hold", 0)
	update_sprite(&s, 1.0)
	testing.expect_value(t, s.frame, 0)
}

@(test)
update_sprite_dt_non_positive_noop :: proc(t: ^testing.T) {
	data := make_test_character()
	defer destroy_test_character(&data)

	s := spawn_sprite(&data, {}, "idle", 0)
	update_sprite(&s, 0)
	update_sprite(&s, -0.1)
	testing.expect_value(t, s.frame, 0)
	testing.expect_value(t, s.time, f32(0))
}

@(test)
update_sprite_nil_and_missing_clip_safe :: proc(t: ^testing.T) {
	update_sprite(nil, 0.1)

	data := make_test_character()
	defer destroy_test_character(&data)

	s := Sprite {
		data  = &data,
		clip  = "missing",
		frame = 0,
		time  = 0,
	}
	update_sprite(&s, 0.1)
	testing.expect_value(t, s.frame, 0)

	s2 := Sprite {
		data = nil,
		clip = "idle",
	}
	update_sprite(&s2, 0.1)
}
