package main

import "errors"

// DurakPlayer — состояние одного игрока в партии Дурак.
type DurakPlayer struct {
	Name    string
	Hand    []Card
	IsHuman bool
	Out     bool // "отбился насовсем": карт нет и колода пуста
}

// durakTablePair — одна атакующая карта на столе и (если есть)
// карта, которой её отбили.
type durakTablePair struct {
	Attack Card
	Defend *Card
}

// DurakPhase — чья сейчас очередь действовать.
type DurakPhase int

const (
	// DurakPhaseAttack — самый первый ход раунда: атакующий обязан
	// подкинуть карту (стол ещё пуст, выбора "пасовать" нет).
	DurakPhaseAttack DurakPhase = iota
	// DurakPhaseDefend — защищающийся должен отбить последнюю
	// подкинутую карту либо забрать весь стол.
	DurakPhaseDefend
	// DurakPhaseThrowIn — стол полностью отбит; по очереди (см.
	// ThrowOrder/ThrowTurnPos) любой игрок, кроме защищающегося,
	// может либо подкинуть ещё одну карту подходящего ранга, либо
	// пасовать. Как только все подряд спасовали (или лимит атаки
	// исчерпан) — раунд уходит в отбой.
	DurakPhaseThrowIn
	// DurakPhaseGameOver — партия завершена.
	DurakPhaseGameOver
)

var (
	ErrDurakWrongPhase      = errors.New("недопустимое действие для текущей фазы хода")
	ErrDurakCardNotInHand   = errors.New("карты нет на руке игрока")
	ErrDurakInvalidThrowIn  = errors.New("эту карту нельзя подкинуть: не тот ранг или лимит атаки исчерпан")
	ErrDurakInvalidDefend   = errors.New("эта карта не бьёт атакующую карту")
	ErrDurakNothingToDefend = errors.New("на столе нет карты, которую нужно отбивать")
	ErrDurakEmptyTable      = errors.New("на столе нет карт")
)

// DurakResult — итог завершившейся партии.
type DurakResult struct {
	Loser     int  // индекс проигравшего ("дурака"); не имеет смысла при NoLoser/Stalemate
	NoLoser   bool // все игроки одновременно избавились от карт — без проигравшего
	Stalemate bool // партия принудительно завершена вничью защитой от зацикливания
}

// DurakGameState — полное состояние партии в Дурак.
type DurakGameState struct {
	Players []*DurakPlayer

	Talon     []Card // колода для добора; Talon[0] — верхняя (берётся первой)
	TrumpSuit Suit
	TrumpCard Card // вскрытая козырная карта — последняя в Talon, показывается отдельно

	AttackerIdx int
	DefenderIdx int
	Table       []durakTablePair
	Phase       DurakPhase
	Result      *DurakResult

	// attackCap — сколько карт максимум может оказаться на столе в
	// этом раунде: min(6, размер руки защищающегося на момент
	// начала раунда). Фиксируется один раз при начале раунда.
	attackCap int

	// ThrowOrder — порядок подкидывающих в текущем раунде: все
	// активные игроки, кроме защищающегося, начиная с атакующего, по
	// кругу. Вычисляется один раз при начале раунда.
	ThrowOrder []int
	// ThrowTurnPos — индекс в ThrowOrder, чья сейчас очередь решать
	// "подкинуть или пасовать" в фазе DurakPhaseThrowIn.
	ThrowTurnPos int
	// passedSinceThrow считает подряд идущие пасы (без единого
	// подкидывания между ними) в текущей серии решений. Когда оно
	// достигает len(ThrowOrder), все отказались — раунд уходит в
	// отбой.
	passedSinceThrow int

	// takesSinceBito считает подряд идущие завершения раундов через
	// "взятие" (без единого "бито" между ними). Когда карты на столе
	// не выходят из игры (только перекладываются между руками),
	// партия теоретически может зациклиться навсегда — это защита
	// от такого зацикливания.
	takesSinceBito int
}

// maxTakesWithoutBito — порог принудительного завершения партии
// вничью (Stalemate), если очень долго не происходит ни одного
// "бито" подряд (карты не выходят из игры).
const maxTakesWithoutBito = 200

