package horse

import (
	"bytes"
	"fmt"
	"strconv"
)

type SkillID int32

type TenThousandths int32

func (x TenThousandths) String() string {
	b := make([]byte, 0, 12)
	if x < 0 {
		x = -x
		b = append(b, '-')
	}
	b = strconv.AppendInt(b, int64(x/1e4), 10)
	if x%1e4 != 0 {
		b = append(b, '.')
		b = fmt.Appendf(b, "%04d", x%1e4)
		b = bytes.TrimRight(b, "0")
	}
	return string(b)
}

// Skill is the internal data about a skill.
type Skill struct {
	ID          SkillID      `json:"skill_id"`
	Name        string       `json:"name"`
	Description string       `json:"description"`
	Group       SkillGroupID `json:"group"`
	Rarity      int8         `json:"rarity"`
	GroupRate   int8         `json:"group_rate"`
	GradeValue  int32        `json:"grade_value,omitzero"`
	WitCheck    bool         `json:"wit_check"`
	Activations []Activation `json:"activations"`
	UniqueOwner string       `json:"unique_owner,omitzero"`
	Tags        []uint16     `json:"tags"`
	SPCost      int          `json:"sp_cost,omitzero"`
	IconID      int          `json:"icon_id"`
}

// Activation is the parameters controlling when a skill activates.
type Activation struct {
	Precondition string         `json:"precondition,omitzero"`
	Condition    string         `json:"condition"`
	Duration     TenThousandths `json:"duration,omitzero"`
	DurScale     DurScale       `json:"dur_scale"`
	Cooldown     TenThousandths `json:"cooldown,omitzero"`
	Abilities    []Ability      `json:"abilities"`
}

// Ability is an individual effect applied by a skill.
type Ability struct {
	Type        AbilityType       `json:"type"`
	ValueUsage  AbilityValueUsage `json:"value_usage"`
	Value       TenThousandths    `json:"value"`
	Target      AbilityTarget     `json:"target"`
	TargetValue int32             `json:"target_value,omitzero"`
}

func (a Ability) String() string {
	r := make([]byte, 0, 64)
	r = append(r, a.Type.String()...)
	if a.Value != 0 {
		r = append(r, ' ')
		if a.Value > 0 {
			r = append(r, '+')
		}
		switch a.Type {
		case AbilityPassiveSpeed, AbilityPassiveStamina, AbilityPassivePower, AbilityPassiveGuts, AbilityPassiveWit:
			r = append(r, a.Value.String()...)
		case AbilityVision:
			r = append(r, a.Value.String()...)
			r = append(r, 'm')
		case AbilityHP:
			r = append(r, (a.Value * 100).String()...)
			r = append(r, '%')
		case AbilityRushedChance:
			r = append(r, a.Value.String()...)
			r = append(r, '%')
		case AbilityGateDelay, AbilityActivateRare:
			// These skill types in particular should be × instead of +.
			r = append(r[:len(r)-1], "×"...)
			r = append(r, a.Value.String()...)
		case AbilityFrenzy, AbilityAddGateDelay:
			r = append(r, a.Value.String()...)
			r = append(r, 's')
		case AbilityCurrentSpeed, AbilityCurrentSpeedUp, AbilityTargetSpeed:
			r = append(r, a.Value.String()...)
			r = append(r, " m/s"...)
		case AbilityLaneSpeed:
			r = append(r, a.Value.String()...)
			r = append(r, " CW/s"...)
		case AbilityAccel:
			r = append(r, a.Value.String()...)
			r = append(r, " m/s²"...)
		case AbilityLaneChange:
			// This skill type should be "to 0.5 CW."
			// (The only skill that has it is Dodging Danger/Sixth Sense.)
			r = append(r[:len(r)-1], "to "...)
			r = append(r, a.Value.String()...)
			r = append(r, " CW"...)
		}
	}
	switch a.Target {
	case TargetSelf:
		// do nothing
	case TargetStyle, TargetRushingStyle:
		// TargetValue is the style to target, not the number of targets.
		r = append(r, " to "...)
		r = append(r, a.Target.String()...)
		switch a.TargetValue {
		case 1:
			r = append(r, " Front Runner"...)
		case 2:
			r = append(r, " Pace Chaser"...)
		case 3:
			r = append(r, " Late Surger"...)
		case 4:
			r = append(r, " End Closer"...)
		}
	case TargetAhead, TargetBehind:
		r = append(r, " to "...)
		if a.TargetValue == 1 {
			r = append(r, "the runner"...)
		} else {
			r = strconv.AppendInt(r, int64(a.TargetValue), 10)
		}
		r = append(r, ' ')
		r = append(r, a.Target.String()...)
	default:
		// For other targeting types, TargetValue is either irrelevant or limit.
		r = append(r, " to "...)
		if a.TargetValue > 1 && a.TargetValue < 18 {
			r = strconv.AppendInt(r, int64(a.TargetValue), 10)
			r = append(r, ' ')
		}
		r = append(r, a.Target.String()...)
	}
	if a.ValueUsage != ValueUsageDirect {
		r = append(r, ' ')
		r = append(r, a.ValueUsage.String()...)
	}
	return string(r)
}

type DurScale int8

//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type DurScale -trimprefix Duration -linecomment
const (
	DurationDirect        DurScale = 1 // directly
	DurationFrontDistance DurScale = 2 // scaling with distance from the front
	DurationRemainingHP   DurScale = 3 // scaling with remaining HP
	DurationIncrementPass DurScale = 4 // increasing with each pass while active
	DurationMidSideBlock  DurScale = 5 // scaling with mid-race phase blocked side time
	DurationRemainingHP2  DurScale = 7 // scaling with remaining HP
)

