package main

import "math/rand"

// thousandNamePool — имена ботов в 1000. Отдельный пул — мужские
// имена для разнообразия среди игр коллекции.
var thousandNamePool = []string{
	"Виктор", "Роман", "Максим", "Артём", "Кирилл", "Егор", "Денис", "Павел",
}

// thousandNamePoolEN — англоязычная версия того же пула.
var thousandNamePoolEN = []string{
	"Victor", "Roman", "Max", "Arthur", "Cyril", "Eric", "Dennis", "Paul",
}

func activeThousandNamePool() []string {
	if CurrentLang() == LangEN {
		return thousandNamePoolEN
	}
	return thousandNamePool
}

func pickThousandBotName(used map[string]bool) string {
	pool := activeThousandNamePool()
	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))]
}

func newThousandBotNames(n int) []string {
	used := make(map[string]bool, n)
	names := make([]string, n)
	for i := 0; i < n; i++ {
		name := pickThousandBotName(used)
		used[name] = true
		names[i] = name
	}
	return names
}

// ThousandBot — единственная (без градации уровней) стратегия
// бота для 1000: оценивает силу руки для торгов, сбрасывает
// заведомо слабые карты, старается объявлять марьяжи и играть
// дёшево там, где нельзя выиграть взятку.
type ThousandBot struct{}

// estimateHandStrength грубо оценивает, сколько очков реалистично
// набрать с такой рукой: сумма очков карт на руке плюс стоимость
// лучшего марьяжа (марьяж считается один раз, даже если их
// несколько, — консервативная оценка).
func estimateHandStrength(hand []Card) int {
	sum := 0
	for _, c := range hand {
		sum += thousandCardValue(c.Rank)
	}
	return sum + bestMarriageValue(hand)
}

// DecideBid решает, поднимать ли ставку в текущих торгах и до
// какой суммы, либо пасовать. Возвращает (amount, true), если нужно
// вызвать g.Bid(amount), иначе (_, false) для g.Pass().
func (ThousandBot) DecideBid(g *ThousandGameState) (int, bool) {
	p := g.current()
	estimate := estimateHandStrength(p.Hand)
	capAmt := g.maxBidFor(g.CurrentBidderIdx)

	minRequired := g.HighBid + 5
	mandatory := g.bidsMade == 0 && g.CurrentBidderIdx == g.firstBidderIdx
	if mandatory {
		minRequired = 100
	}

	if minRequired > capAmt {
		if mandatory {
			return 100, true
		}
		return 0, false
	}
	if estimate+10 < minRequired && !mandatory {
		return 0, false
	}
	bid := minRequired
	if bid > capAmt {
		bid = capAmt
	}
	return bid, true
}

// DecideDiscards выбирает 2 карты для сноса сопернику: самые
// дешёвые карты руки, по возможности не разбивая марьяж.
func (ThousandBot) DecideDiscards(hand []Card) [2]Card {
	type scored struct {
		card Card
		cost int
	}
	list := make([]scored, len(hand))
	for i, c := range hand {
		cost := thousandCardValue(c.Rank)
		if (c.Rank == King || c.Rank == Queen) && hasMarriage(hand, c.Suit) {
			cost += 1000
		}
		list[i] = scored{card: c, cost: cost}
	}
	for i := 0; i < len(list); i++ {
		for j := i + 1; j < len(list); j++ {
			if list[j].cost < list[i].cost {
				list[i], list[j] = list[j], list[i]
			}
		}
	}
	return [2]Card{list[0].card, list[1].card}
}

// DecidePlay решает, какую карту сыграть в текущей взятке: если
// ходит первым и есть необъявленный марьяж (не на первой взятке
// раздачи) — предпочитает объявить самый дорогой; иначе играет
// самую дешёвую из разрешённых карт (по возможности выигрышную
// дёшево, а если выиграть нельзя — минимальные потери).
func (ThousandBot) DecidePlay(g *ThousandGameState) Card {
	actor := g.currentTrickActor()
	hand := g.Players[actor].Hand

	leading := len(g.CurrentTrick) == 0
	if leading && g.TricksPlayed > 0 {
		bestSuit := Suit(-1)
		bestValue := -1
		for s := Clubs; s <= Spades; s++ {
			if hasMarriage(hand, s) {
				if v := thousandMarriageValue[s]; v > bestValue {
					bestValue = v
					bestSuit = s
				}
			}
		}
		if bestValue >= 0 {
			return Card{Suit: bestSuit, Rank: King}
		}
	}

	var legal []Card
	for _, c := range hand {
		if leading || g.isValidPlay(hand, c) {
			legal = append(legal, c)
		}
	}
	if len(legal) == 0 {
		legal = hand
	}

	if leading {
		return weakestByThousandRank(legal)
	}

	led, _ := g.ledSuit()
	currentBest := g.CurrentTrick[0]
	bestIsTrump := g.TrumpSet && currentBest.Card.Suit == g.TrumpSuit
	for _, play := range g.CurrentTrick[1:] {
		isTrump := g.TrumpSet && play.Card.Suit == g.TrumpSuit
		if (isTrump && !bestIsTrump) ||
			(isTrump == bestIsTrump && play.Card.Suit == currentBest.Card.Suit &&
				thousandRankOrder[play.Card.Rank] > thousandRankOrder[currentBest.Card.Rank]) {
			currentBest = play
			bestIsTrump = isTrump
		}
	}

	var winners []Card
	for _, c := range legal {
		isTrump := g.TrumpSet && c.Suit == g.TrumpSuit
		wins := false
		switch {
		case isTrump && !bestIsTrump:
			wins = true
		case isTrump == bestIsTrump && c.Suit == currentBest.Card.Suit &&
			thousandRankOrder[c.Rank] > thousandRankOrder[currentBest.Card.Rank]:
			wins = true
		case !bestIsTrump && c.Suit == led && currentBest.Card.Suit != led:
			wins = true
		}
		if wins {
			winners = append(winners, c)
		}
	}
	if len(winners) > 0 {
		return weakestByThousandRank(winners)
	}
	return weakestByThousandRank(legal)
}

// weakestByThousandRank находит карту с наименьшим старшинством в
// наборе (при равенстве — с меньшей очковой стоимостью).
func weakestByThousandRank(cards []Card) Card {
	best := cards[0]
	for _, c := range cards[1:] {
		if thousandRankOrder[c.Rank] < thousandRankOrder[best.Rank] ||
			(thousandRankOrder[c.Rank] == thousandRankOrder[best.Rank] && thousandCardValue(c.Rank) < thousandCardValue(best.Rank)) {
			best = c
		}
	}
	return best
}

// PlayFullTurn исполняет одно решение бота (в контексте 1000 —
// один шаг торгов, снос, или один ход во взятке) — используется
// при автоматической прокрутке партии.
func (b ThousandBot) PlayFullTurn(g *ThousandGameState) error {
	switch g.Phase {
	case ThousandPhaseBidding:
		if amount, ok := b.DecideBid(g); ok {
			return g.Bid(amount)
		}
		return g.Pass()
	case ThousandPhaseDiscard:
		discards := b.DecideDiscards(g.Players[g.DeclarerIdx].Hand)
		if err := g.DiscardCard(discards[0]); err != nil {
			return err
		}
		if g.Phase != ThousandPhaseDiscard {
			return nil
		}
		return g.DiscardCard(discards[1])
	case ThousandPhaseTrick:
		card := b.DecidePlay(g)
		return g.PlayCard(card)
	}
	return nil
}