// NewDurakGame создаёт новую партию: тасует колоду на 36 карт,
// раздаёт по 6 карт (кому-то может достаться меньше — например, при
// 6 игроках колода не делится ровно), вскрывает козырь и определяет
// первого атакующего (у кого на руке самый младший козырь).
func NewDurakGame(names []string, humanIdx int) *DurakGameState {
	deck := NewDurakDeck()
	deck.Shuffle()

	trumpCard := deck.cards[len(deck.cards)-1]

	players := make([]*DurakPlayer, len(names))
	for i, name := range names {
		players[i] = &DurakPlayer{Name: name, IsHuman: i == humanIdx}
	}

	g := &DurakGameState{
		Players:   players,
		Talon:     deck.cards,
		TrumpSuit: trumpCard.Suit,
		TrumpCard: trumpCard,
	}

	// начальная раздача: раунд-робин по одной карте, пока у всех не
	// станет по 6 либо в колоде не останется только козырная карта
	for dealt := true; dealt; {
		dealt = false
		for _, p := range g.Players {
			if len(p.Hand) < 6 && len(g.Talon) > 1 {
				p.Hand = append(p.Hand, g.drawReserveTrump())
				dealt = true
			}
		}
	}

	g.AttackerIdx = g.findInitialAttacker()
	g.DefenderIdx = g.nextActive(g.AttackerIdx)
	g.startNewBout()
	return g
}

// startNewBout готовит служебные поля к началу нового раунда:
// лимит атаки и порядок подкидывающих, всегда исходя из текущих
// AttackerIdx/DefenderIdx.
func (g *DurakGameState) startNewBout() {
	g.attackCap = g.currentAttackCap()
	g.ThrowOrder = g.computeThrowOrder()
	g.ThrowTurnPos = 0
	g.passedSinceThrow = 0
	g.Phase = DurakPhaseAttack
}

// computeThrowOrder строит порядок подкидывающих: все активные
// игроки, кроме защищающегося, начиная с атакующего, по кругу.
func (g *DurakGameState) computeThrowOrder() []int {
	order := []int{}
	idx := g.AttackerIdx
	for {
		if idx != g.DefenderIdx {
			order = append(order, idx)
		}
		next := g.nextActive(idx)
		if next == g.AttackerIdx {
			break
		}
		idx = next
		if len(order) > len(g.Players) {
			break // защита от зацикливания в вырожденных случаях
		}
	}
	return order
}

// drawReserveTrump снимает верхнюю карту талона, никогда не трогая
// последнюю (козырную) карту, пока в талоне есть что-то ещё —
// используется только при начальной раздаче.
func (g *DurakGameState) drawReserveTrump() Card {
	c := g.Talon[0]
	g.Talon = g.Talon[1:]
	return c
}

// drawAny снимает верхнюю карту талона, включая последнюю
// (козырную), если до неё дошла очередь — используется при доборе
// карт между раундами.
func (g *DurakGameState) drawAny() Card {
	c := g.Talon[0]
	g.Talon = g.Talon[1:]
	return c
}

// findInitialAttacker находит игрока с самым младшим козырем на
// руке — он ходит первым. Если ни у кого нет козыря (редкий случай
// при небольшом числе игроков), первым ходит игрок 0.
func (g *DurakGameState) findInitialAttacker() int {
	best := -1
	bestRank := 99
	for i, p := range g.Players {
		for _, c := range p.Hand {
			if c.Suit == g.TrumpSuit && durakRankOrder[c.Rank] < bestRank {
				bestRank = durakRankOrder[c.Rank]
				best = i
			}
		}
	}
	if best == -1 {
		return 0
	}
	return best
}

// nextActive возвращает индекс следующего по кругу игрока, который
// ещё не вышел из игры (Out == false).
func (g *DurakGameState) nextActive(from int) int {
	n := len(g.Players)
	for i := 1; i <= n; i++ {
		idx := (from + i) % n
		if !g.Players[idx].Out {
			return idx
		}
	}
	return from
}

func (g *DurakGameState) attacker() *DurakPlayer { return g.Players[g.AttackerIdx] }
func (g *DurakGameState) defender() *DurakPlayer { return g.Players[g.DefenderIdx] }

func (g *DurakGameState) currentAttackCap() int {
	capacity := 6
	if len(g.defender().Hand) < capacity {
		capacity = len(g.defender().Hand)
	}
	return capacity
}

// CurrentThrower возвращает индекс игрока, чья сейчас очередь
// решать "подкинуть или пасовать" в фазе DurakPhaseThrowIn. Если
// подкидывать сейчас некому (ThrowOrder пуст — вырожденный случай),
// возвращает -1.
func (g *DurakGameState) CurrentThrower() int {
	if len(g.ThrowOrder) == 0 {
		return -1
	}
	return g.ThrowOrder[g.ThrowTurnPos]
}

func hasCard(hand []Card, c Card) bool {
	for _, card := range hand {
		if card == c {
			return true
		}
	}
	return false
}

func removeCard(hand []Card, c Card) []Card {
	for i, card := range hand {
		if card == c {
			return append(hand[:i], hand[i+1:]...)
		}
	}
	return hand
}

// tableHasRank проверяет, встречается ли ранг card среди уже
// сыгранных на столе карт (атакующих и отбивающих) — именно такими
// картами можно подкидывать.
func (g *DurakGameState) tableHasRank(r Rank) bool {
	for _, pair := range g.Table {
		if pair.Attack.Rank == r {
			return true
		}
		if pair.Defend != nil && pair.Defend.Rank == r {
			return true
		}
	}
	return false
}

