package main

import "math"

// WayfarerEquipment — необязательное снаряжение корабля.
type WayfarerEquipment int

const (
	EquipFuelScoop WayfarerEquipment = iota
	EquipCargoExpansion
	EquipShieldBooster
	EquipEscapePod
	EquipBetterLaser
)

// WayfarerShip — полное состояние корабля и его владельца.
type WayfarerShip struct {
	Credits float64

	FuelCapacity float64
	Fuel         float64
	FuelPerLY    float64 // расход топлива на 1 условный световой год

	CargoCapacity  int
	Cargo          []int     // Cargo[i] — количество товара i в трюме
	CargoCostBasis []float64 // CargoCostBasis[i] — средняя цена покупки за единицу текущего груза i (0, если груза нет)

	MaxHull    int
	Hull       int
	MaxShield  int
	Shield     int
	LaserPower int

	// MissileCapacity — сколько ракет (любого типа суммарно)
	// помещается на борту; зависит от вида корабля — у стартового 2.
	// Пока в игре только один вид корабля, но задел на будущее уже
	// сделан отдельным полем, а не константой.
	MissileCapacity int
	RegularMissiles int // обычные ракеты — слабее лазера по щитам, сильнее по голому корпусу; от них можно увернуться
	HomingMissiles  int // самонаводящиеся — от них увернуться нельзя, только обмануть тепловой ловушкой
	Decoys          int // тепловые ловушки — расходуются, чтобы обмануть одну самонаводящуюся ракету

	Equipment map[WayfarerEquipment]bool

	Kills int // сколько вражеских кораблей уничтожено — для рейтинга пилота

	CurrentSystem WayfarerSystemID
	Visited       map[WayfarerSystemID]bool

	ActiveQuest     *WayfarerQuestState
	CompletedQuests map[string]bool
	FailedQuests    map[string]bool
}

// NewWayfarerShip создаёт начальный корабль игрока — скромный, но
// работоспособный торговец на старте карьеры.
func NewWayfarerShip(start WayfarerSystemID) *WayfarerShip {
	s := &WayfarerShip{
		Credits:         1000,
		FuelCapacity:    7,
		Fuel:            7,
		FuelPerLY:       1.0 / 30.0, // условно: полный бак = ~7 прыжков по системе средней удалённости
		CargoCapacity:   20,
		MaxHull:         100,
		Hull:            100,
		MaxShield:       50,
		Shield:          50,
		LaserPower:      12,
		MissileCapacity: 2,
		Equipment:       map[WayfarerEquipment]bool{},
		CurrentSystem:   start,
		Visited:         map[WayfarerSystemID]bool{start: true},
		CompletedQuests: map[string]bool{},
		FailedQuests:    map[string]bool{},
	}
	s.Cargo = make([]int, len(WayfarerCommodities))
	s.CargoCostBasis = make([]float64, len(WayfarerCommodities))
	return s
}

// CargoUsed — сколько единиц трюма сейчас занято.
func (s *WayfarerShip) CargoUsed() int {
	used := 0
	for _, n := range s.Cargo {
		used += n
	}
	return used
}

// CargoFree — сколько единиц трюма ещё свободно.
func (s *WayfarerShip) CargoFree() int {
	return s.CargoCapacity - s.CargoUsed()
}

// MaxJumpRange — максимальное расстояние прыжка при полном баке.
func (s *WayfarerShip) MaxJumpRange() float64 {
	return s.FuelCapacity / s.FuelPerLY
}

// FuelRange — максимальное расстояние прыжка при текущем запасе
// топлива.
func (s *WayfarerShip) FuelRange() float64 {
	return s.Fuel / s.FuelPerLY
}

var (
	errWayfarerInsufficientFunds = wayfarerErr("недостаточно кредитов")
	errWayfarerCargoFull         = wayfarerErr("трюм заполнен")
	errWayfarerNoCargo           = wayfarerErr("этого товара нет в трюме в таком количестве")
	errWayfarerOutOfRange        = wayfarerErr("не хватит топлива долететь так далеко")
	errWayfarerSameSystem        = wayfarerErr("вы уже в этой системе")

	errWayfarerNoMissileSlots            = wayfarerErr("все ракетные слоты уже заняты")
	errWayfarerNoMissiles                = wayfarerErr("на борту нет ракет этого типа")
	errWayfarerInvalidWeapon             = wayfarerErr("неизвестный тип вооружения")
	errWayfarerInvalidShieldDistribution = wayfarerErr("распределение энергии щита не может превышать общий запас")
)

type wayfarerErr string

func (e wayfarerErr) Error() string { return string(e) }

// BuyCommodity покупает qty единиц товара commodityIdx по цене
// unitPrice за единицу (цена вычисляется вызывающим кодом через
// CommodityPrice — так UI и движок используют одно и то же число,
// без риска рассинхронизации от повторного вызова генератора
// случайности). Заодно обновляет среднюю цену покупки этого товара
// в трюме (взвешенное среднее старой и новой партии) — чтобы потом
// на рынке можно было показать, по какой цене товар куплен.
func (s *WayfarerShip) BuyCommodity(commodityIdx int, qty int, unitPrice float64) error {
	if qty <= 0 {
		return nil
	}
	if qty > s.CargoFree() {
		return errWayfarerCargoFull
	}
	cost := unitPrice * float64(qty)
	if cost > s.Credits {
		return errWayfarerInsufficientFunds
	}
	s.Credits -= cost

	oldQty := s.Cargo[commodityIdx]
	oldTotal := s.CargoCostBasis[commodityIdx] * float64(oldQty)
	s.Cargo[commodityIdx] += qty
	s.CargoCostBasis[commodityIdx] = (oldTotal + cost) / float64(s.Cargo[commodityIdx])
	return nil
}

