package main

import "math/rand"

// WayfarerCombatOutcome — текущий/итоговый исход боя.
type WayfarerCombatOutcome int

const (
	CombatOngoing    WayfarerCombatOutcome = iota
	CombatWon                              // враг уничтожен
	CombatLost                             // корабль игрока уничтожен
	CombatFled                             // игрок сбежал
	CombatNegotiated                       // удалось договориться
)

// WayfarerWeaponType — чем именно наносится урон в бою: обычный
// лазер или один из двух видов ракет.
type WayfarerWeaponType int

const (
	WayfarerWeaponLaser WayfarerWeaponType = iota
	// WayfarerWeaponMissileRegular — обычная ракета: слабее лазера,
	// пока цель прикрыта щитом, но сильнее лазера по голому корпусу.
	// От неё можно попытаться увернуться отдельным действием (см.
	// WayfarerCombatState.Dodge).
	WayfarerWeaponMissileRegular
	// WayfarerWeaponMissileHoming — самонаводящаяся ракета: та же
	// разница в уроне по щиту/корпусу, что и у обычной, но увернуться
	// от неё пилотированием нельзя — только обмануть тепловой
	// ловушкой (см. WayfarerCombatState.UseDecoy).
	WayfarerWeaponMissileHoming
)

// WayfarerShieldFacing — одно из четырёх направлений, между которыми
// распределяется энергия щита (и у игрока, и у врага — симметрично).
type WayfarerShieldFacing int

const (
	WayfarerShieldFore  WayfarerShieldFacing = iota // нос
	WayfarerShieldAft                               // хвост (корма)
	WayfarerShieldLeft                              // левый борт
	WayfarerShieldRight                             // правый борт
)

// WayfarerApproach — с какого направления заходит враг для СЛЕДУЮЩЕЙ
// атаки. Известно игроку заранее (см. NextApproach) и сужает набор
// направлений щита, куда реально может прилететь удар — но не
// называет конкретное направление напрямую, оставляя разведку
// неполной.
type WayfarerApproach int

const (
	WayfarerApproachFront    WayfarerApproach = iota // строго спереди — удар точно в нос
	WayfarerApproachBehind                           // строго сзади — удар точно в хвост
	WayfarerApproachLeft                             // слева — удар в левый борт либо в хвост
	WayfarerApproachRight                            // справа — удар в правый борт либо в хвост
	WayfarerApproachVertical                         // сверху/снизу — удар в нос либо в хвост
)

// wayfarerApproachCandidates — какие направления щита реально может
// задеть атака при данном заходе врага (см. WayfarerApproach).
var wayfarerApproachCandidates = map[WayfarerApproach][]WayfarerShieldFacing{
	WayfarerApproachFront:    {WayfarerShieldFore},
	WayfarerApproachBehind:   {WayfarerShieldAft},
	WayfarerApproachLeft:     {WayfarerShieldLeft, WayfarerShieldAft},
	WayfarerApproachRight:    {WayfarerShieldRight, WayfarerShieldAft},
	WayfarerApproachVertical: {WayfarerShieldFore, WayfarerShieldAft},
}

// Базовые вероятности успеха действий "бегство"/"переговоры"/"уклонение
// от обычной ракеты" — в сумме с некоторой случайностью боя это
// делает все три реальными, но не гарантированными альтернативами
// прямому бою.
const (
	wayfarerFleeChance      = 0.45
	wayfarerNegotiateChance = 0.35
	wayfarerDodgeChance     = 0.5
)

// Ракеты (что обычные, что самонаводящиеся) наносят одинаково
// изменённый урон относительно лазера: слабее, пока щит той стороны
// ещё держится, и сильнее, если щита с этой стороны уже нет.
const (
	wayfarerMissileShieldMultiplier = 0.5
	wayfarerMissileHullMultiplier   = 1.5
)