// hasThrowableCard проверяет, есть ли у игрока idx в руке хотя бы
// одна карта, которую сейчас реально можно подкинуть (ранг уже
// встречается на столе, и лимит атаки не превышен). Используется,
// чтобы не заставлять игрока (человека или бота) явно пасовать,
// когда подкинуть заведомо нечего — пустая рука тоже считается
// "нечего подкинуть".
func (g *DurakGameState) hasThrowableCard(idx int) bool {
	if idx < 0 || idx >= len(g.Players) {
		return false
	}
	if len(g.Table) >= g.attackCap {
		return false
	}
	for _, c := range g.Players[idx].Hand {
		if g.tableHasRank(c.Rank) {
			return true
		}
	}
	return false
}

// ThrowIn подкидывает карту на стол. В фазе DurakPhaseAttack это
// обязательный первый ход раунда (действует атакующий, стол ещё
// пуст). В фазе DurakPhaseThrowIn действует игрок из ThrowOrder,
// чья сейчас очередь (CurrentThrower) — карта должна быть рангом,
// уже встречающимся на столе.
func (g *DurakGameState) ThrowIn(card Card) error {
	switch g.Phase {
	case DurakPhaseAttack:
		return g.throwInAsAttacker(card)
	case DurakPhaseThrowIn:
		return g.throwInAsThrower(card)
	default:
		return ErrDurakWrongPhase
	}
}

func (g *DurakGameState) throwInAsAttacker(card Card) error {
	if !hasCard(g.attacker().Hand, card) {
		return ErrDurakCardNotInHand
	}
	if len(g.Table) >= g.attackCap {
		return ErrDurakInvalidThrowIn
	}
	g.attacker().Hand = removeCard(g.attacker().Hand, card)
	g.Table = append(g.Table, durakTablePair{Attack: card})
	g.Phase = DurakPhaseDefend
	return nil
}

func (g *DurakGameState) throwInAsThrower(card Card) error {
	throwerIdx := g.CurrentThrower()
	if throwerIdx == -1 {
		return ErrDurakWrongPhase
	}
	thrower := g.Players[throwerIdx]
	if !hasCard(thrower.Hand, card) {
		return ErrDurakCardNotInHand
	}
	if !g.tableHasRank(card.Rank) {
		return ErrDurakInvalidThrowIn
	}
	if len(g.Table) >= g.attackCap {
		return ErrDurakInvalidThrowIn
	}

	thrower.Hand = removeCard(thrower.Hand, card)
	g.Table = append(g.Table, durakTablePair{Attack: card})
	g.passedSinceThrow = 0
	g.advanceThrowTurn()
	g.Phase = DurakPhaseDefend
	return nil
}

// Pass — игрок, чья сейчас очередь в фазе DurakPhaseThrowIn,
// отказывается подкидывать. Если после этого все по кругу подряд
// отказались (или желающих подкидывать не осталось) — раунд уходит
// в отбой.
func (g *DurakGameState) Pass() error {
	if g.Phase != DurakPhaseThrowIn {
		return ErrDurakWrongPhase
	}
	if len(g.ThrowOrder) == 0 {
		g.resolveBito()
		return nil
	}
	g.passedSinceThrow++
	g.advanceThrowTurn()
	if g.passedSinceThrow >= len(g.ThrowOrder) {
		g.resolveBito()
	}
	return nil
}

func (g *DurakGameState) advanceThrowTurn() {
	if len(g.ThrowOrder) == 0 {
		return
	}
	g.ThrowTurnPos = (g.ThrowTurnPos + 1) % len(g.ThrowOrder)
}

// enterThrowInPhase переводит раунд в фазу подкидывания после
// успешной защиты — либо сразу завершает раунд отбоем, если лимит
// атаки уже исчерпан или подкидывать больше некому.
func (g *DurakGameState) enterThrowInPhase() {
	if len(g.Table) >= g.attackCap || len(g.ThrowOrder) == 0 {
		g.resolveBito()
		return
	}
	g.Phase = DurakPhaseThrowIn
}

// Defend — защищающийся отбивает последнюю подкинутую карту.
func (g *DurakGameState) Defend(card Card) error {
	if g.Phase != DurakPhaseDefend {
		return ErrDurakWrongPhase
	}
	if len(g.Table) == 0 {
		return ErrDurakNothingToDefend
	}
	last := &g.Table[len(g.Table)-1]
	if last.Defend != nil {
		return ErrDurakNothingToDefend
	}
	if !hasCard(g.defender().Hand, card) {
		return ErrDurakCardNotInHand
	}
	if !durakBeats(card, last.Attack, g.TrumpSuit) {
		return ErrDurakInvalidDefend
	}

	g.defender().Hand = removeCard(g.defender().Hand, card)
	last.Defend = &card
	g.enterThrowInPhase()
	return nil
}