type AbilityType int8

//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AbilityType -trimprefix Ability -linecomment
const (
	AbilityPassiveSpeed   AbilityType = 1  // Speed
	AbilityPassiveStamina AbilityType = 2  // Stamina
	AbilityPassivePower   AbilityType = 3  // Power
	AbilityPassiveGuts    AbilityType = 4  // Guts
	AbilityPassiveWit     AbilityType = 5  // Wit
	AbilityGreatEscape    AbilityType = 6  // Enable Great Escape
	AbilityVision         AbilityType = 8  // Vision
	AbilityHP             AbilityType = 9  // HP
	AbilityGateDelay      AbilityType = 10 // Gate delay multiplier
	AbilityFrenzy         AbilityType = 13 // Frenzy
	AbilityAddGateDelay   AbilityType = 14 // Added gate delay
	AbilityCurrentSpeed   AbilityType = 21 // Current speed
	AbilityCurrentSpeedUp AbilityType = 22 // Current speed
	AbilityTargetSpeed    AbilityType = 27 // Target speed
	AbilityLaneSpeed      AbilityType = 28 // Lane change speed
	AbilityRushedChance   AbilityType = 29 // Rushed chance
	AbilityAccel          AbilityType = 31 // Acceleration
	AbilityLaneChange     AbilityType = 35 // Set target lane
	AbilityActivateRare   AbilityType = 37 // Activate rare skills
)

type AbilityValueUsage int8

//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AbilityValueUsage -trimprefix ValueUsage -linecomment
const (
	ValueUsageDirect       AbilityValueUsage = 1  // directly
	ValueUsageSkillCount   AbilityValueUsage = 2  // scaling with the number of skills
	ValueUsageTeamSpeed    AbilityValueUsage = 3  // scaling with team Speed
	ValueUsageTeamStamina  AbilityValueUsage = 4  // scaling with team Stamina
	ValueUsageTeamPower    AbilityValueUsage = 5  // scaling with team Power
	ValueUsageTeamGuts     AbilityValueUsage = 6  // scaling with team Guts
	ValueUsageTeamWit      AbilityValueUsage = 7  // scaling with team Wit
	ValueUsageRandom       AbilityValueUsage = 8  // with a random 0× to 0.04× multiplier
	ValueUsageRandom2      AbilityValueUsage = 9  // with a random 0× to 0.04× multiplier
	ValueUsageClimax       AbilityValueUsage = 10 // scaling with the number of races won in the career
	ValueUsageFans         AbilityValueUsage = 12 // scaling with the number of fans earned in the career
	ValueUsageMaxStat      AbilityValueUsage = 13 // scaling with the highest raw stat
	ValueUsageGreenCount   AbilityValueUsage = 14 // scaling with the number of Passive skills activated
	ValueUsageDistAdd      AbilityValueUsage = 19 // plus extra when far from the lead
	ValueUsageMidSideBlock AbilityValueUsage = 20 // scaling with mid-race phase blocked side time
	ValueUsageSpeed        AbilityValueUsage = 22 // scaling with overall speed
	ValueUsageSpeed2       AbilityValueUsage = 23 // scaling with overall speed
	ValueUsageArcPotential AbilityValueUsage = 24 // scaling with L'Arc global potential
	ValueUsageMaxLead      AbilityValueUsage = 25 // scaling with the longest lead obtained in the first ⅔
)

type AbilityTarget int8

//go:generate go run golang.org/x/tools/cmd/stringer@v0.41.0 -type AbilityTarget -trimprefix Target -linecomment
const (
	TargetSelf          AbilityTarget = 1  // self
	TargetSympathizers  AbilityTarget = 2  // others with Sympathy
	TargetInView        AbilityTarget = 4  // others in view
	TargetFrontmost     AbilityTarget = 7  // frontmost
	TargetAhead         AbilityTarget = 9  // ahead
	TargetBehind        AbilityTarget = 10 // behind
	TargetAllTeammates  AbilityTarget = 11 // all teammates
	TargetStyle         AbilityTarget = 18 // using style
	TargetRushingAhead  AbilityTarget = 19 // rushing others ahead
	TargetRushingBehind AbilityTarget = 20 // rushing others behind
	TargetRushingStyle  AbilityTarget = 21 // rushing using style
	TargetCharacter     AbilityTarget = 22 // specific character
	TargetTriggering    AbilityTarget = 23 // whosoever triggered this skill
)

type SkillGroupID int32

// SkillGroup is a group of skills which are alternate versions of each other.
//
// Any of the skill IDs in a group may be zero, indicating that there is no
// skill with the corresponding group rate.
// Some skill groups contain only Skill2 or SkillBad, while others may have all
// four skills.
//
// As a special case, horsegen lists both unique skills and their inherited
// versions in the skill groups for both.
type SkillGroup struct {
	ID SkillGroupID `json:"skill_group"`
	// Skill1 is the base version of the skill, either a common (white) skill
	// or an Uma's own unique.
	Skill1 SkillID `json:"skill1,omitzero"`
	// Skill2 is the first upgraded version of the skill: a rare (gold)
	// skill, a double circle skill, or an inherited unique skill.
	Skill2 SkillID `json:"skill2,omitzero"`
	// Skill3 is the highest upgraded version, a gold version of a skill with
	// a double circle version.
	Skill3 SkillID `json:"skill3,omitzero"`
	// SkillBad is a negative (purple) skill.
	SkillBad SkillID `json:"skill_bad,omitzero"`
}
