package main

import "math/rand"

// durakNamePool — имена ботов в Дураке. Отдельный пул от Тонка
// (там прозвища, здесь — обычные русские имена), но по той же
// логике: уровень игры бота нигде не отражается в имени, а он тут
// и всего один (DurakBot{}) — градации сложности нет.
var durakNamePool = []string{
	"Иван", "Пётр", "Алексей", "Дмитрий", "Сергей", "Николай", "Андрей", "Михаил",
}

// durakNamePoolEN — англоязычная версия того же пула.
var durakNamePoolEN = []string{
	"John", "Peter", "Alex", "James", "Simon", "Nicholas", "Andrew", "Michael",
}

func activeDurakNamePool() []string {
	if CurrentLang() == LangEN {
		return durakNamePoolEN
	}
	return durakNamePool
}

// pickDurakBotName выбирает случайное имя из активного (для
// текущего языка) пула, по возможности не повторяющее уже занятые
// (used).
func pickDurakBotName(used map[string]bool) string {
	pool := activeDurakNamePool()
	free := make([]string, 0, len(pool))
	for _, name := range pool {
		if !used[name] {
			free = append(free, name)
		}
	}
	if len(free) == 0 {
		free = pool
	}
	return free[rand.Intn(len(free))]
}

// newDurakBotNames возвращает n случайных (без повторов, если
// хватает пула) имён ботов для Дурака.
func newDurakBotNames(n int) []string {
	used := make(map[string]bool, n)
	names := make([]string, n)
	for i := 0; i < n; i++ {
		name := pickDurakBotName(used)
		used[name] = true
		names[i] = name
	}
	return names
}

// DurakBot — единственная (без градации уровней) стратегия бота
// для Дурака: разумная, но не идеальная игра. Бережёт козыри,
// старается не подставляться и подкидывает только тогда, когда это
// низкорисково.
type DurakBot struct{}

// DecideAttack решает, какой картой открыть раунд. Вызывается
// только в фазе DurakPhaseAttack — это обязательный первый ход,
// отказаться нельзя, поэтому второе возвращаемое значение всегда
// true, если на руке вообще есть карты (иначе бот не должен был
// стать атакующим в принципе).
func (DurakBot) DecideAttack(g *DurakGameState) (Card, bool) {
	hand := g.attacker().Hand
	if len(hand) == 0 {
		return Card{}, false
	}
	return weakestCard(hand, g.TrumpSuit, true), true
}

// DecideThrowOrPass решает, что делать игроку, чья сейчас очередь в
// фазе DurakPhaseThrowIn: подкинуть карту подходящего ранга или
// пасовать. Возвращает (card, true), если нужно подкинуть card,
// иначе (_, false), если нужно пасовать (вызывающий код вызывает
// g.Pass()).
func (DurakBot) DecideThrowOrPass(g *DurakGameState) (Card, bool) {
	throwerIdx := g.CurrentThrower()
	if throwerIdx == -1 {
		return Card{}, false
	}
	hand := g.Players[throwerIdx].Hand
	if len(g.Table) >= g.attackCap {
		return Card{}, false
	}

	var candidates []Card
	for _, c := range hand {
		if g.tableHasRank(c.Rank) {
			candidates = append(candidates, c)
		}
	}
	if len(candidates) == 0 {
		return Card{}, false
	}
	return weakestCard(candidates, g.TrumpSuit, true), true
}

// DecideDefend решает, чем отбиваться от последней неотбитой карты
// на столе. Возвращает (card, true), если нужно отбить card, иначе
// (_, false), если нужно забрать стол (вызывающий код вызывает
// g.Take()). Вызывать имеет смысл только когда Phase ==
// DurakPhaseDefend.
func (DurakBot) DecideDefend(g *DurakGameState) (Card, bool) {
	if len(g.Table) == 0 {
		return Card{}, false
	}
	attackCard := g.Table[len(g.Table)-1].Attack
	hand := g.defender().Hand

	var best *Card
	for i, c := range hand {
		if !durakBeats(c, attackCard, g.TrumpSuit) {
			continue
		}
		if best == nil || durakDefendCost(c, g.TrumpSuit) < durakDefendCost(*best, g.TrumpSuit) {
			cc := hand[i]
			best = &cc
		}
	}
	if best == nil {
		return Card{}, false
	}
	return *best, true
}

// weakestCard находит самую "дешёвую" карту в наборе: если
// preferNonTrump=true и есть некозырные карты, ищет минимум среди
// них; иначе ищет минимум по всему набору. "Минимум" — по
// старшинству в Дураке (durakRankOrder).
func weakestCard(cards []Card, trump Suit, preferNonTrump bool) Card {
	pool := cards
	if preferNonTrump {
		var nonTrump []Card
		for _, c := range cards {
			if c.Suit != trump {
				nonTrump = append(nonTrump, c)
			}
		}
		if len(nonTrump) > 0 {
			pool = nonTrump
		}
	}
	best := pool[0]
	for _, c := range pool[1:] {
		if durakRankOrder[c.Rank] < durakRankOrder[best.Rank] {
			best = c
		}
	}
	return best
}

// durakDefendCost оценивает "стоимость" использования карты для
// защиты: некозырные карты дешевле козырей (козыри — ценный
// ресурс, тратить их на отбой лишний раз не хочется), а внутри
// каждой категории дешевле карты младше.
func durakDefendCost(c Card, trump Suit) int {
	base := durakRankOrder[c.Rank]
	if c.Suit == trump {
		return base + 100 // козырь всегда "дороже" любой некозырной карты
	}
	return base
}

// PlayFullTurn исполняет одно решение бота: подкинуть/отбиться/
// пасовать/забрать стол — используется при автоматической прокрутке
// партии. Возвращает ошибку только если что-то пошло не так на
// уровне игровой логики (не должно случаться при корректной работе
// решающих функций).
func (b DurakBot) PlayFullTurn(g *DurakGameState) error {
	switch g.Phase {
	case DurakPhaseAttack:
		if card, ok := b.DecideAttack(g); ok {
			return g.ThrowIn(card)
		}
		return nil // не должно происходить: у атакующего не может не быть карт
	case DurakPhaseDefend:
		if card, ok := b.DecideDefend(g); ok {
			return g.Defend(card)
		}
		return g.Take()
	case DurakPhaseThrowIn:
		if card, ok := b.DecideThrowOrPass(g); ok {
			return g.ThrowIn(card)
		}
		return g.Pass()
	}
	return nil
}