// WayfarerCombatState — состояние одного пошагового боя.
type WayfarerCombatState struct {
	EnemyNameKey string
	EnemyLaser   int
	EnemyHull    int
	EnemyMaxHull int
	EnemyWeapon  WayfarerWeaponType // чем атакует враг — известно игроку с начала боя

	// EnemyShieldFore/Aft/Left/Right — щиты врага, устроены
	// симметрично щитам игрока (см. ниже). Игрок стреляет
	// прицельно — по конкретному направлению (Attack/AttackWithMissile).
	EnemyShieldFore  int
	EnemyShieldAft   int
	EnemyShieldLeft  int
	EnemyShieldRight int
	EnemyMaxShield   int

	PlayerLaser   int
	PlayerHull    int
	PlayerMaxHull int

	// PlayerShieldFore/Aft/Left/Right — энергия щита игрока по
	// четырём направлениям; в сумме не может превышать
	// PlayerMaxShield. Перераспределяется действием DistributeShields.
	PlayerShieldFore  int
	PlayerShieldAft   int
	PlayerShieldLeft  int
	PlayerShieldRight int
	PlayerMaxShield   int

	Negotiable bool // можно ли вообще пытаться договориться с этим противником
	Round      int
	Outcome    WayfarerCombatOutcome

	LastPlayerDamage    int                  // урон, нанесённый игроком в последнем раунде (для лога)
	LastEnemyDamage     int                  // урон, полученный игроком в последнем раунде
	LastHitFacing       WayfarerShieldFacing // какое направление ЩИТА ИГРОКА приняло удар в последнем раунде
	LastPlayerHitFacing WayfarerShieldFacing // в какое направление ЩИТА ВРАГА стрелял игрок в последнем раунде

	// NextApproach — с какого направления зайдёт враг для СЛЕДУЮЩЕЙ
	// атаки; показывается игроку ДО выбора действия (см.
	// RollNextApproach), чтобы решение о распределении щита было
	// осмысленным, а не гаданием вслепую.
	NextApproach WayfarerApproach
}

// randomEnemyWeapon выбирает случайное вооружение врага при
// генерации боевой встречи: чаще лазер, реже — один из видов ракет.
func randomEnemyWeapon(rnd *rand.Rand) WayfarerWeaponType {
	roll := rnd.Float64()
	switch {
	case roll < 0.6:
		return WayfarerWeaponLaser
	case roll < 0.85:
		return WayfarerWeaponMissileRegular
	default:
		return WayfarerWeaponMissileHoming
	}
}

// wayfarerWeaponNameKey / wayfarerFacingNameKey / wayfarerApproachNameKey
// — ключи переводов названий для отображения в бою.
var wayfarerWeaponNameKey = map[WayfarerWeaponType]string{
	WayfarerWeaponLaser:          "wf.weapon.laser",
	WayfarerWeaponMissileRegular: "wf.weapon.missile_regular",
	WayfarerWeaponMissileHoming:  "wf.weapon.missile_homing",
}

var wayfarerFacingNameKey = map[WayfarerShieldFacing]string{
	WayfarerShieldFore:  "wf.combat.facing.fore",
	WayfarerShieldAft:   "wf.combat.facing.aft",
	WayfarerShieldLeft:  "wf.combat.facing.left",
	WayfarerShieldRight: "wf.combat.facing.right",
}

var wayfarerApproachNameKey = map[WayfarerApproach]string{
	WayfarerApproachFront:    "wf.combat.approach.front",
	WayfarerApproachBehind:   "wf.combat.approach.behind",
	WayfarerApproachLeft:     "wf.combat.approach.left",
	WayfarerApproachRight:    "wf.combat.approach.right",
	WayfarerApproachVertical: "wf.combat.approach.vertical",
}

// splitShieldFour раскладывает суммарный запас энергии щита на
// четыре направления по умолчанию — поровну, остаток на нос.
func splitShieldFour(total int) (fore, aft, left, right int) {
	quarter := total / 4
	rest := total - quarter*3
	return rest, quarter, quarter, quarter
}

