package data

import (
	"fmt"

	"github.com/emef/bitfield"

	"code.ur.gs/lupine/ordoor/internal/util/asciiscan"
)

// AnimAction represents an animation that is stored in WarHammer.ani
type AnimAction int

// CharacterType represents one of the different types of character in the game.
//
// TODO: can we load the list of character types anywhere or is it hardcoded in
// the original too?
type CharacterType int

const (
	AnimActionNone             AnimAction = 0
	AnimActionAnim             AnimAction = 1
	AnimActionWalk             AnimAction = 2
	AnimActionExplosion        AnimAction = 3
	AnimActionProjectile       AnimAction = 4
	AnimActionSmoke            AnimAction = 5
	AnimActionStandingShoot    AnimAction = 6
	AnimActionStandingDeath    AnimAction = 7
	AnimActionPain             AnimAction = 8
	AnimActionSpellFx1         AnimAction = 9
	AnimActionSpellFx2         AnimAction = 10
	AnimActionSpellFx3         AnimAction = 11
	AnimActionSpellFx4         AnimAction = 12
	AnimActionSpellFx5         AnimAction = 13
	AnimActionRun              AnimAction = 14
	AnimActionCrouch           AnimAction = 15
	AnimActionStand            AnimAction = 16
	AnimActionStandingRead     AnimAction = 17
	AnimActionStandingUnready  AnimAction = 18
	AnimActionCrouchingReady   AnimAction = 19
	AnimActionCrouchingUnready AnimAction = 20
	AnimActionCrouchingShoot   AnimAction = 21
	AnimActionStandingGrenade  AnimAction = 22
	AnimActionCrouchingGrenade AnimAction = 23
	AnimActionDrawMelee        AnimAction = 24
	AnimActionSlash            AnimAction = 25
	AnimActionStab             AnimAction = 26
	AnimActionBlown            AnimAction = 27
	AnimActionCrouchingDeath   AnimAction = 28
	AnimActionJump             AnimAction = 29
	AnimActionHeal             AnimAction = 30
	AnimActionTechWork         AnimAction = 31
	AnimActionCast             AnimAction = 32
	AnimActionShoot            AnimAction = 33
	AnimActionDeath            AnimAction = 34
	AnimActionFromWarp         AnimAction = 35

	AnimActionStart = AnimActionNone
	AnimActionEnd   = AnimActionFromWarp
	AnimActionCount = AnimActionEnd - AnimActionStart + 1

	// FIXME: indexed from 1, very annoying
	CharacterTypeTactical        CharacterType = 1
	CharacterTypeAssault         CharacterType = 2
	CharacterTypeDevastator      CharacterType = 3
	CharacterTypeTerminator      CharacterType = 4
	CharacterTypeApothecary      CharacterType = 5
	CharacterTypeTechmarine      CharacterType = 6
	CharacterTypeChaplain        CharacterType = 7
	CharacterTypeLibrarian       CharacterType = 8
	CharacterTypeCaptain         CharacterType = 9
	CharacterTypeChaosMarine     CharacterType = 10
	CharacterTypeChaosLord       CharacterType = 11
	CharacterTypeChaosChaplain   CharacterType = 12
	CharacterTypeChaosSorcerer   CharacterType = 13
	CharacterTypeChaosTerminator CharacterType = 14
	CharacterTypeKhorneBerserker CharacterType = 15
	CharacterTypeBloodThirster   CharacterType = 16
	CharacterTypeBloodLetter     CharacterType = 17
	CharacterTypeFleshHound      CharacterType = 18
	CharacterTypeLordOfChange    CharacterType = 19
	CharacterTypeFlamer          CharacterType = 20
	CharacterTypePinkHorror      CharacterType = 21
	CharacterTypeBlueHorror      CharacterType = 22
	CharacterTypeChaosCultist    CharacterType = 23

	CharacterTypeStart = CharacterTypeTactical
	CharacterTypeEnd   = CharacterTypeChaosCultist
	CharacterTypeCount = CharacterTypeEnd - CharacterTypeStart + 1
)

// HasAction tells us whether a character has an animation or not.
type HasAction struct {
	bits bitfield.BitField
}

func LoadHasAction(filename string) (*HasAction, error) {
	scanner, err := asciiscan.New(filename)
	if err != nil {
		return nil, err
	}

	defer scanner.Close()

	out := &HasAction{
		bits: bitfield.New(int(CharacterTypeCount) * int(AnimActionCount)),
	}

	// Reuse this for every loop
	var actions [AnimActionCount]bool
	ptrs := make([]*bool, len(actions))
	for i, _ := range actions {
		ptrs[i] = &actions[i]
	}

	for c := CharacterTypeStart; c <= CharacterTypeEnd; c++ {
		if err := scanner.ConsumeBoolPtrs(ptrs...); err != nil {
			return nil, err
		}

		for j, value := range actions {
			a := AnimActionStart + AnimAction(j)

			out.set(c, a, value)
		}
	}

	return out, nil
}

func (h *HasAction) Check(c CharacterType, a AnimAction) bool {
	return h.bits.Test(h.offset(c, a))
}

func (h *HasAction) offset(c CharacterType, a AnimAction) uint32 {
	// Best to view this as a 2D array with CharacterTypeCount * AnimActionCount elements
	i := uint32(c - CharacterTypeStart)
	j := uint32(a - AnimActionStart)

	return (i * uint32(AnimActionCount)) + j
}

func (h *HasAction) set(c CharacterType, a AnimAction, value bool) {
	if value {
		h.bits.Set(h.offset(c, a))
	} else {
		h.bits.Clear(h.offset(c, a))
	}
}

// Actions returns the list of animations that a character type has
func (h *HasAction) Actions(c CharacterType) []AnimAction {
	var out []AnimAction

	for j := AnimActionStart; j < AnimActionCount; j++ {
		if h.Check(c, j) {
			out = append(out, j)
		}
	}

	return out
}

func (h *HasAction) Print() {
	fmt.Println("    Tac  Ass  Dev  Term Apo  Tech Chp  Lib  Cpt  CMar CLrd CChp CSrc CTrm Kbz  BTh  BL   FHnd LoC  Flm  PHr  BHr  Cult")
	for a := AnimActionStart; a <= AnimActionEnd; a++ {
		fmt.Printf("%.2d", int(a))
		for c := CharacterTypeStart; c <= CharacterTypeEnd; c++ {
			if h.Check(c, a) {
				fmt.Print("  x  ")
			} else {
				fmt.Print("     ")
			}
		}

		fmt.Println("")
	}
}