// resolveBito завершает раунд "бито": все карты на столе выходят из
// игры навсегда, роли переходят по кругу от защищающегося.
func (g *DurakGameState) resolveBito() {
	oldAttacker, oldDefender := g.AttackerIdx, g.DefenderIdx
	g.Table = nil
	g.takesSinceBito = 0

	refillOrder := g.refillOrderFrom(oldAttacker, oldDefender)
	g.refill(refillOrder)

	g.finishBout(oldDefender)
}

// Take — защищающийся забирает весь стол себе в руку.
func (g *DurakGameState) Take() error {
	if g.Phase != DurakPhaseDefend {
		return ErrDurakWrongPhase
	}
	if len(g.Table) == 0 {
		return ErrDurakEmptyTable
	}

	for _, pair := range g.Table {
		g.defender().Hand = append(g.defender().Hand, pair.Attack)
		if pair.Defend != nil {
			g.defender().Hand = append(g.defender().Hand, *pair.Defend)
		}
	}
	oldAttacker, oldDefender := g.AttackerIdx, g.DefenderIdx
	g.Table = nil
	g.takesSinceBito++

	if g.takesSinceBito >= maxTakesWithoutBito {
		g.Result = &DurakResult{Stalemate: true}
		g.Phase = DurakPhaseGameOver
		return nil
	}

	// защищающийся забрал карты — добор ему не нужен, он и так с
	// полной рукой; добирают все остальные, начиная с атакующего
	refillOrder := g.refillOrderFrom(oldAttacker, oldDefender)
	filtered := refillOrder[:0]
	for _, idx := range refillOrder {
		if idx != oldDefender {
			filtered = append(filtered, idx)
		}
	}
	g.refill(filtered)

	g.finishBoutAfterTake(oldDefender)
	return nil
}

// refillOrderFrom строит порядок добора карт: начиная с атакующего
// раунда, по кругу, заканчивая защищающимся — учитывая только ещё
// не выбывших игроков.
func (g *DurakGameState) refillOrderFrom(attackerIdx, defenderIdx int) []int {
	order := []int{}
	seen := map[int]bool{}
	idx := attackerIdx
	for {
		if !g.Players[idx].Out && !seen[idx] {
			order = append(order, idx)
			seen[idx] = true
		}
		if idx == defenderIdx {
			break
		}
		idx = g.nextActive(idx)
		if len(order) > len(g.Players) {
			break // защита от зацикливания в вырожденных случаях
		}
	}
	return order
}

func (g *DurakGameState) refill(order []int) {
	for _, idx := range order {
		p := g.Players[idx]
		for len(p.Hand) < 6 && len(g.Talon) > 0 {
			p.Hand = append(p.Hand, g.drawAny())
		}
	}
}

// finishBout завершает раунд по "бито": роли переходят к следующей
// паре игроков по кругу от старого защищающегося.
func (g *DurakGameState) finishBout(oldDefenderIdx int) {
	g.markOutPlayers()
	if g.checkGameOver() {
		return
	}
	g.AttackerIdx = g.nextActive(oldDefenderIdx)
	g.DefenderIdx = g.nextActive(g.AttackerIdx)
	g.startNewBout()
}

// finishBoutAfterTake завершает раунд после того, как защищающийся
// забрал стол: атакующим становится следующий по кругу от него.
func (g *DurakGameState) finishBoutAfterTake(oldDefenderIdx int) {
	g.markOutPlayers()
	if g.checkGameOver() {
		return
	}
	g.AttackerIdx = g.nextActive(oldDefenderIdx)
	g.DefenderIdx = g.nextActive(g.AttackerIdx)
	g.startNewBout()
}

// markOutPlayers отмечает как выбывших (в безопасности) игроков,
// у которых закончились и карты на руке, и колода для добора.
func (g *DurakGameState) markOutPlayers() {
	if len(g.Talon) > 0 {
		return
	}
	for _, p := range g.Players {
		if len(p.Hand) == 0 {
			p.Out = true
		}
	}
}

// checkGameOver проверяет, не осталось ли активных (не вышедших)
// игроков меньше двух — тогда партия завершена.
func (g *DurakGameState) checkGameOver() bool {
	active := make([]int, 0, len(g.Players))
	for i, p := range g.Players {
		if !p.Out {
			active = append(active, i)
		}
	}
	switch len(active) {
	case 0:
		g.Result = &DurakResult{NoLoser: true}
		g.Phase = DurakPhaseGameOver
		return true
	case 1:
		g.Result = &DurakResult{Loser: active[0]}
		g.Phase = DurakPhaseGameOver
		return true
	default:
		return false
	}
}