// NewWayfarerCombat создаёт новое состояние боя. Щиты (и игрока, и
// врага) по умолчанию распределяются по четырём направлениям
// поровну (остаток — на нос). Сразу же определяется, с какого
// направления зайдёт враг для самой первой атаки.
func NewWayfarerCombat(enemyNameKey string, enemyHull, enemyLaser int, playerHull, playerMaxHull, playerShield, playerMaxShield, playerLaser int, negotiable bool, enemyWeapon WayfarerWeaponType, enemyShield int, rnd *rand.Rand) *WayfarerCombatState {
	c := &WayfarerCombatState{
		EnemyNameKey:    enemyNameKey,
		EnemyHull:       enemyHull,
		EnemyMaxHull:    enemyHull,
		EnemyLaser:      enemyLaser,
		EnemyWeapon:     enemyWeapon,
		EnemyMaxShield:  enemyShield,
		PlayerHull:      playerHull,
		PlayerMaxHull:   playerMaxHull,
		PlayerMaxShield: playerMaxShield,
		PlayerLaser:     playerLaser,
		Negotiable:      negotiable,
	}
	c.PlayerShieldFore, c.PlayerShieldAft, c.PlayerShieldLeft, c.PlayerShieldRight = splitShieldFour(playerShield)
	c.EnemyShieldFore, c.EnemyShieldAft, c.EnemyShieldLeft, c.EnemyShieldRight = splitShieldFour(enemyShield)
	c.RollNextApproach(rnd)
	return c
}

// RollNextApproach выбирает случайное направление, с которого зайдёт
// враг для СЛЕДУЮЩЕЙ атаки.
func (c *WayfarerCombatState) RollNextApproach(rnd *rand.Rand) {
	c.NextApproach = WayfarerApproach(rnd.Intn(5))
}

// TotalShield — суммарная энергия щита игрока по всем направлениям
// (используется при синхронизации с кораблём после боя).
func (c *WayfarerCombatState) TotalShield() int {
	return c.PlayerShieldFore + c.PlayerShieldAft + c.PlayerShieldLeft + c.PlayerShieldRight
}

// TotalEnemyShield — суммарная энергия щита врага по всем направлениям.
func (c *WayfarerCombatState) TotalEnemyShield() int {
	return c.EnemyShieldFore + c.EnemyShieldAft + c.EnemyShieldLeft + c.EnemyShieldRight
}

func (c *WayfarerCombatState) playerShieldFor(facing WayfarerShieldFacing) *int {
	switch facing {
	case WayfarerShieldFore:
		return &c.PlayerShieldFore
	case WayfarerShieldAft:
		return &c.PlayerShieldAft
	case WayfarerShieldLeft:
		return &c.PlayerShieldLeft
	default:
		return &c.PlayerShieldRight
	}
}

func (c *WayfarerCombatState) enemyShieldFor(facing WayfarerShieldFacing) *int {
	switch facing {
	case WayfarerShieldFore:
		return &c.EnemyShieldFore
	case WayfarerShieldAft:
		return &c.EnemyShieldAft
	case WayfarerShieldLeft:
		return &c.EnemyShieldLeft
	default:
		return &c.EnemyShieldRight
	}
}

// rollDamage — базовый урон laser ± 30% случайного разброса.
func rollDamage(laser int, rnd *rand.Rand) int {
	factor := 0.7 + rnd.Float64()*0.6
	dmg := int(float64(laser) * factor)
	if dmg < 1 {
		dmg = 1
	}
	return dmg
}

// applyDamage наносит урон dmg сначала по щиту, затем по корпусу
// (щит поглощает урон первым, как в большинстве похожих игр). Для
// обычного лазера — без изменений множителя.
func applyDamage(shield, hull *int, dmg int) {
	if *shield > 0 {
		absorbed := dmg
		if absorbed > *shield {
			absorbed = *shield
		}
		*shield -= absorbed
		dmg -= absorbed
	}
	if dmg > 0 {
		*hull -= dmg
		if *hull < 0 {
			*hull = 0
		}
	}
}

