package scrabble

import (
	"testing"

	"github.com/iliadenisov/alphabet"

	"gitea.iliadenisov.ru/developer/scrabble-solver/board"
	"gitea.iliadenisov.ru/developer/scrabble-solver/dictdawg"
	"gitea.iliadenisov.ru/developer/scrabble-solver/internal/encoding"
	"gitea.iliadenisov.ru/developer/scrabble-solver/rack"
	"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
	"gitea.iliadenisov.ru/developer/scrabble-solver/wordlist"
)

func makeRack(letters string, blanks int) rack.Rack {
	r := rack.New(26)
	for i := range len(letters) {
		r.Add(letters[i] - 'a')
	}
	for range blanks {
		r.AddBlank()
	}
	return r
}

func placeWord(b *board.Board, r, c int, dir Direction, word string) {
	for i := range len(word) {
		rr, cc := r, c+i
		if dir == Vertical {
			rr, cc = r+i, c
		}
		b.Set(rr, cc, encoding.Cell(word[i]-'a', false))
	}
}

func genMoves(moves []Move) map[string]Move {
	out := make(map[string]Move, len(moves))
	for _, m := range moves {
		out[moveKey(m.Dir, m.Tiles)] = m
	}
	return out
}

// testWords is a small lexicon with enough overlaps to form across and cross plays.
var testWords = []string{
	"aa", "ace", "act", "arc", "are", "art", "as", "at", "ate",
	"cab", "cap", "car", "care", "cars", "cart", "cat", "cats", "cot",
	"oat", "oats", "ta", "tar", "tare", "tat", "tea", "teat",
}

func compareToBrute(t *testing.T, name string, gen Generator, b *board.Board, d dict, rk rack.Rack, mode Mode) {
	t.Helper()
	want := bruteForce(b, plainRulesShared, d, rk, mode)
	got := genMoves(gen.GenerateMoves(b, rk, mode))

	for k, wm := range want {
		gm, ok := got[k]
		if !ok {
			t.Errorf("%s [%s]: %s missing %s (score %d)", name, gen.Name(), gen.Name(), k, wm.Score)
			continue
		}
		if gm.Score != wm.Score {
			t.Errorf("%s [%s]: %s score %d, want %d", name, gen.Name(), k, gm.Score, wm.Score)
		}
	}
	for k := range got {
		if _, ok := want[k]; !ok {
			t.Errorf("%s [%s]: extra move %s", name, gen.Name(), k)
		}
	}
	if len(got) != len(want) {
		t.Errorf("%s [%s]: %d moves, oracle has %d", name, gen.Name(), len(got), len(want))
	}
}

func mustPlainRules() *rules.Ruleset {
	eng := rules.English()
	rs, err := rules.FromTemplate("plain7", eng.Alphabet, eng.Values, eng.Counts, 2, 7, 50, plain7)
	if err != nil {
		panic(err)
	}
	return rs
}

var plainRulesShared = mustPlainRules()

type scenario struct {
	name  string
	setup func(*board.Board)
	rack  rack.Rack
	mode  Mode
}

func genScenarios() []scenario {
	return []scenario{
		{"first move", func(*board.Board) {}, makeRack("cat", 0), Both},
		{"first move blank", func(*board.Board) {}, makeRack("ca", 1), Both},
		{"extend cat", func(b *board.Board) { placeWord(b, 3, 1, Horizontal, "cat") }, makeRack("srs", 0), Both},
		{"cross cat", func(b *board.Board) { placeWord(b, 1, 3, Horizontal, "cat") }, makeRack("aort", 0), Both},
		{"only horizontal", func(b *board.Board) { placeWord(b, 3, 1, Horizontal, "cat") }, makeRack("aser", 0), OnlyHorizontal},
		{"only vertical", func(b *board.Board) { placeWord(b, 1, 3, Vertical, "cat") }, makeRack("aser", 0), OnlyVertical},
	}
}

func TestDAWGGeneratorVsBruteForce(t *testing.T) {
	rs := plainRulesShared
	words := wordlist.Encode(testWords, alphabet.Latin(), 2, 15)
	f, err := dictdawg.Build(alphabet.Latin(), words)
	if err != nil {
		t.Fatal(err)
	}
	gen := NewDAWGGenerator(rs, f)
	d := makeDict(words)

	for _, c := range genScenarios() {
		b := board.New(rs.Rows, rs.Cols)
		c.setup(b)
		compareToBrute(t, c.name, gen, b, d, c.rack, c.mode)
	}
}