// SellCommodity продаёт qty единиц товара commodityIdx по цене
// unitPrice за единицу.
func (s *WayfarerShip) SellCommodity(commodityIdx int, qty int, unitPrice float64) error {
	if qty <= 0 {
		return nil
	}
	if qty > s.Cargo[commodityIdx] {
		return errWayfarerNoCargo
	}
	s.Cargo[commodityIdx] -= qty
	s.Credits += unitPrice * float64(qty)
	if s.Cargo[commodityIdx] == 0 {
		s.CargoCostBasis[commodityIdx] = 0 // трюм по этому товару опустел — старая цена покупки больше не актуальна
	}
	return nil
}

// Refuel заправляет корабль на amount единиц топлива (не больше
// полного бака) по цене pricePerUnit за единицу.
func (s *WayfarerShip) Refuel(amount float64, pricePerUnit float64) error {
	if amount <= 0 {
		return nil
	}
	if s.Fuel+amount > s.FuelCapacity {
		amount = s.FuelCapacity - s.Fuel
	}
	cost := amount * pricePerUnit
	if cost > s.Credits {
		return errWayfarerInsufficientFunds
	}
	s.Credits -= cost
	s.Fuel += amount
	return nil
}

// Repair чинит корпус на amount очков по цене pricePerPoint.
func (s *WayfarerShip) Repair(amount int, pricePerPoint float64) error {
	if amount <= 0 {
		return nil
	}
	if s.Hull+amount > s.MaxHull {
		amount = s.MaxHull - s.Hull
	}
	cost := float64(amount) * pricePerPoint
	if cost > s.Credits {
		return errWayfarerInsufficientFunds
	}
	s.Credits -= cost
	s.Hull += amount
	return nil
}

// MissilesLoaded — сколько ракет любого типа сейчас на борту.
func (s *WayfarerShip) MissilesLoaded() int {
	return s.RegularMissiles + s.HomingMissiles
}

// BuyMissile покупает одну ракету указанного типа (обычную или
// самонаводящуюся), если хватает кредитов и есть свободный ракетный
// слот (MissileCapacity).
func (s *WayfarerShip) BuyMissile(weapon WayfarerWeaponType, pricePerUnit float64) error {
	if weapon != WayfarerWeaponMissileRegular && weapon != WayfarerWeaponMissileHoming {
		return errWayfarerInvalidWeapon
	}
	if s.MissilesLoaded() >= s.MissileCapacity {
		return errWayfarerNoMissileSlots
	}
	if pricePerUnit > s.Credits {
		return errWayfarerInsufficientFunds
	}
	s.Credits -= pricePerUnit
	if weapon == WayfarerWeaponMissileRegular {
		s.RegularMissiles++
	} else {
		s.HomingMissiles++
	}
	return nil
}

// BuyDecoy покупает одну тепловую ловушку — расходуется, чтобы
// гарантированно обмануть самонаводящуюся ракету в бою (см.
// WayfarerCombatState.UseDecoy). Вместимость трюма/ракетных слотов
// на ловушки не распространяется — считаем их мелким расходником.
func (s *WayfarerShip) BuyDecoy(pricePerUnit float64) error {
	if pricePerUnit > s.Credits {
		return errWayfarerInsufficientFunds
	}
	s.Credits -= pricePerUnit
	s.Decoys++
	return nil
}

// JumpTo перемещает корабль в целевую систему, если хватает
// топлива, списывая расход по фактическому расстоянию.
func (s *WayfarerShip) JumpTo(target WayfarerSystemID) error {
	if target == s.CurrentSystem {
		return errWayfarerSameSystem
	}
	from := GenerateSystem(s.CurrentSystem)
	to := GenerateSystem(target)
	dist := from.Distance(to)
	fuelNeeded := dist * s.FuelPerLY
	if fuelNeeded > s.Fuel {
		return errWayfarerOutOfRange
	}
	s.Fuel -= fuelNeeded
	s.CurrentSystem = target
	s.Visited[target] = true
	return nil
}

// wayfarerRatingThresholds — пороги числа побед для присвоения
// пилотского рейтинга (намёк на классическую систему рангов Elite —
// свой набор названий и порогов, не копия оригинальных чисел).
var wayfarerRatingThresholds = []struct {
	Kills int
	Key   string
}{
	{0, "rating.harmless"},
	{4, "rating.mostly_harmless"},
	{12, "rating.poor"},
	{28, "rating.average"},
	{50, "rating.above_average"},
	{100, "rating.competent"},
	{200, "rating.dangerous"},
	{400, "rating.deadly"},
	{800, "rating.wayfarer_elite"},
}

// PilotRating возвращает ключ перевода текущего пилотского ранга.
func (s *WayfarerShip) PilotRating() string {
	rating := wayfarerRatingThresholds[0].Key
	for _, r := range wayfarerRatingThresholds {
		if s.Kills >= r.Kills {
			rating = r.Key
		}
	}
	return rating
}

// NetWorth — кредиты плюс примерная стоимость груза в трюме (по
// половине базовой цены каждого товара — грубая, но достаточная для
// отображения общего "веса" игрока оценка).
func (s *WayfarerShip) NetWorth() float64 {
	worth := s.Credits
	for i, n := range s.Cargo {
		worth += float64(n) * WayfarerCommodities[i].BasePrice * 0.5
	}
	return math.Round(worth)
}