// applyWeaponDamage наносит dmg с учётом типа оружия: лазер — как и
// раньше (applyDamage), ракета (любая) — слабее, пока щит с этой
// стороны ещё держится (только часть dmg, по
// wayfarerMissileShieldMultiplier, уходит на щит — до корпуса в тот
// же раунд не доходит), и сильнее (dmg*wayfarerMissileHullMultiplier),
// если щита с этой стороны уже нет вовсе.
func applyWeaponDamage(shield, hull *int, dmg int, weapon WayfarerWeaponType) {
	if weapon == WayfarerWeaponLaser {
		applyDamage(shield, hull, dmg)
		return
	}
	if *shield > 0 {
		reduced := int(float64(dmg) * wayfarerMissileShieldMultiplier)
		if reduced < 1 {
			reduced = 1
		}
		if reduced > *shield {
			reduced = *shield
		}
		*shield -= reduced
		return
	}
	boosted := int(float64(dmg) * wayfarerMissileHullMultiplier)
	if boosted < 1 {
		boosted = 1
	}
	*hull -= boosted
	if *hull < 0 {
		*hull = 0
	}
}

// enemyStrikes разыгрывает ответный удар врага: направление удара
// выбирается случайно, но только среди вариантов, допустимых текущим
// NextApproach (см. wayfarerApproachCandidates) — не полностью
// произвольно среди всех четырёх. После удара сразу готовится
// подсказка о заходе для следующего раунда.
func (c *WayfarerCombatState) enemyStrikes(rnd *rand.Rand) {
	candidates := wayfarerApproachCandidates[c.NextApproach]
	facing := candidates[rnd.Intn(len(candidates))]
	c.LastHitFacing = facing
	dmg := rollDamage(c.EnemyLaser, rnd)
	applyWeaponDamage(c.playerShieldFor(facing), &c.PlayerHull, dmg, c.EnemyWeapon)
	c.LastEnemyDamage = dmg
	if c.PlayerHull == 0 {
		c.Outcome = CombatLost
	}
	if c.Outcome == CombatOngoing {
		c.RollNextApproach(rnd)
	}
}

// Attack — игрок атакует лазером выбранное направление щита врага;
// если враг не уничтожен, враг атакует в ответ в тот же раунд.
func (c *WayfarerCombatState) Attack(facing WayfarerShieldFacing, rnd *rand.Rand) {
	if c.Outcome != CombatOngoing {
		return
	}
	c.Round++

	dmg := rollDamage(c.PlayerLaser, rnd)
	c.LastPlayerHitFacing = facing
	applyWeaponDamage(c.enemyShieldFor(facing), &c.EnemyHull, dmg, WayfarerWeaponLaser)
	c.LastPlayerDamage = dmg

	if c.EnemyHull == 0 {
		c.Outcome = CombatWon
		c.LastEnemyDamage = 0
		return
	}

	c.enemyStrikes(rnd)
}

// AttackWithMissile — игрок стреляет ракетой в выбранное направление
// щита врага вместо лазера, расходуя один боеприпас соответствующего
// типа с корабля ship.
func (c *WayfarerCombatState) AttackWithMissile(ship *WayfarerShip, weapon WayfarerWeaponType, facing WayfarerShieldFacing, rnd *rand.Rand) error {
	if c.Outcome != CombatOngoing {
		return nil
	}
	switch weapon {
	case WayfarerWeaponMissileRegular:
		if ship.RegularMissiles <= 0 {
			return errWayfarerNoMissiles
		}
		ship.RegularMissiles--
	case WayfarerWeaponMissileHoming:
		if ship.HomingMissiles <= 0 {
			return errWayfarerNoMissiles
		}
		ship.HomingMissiles--
	default:
		return errWayfarerInvalidWeapon
	}

	c.Round++
	dmg := rollDamage(c.PlayerLaser, rnd)
	c.LastPlayerHitFacing = facing
	applyWeaponDamage(c.enemyShieldFor(facing), &c.EnemyHull, dmg, weapon)
	c.LastPlayerDamage = dmg

	if c.EnemyHull == 0 {
		c.Outcome = CombatWon
		c.LastEnemyDamage = 0
		return nil
	}

	c.enemyStrikes(rnd)
	return nil
}

// Dodge — попытка увернуться от ОБЫЧНОЙ ракеты вместо атаки в этот
// раунд (тратит ход, как и Flee/Negotiate). Имеет смысл (и вообще
// доступно) только если EnemyWeapon == WayfarerWeaponMissileRegular —
// от лазера или самонаводящейся ракеты так уклониться нельзя.
func (c *WayfarerCombatState) Dodge(rnd *rand.Rand) bool {
	if c.Outcome != CombatOngoing || c.EnemyWeapon != WayfarerWeaponMissileRegular {
		return false
	}
	c.Round++

	if rnd.Float64() < wayfarerDodgeChance {
		c.LastEnemyDamage = 0
		return true
	}

	c.enemyStrikes(rnd)
	return false
}

// UseDecoy — расходует одну тепловую ловушку с корабля ship, чтобы
// гарантированно обмануть САМОНАВОДЯЩУЮСЯ ракету в этом раунде
// (тратит ход). Единственная защита от неё — обычное уклонение
// (Dodge) на неё не действует.
func (c *WayfarerCombatState) UseDecoy(ship *WayfarerShip) bool {
	if c.Outcome != CombatOngoing || c.EnemyWeapon != WayfarerWeaponMissileHoming || ship.Decoys <= 0 {
		return false
	}
	ship.Decoys--
	c.Round++
	c.LastEnemyDamage = 0
	return true
}

// DistributeShields перераспределяет энергию щита игрока по четырём
// направлениям (нос/хвост/левый борт/правый борт); суммарно не может
// превышать PlayerMaxShield. Считается отдельным действием —
// расходует ход, после чего враг всё равно атакует уже по новому
// распределению.
func (c *WayfarerCombatState) DistributeShields(fore, aft, left, right int, rnd *rand.Rand) error {
	if c.Outcome != CombatOngoing {
		return nil
	}
	if fore < 0 || aft < 0 || left < 0 || right < 0 || fore+aft+left+right > c.PlayerMaxShield {
		return errWayfarerInvalidShieldDistribution
	}
	c.PlayerShieldFore = fore
	c.PlayerShieldAft = aft
	c.PlayerShieldLeft = left
	c.PlayerShieldRight = right
	c.Round++

	c.enemyStrikes(rnd)
	return nil
}

// Flee — попытка сбежать из боя. При неудаче враг наносит полный
// удар (без ответа игрока в этом раунде — сосредоточились на
// бегстве, не на атаке).
func (c *WayfarerCombatState) Flee(rnd *rand.Rand) bool {
	if c.Outcome != CombatOngoing {
		return false
	}
	c.Round++

	if rnd.Float64() < wayfarerFleeChance {
		c.Outcome = CombatFled
		c.LastEnemyDamage = 0
		return true
	}

	c.enemyStrikes(rnd)
	return false
}

// Negotiate — попытка договориться (доступна только если
// c.Negotiable). При неудаче враг наносит полный удар, как и при
// неудачном бегстве.
func (c *WayfarerCombatState) Negotiate(rnd *rand.Rand) bool {
	if c.Outcome != CombatOngoing || !c.Negotiable {
		return false
	}
	c.Round++

	if rnd.Float64() < wayfarerNegotiateChance {
		c.Outcome = CombatNegotiated
		c.LastEnemyDamage = 0
		return true
	}

	c.enemyStrikes(rnd)
	return false
}
