package ttlv

import (
	"sort"

	"github.com/ansel1/merry"
	"github.com/gemalto/kmip-go/internal/kmiputil"
)

// DefaultRegistry holds the default mappings of types, tags, enums, and bitmasks
// to canonical names and normalized names from the KMIP spec.  It is pre-populated with the 1.4 spec's
// values.  It can be replaced, or additional values can be registered with it.
//
// It is not currently concurrent-safe, so replace or configure it early in your
// program.
var DefaultRegistry Registry

//nolint:gochecknoinits
func init() {
	RegisterTypes(&DefaultRegistry)
}

var (
	ErrInvalidHexString     = kmiputil.ErrInvalidHexString
	ErrUnregisteredEnumName = merry.New("unregistered enum name")
)

// NormalizeName tranforms KMIP names from the spec into the
// normalized form of the name.  Typically, this means removing spaces,
// and replacing some special characters.  The normalized form of the name
// is used in the JSON and XML encodings from the KMIP Profiles.
// The spec describes the normalization process in 5.4.1.1 and 5.5.1.1
func NormalizeName(s string) string {
	return kmiputil.NormalizeName(s)
}

// Enum represents an enumeration of KMIP values (as uint32), and maps them
// to the canonical string names and the normalized string names of the
// value as declared in the KMIP specs.
// Enum is used to transpose values from strings to byte values, as required
// by the JSON and XML encodings defined in the KMIP Profiles spec.
// These mappings are also used to pretty print KMIP values, and to marshal
// and unmarshal enum and bitmask values to golang string values.
//
// Enum currently uses plain maps, so it is not thread safe to register new values
// concurrently.  You should register all values at the start of your program before
// using this package concurrently.
//
// Enums are used in the KMIP spec for two purposes: for defining the possible values
// for values encoded as the KMIP Enumeration type, and for bitmask values.  Bitmask
// values are encoded as Integers, but are really enum values bitwise-OR'd together.
//
// Enums are registered with a Registry.  The code to register enums is typically
// generated by the kmipgen tool.
type Enum struct {
	valuesToName          map[uint32]string
	valuesToCanonicalName map[uint32]string
	nameToValue           map[string]uint32
	canonicalNamesToValue map[string]uint32
	bitMask               bool
}

func NewEnum() Enum {
	return Enum{}
}

func NewBitmask() Enum {
	return Enum{
		bitMask: true,
	}
}

// RegisterValue adds a mapping of a uint32 value to a name.  The name will be
// processed by NormalizeName to produce the normalized enum value name as described
// in the KMIP spec.
func (e *Enum) RegisterValue(v uint32, name string) {
	nn := NormalizeName(name)

	if e.valuesToName == nil {
		e.valuesToName = map[uint32]string{}
		e.nameToValue = map[string]uint32{}
		e.valuesToCanonicalName = map[uint32]string{}
		e.canonicalNamesToValue = map[string]uint32{}
	}

	e.valuesToName[v] = nn
	e.nameToValue[nn] = v
	e.valuesToCanonicalName[v] = name
	e.canonicalNamesToValue[name] = v
}

func (e *Enum) Name(v uint32) (string, bool) {
	if e == nil {
		return "", false
	}

	name, ok := e.valuesToName[v]

	return name, ok
}

func (e *Enum) CanonicalName(v uint32) (string, bool) {
	if e == nil {
		return "", false
	}

	name, ok := e.valuesToCanonicalName[v]

	return name, ok
}

func (e *Enum) Value(name string) (uint32, bool) {
	if e == nil {
		return 0, false
	}

	v, ok := e.nameToValue[name]
	if !ok {
		v, ok = e.canonicalNamesToValue[name]
	}

	return v, ok
}

func (e *Enum) Values() []uint32 {
	values := make([]uint32, 0, len(e.valuesToName))
	for v := range e.valuesToName {
		values = append(values, v)
	}
	// Always list them in order of value so output is stable.
	sort.Sort(uint32Slice(values))

	return values
}

func (e *Enum) Bitmask() bool {
	if e == nil {
		return false
	}

	return e.bitMask
}

// Registry holds all the known tags, types, enums and bitmaps declared in
// a KMIP spec.  It's used throughout the package to map values their canonical
// and normalized names.
type Registry struct {
	enums map[Tag]EnumMap
	tags  Enum
	types Enum
}

func (r *Registry) RegisterType(t Type, name string) {
	r.types.RegisterValue(uint32(t), name)
}

func (r *Registry) RegisterTag(t Tag, name string) {
	r.tags.RegisterValue(uint32(t), name)
}

func (r *Registry) RegisterEnum(t Tag, def EnumMap) {
	if r.enums == nil {
		r.enums = map[Tag]EnumMap{}
	}

	r.enums[t] = def
}

// EnumForTag returns the enum map registered for a tag.  Returns
// nil if no map is registered for this tag.
func (r *Registry) EnumForTag(t Tag) EnumMap {
	if r.enums == nil {
		return nil
	}

	return r.enums[t]
}

func (r *Registry) IsBitmask(t Tag) bool {
	if e := r.EnumForTag(t); e != nil {
		return e.Bitmask()
	}

	return false
}

func (r *Registry) IsEnum(t Tag) bool {
	if e := r.EnumForTag(t); e != nil {
		return !e.Bitmask()
	}

	return false
}

func (r *Registry) Tags() EnumMap {
	return &r.tags
}

func (r *Registry) Types() EnumMap {
	return &r.types
}

func (r *Registry) FormatEnum(t Tag, v uint32) string {
	return FormatEnum(v, r.EnumForTag(t))
}

func (r *Registry) FormatInt(t Tag, v int32) string {
	return FormatInt(v, r.EnumForTag(t))
}

func (r *Registry) FormatTag(t Tag) string {
	return FormatTag(uint32(t), &r.tags)
}

func (r *Registry) FormatTagCanonical(t Tag) string {
	return FormatTagCanonical(uint32(t), &r.tags)
}

func (r *Registry) FormatType(t Type) string {
	return FormatType(byte(t), &r.types)
}

func (r *Registry) ParseEnum(t Tag, s string) (uint32, error) {
	return ParseEnum(s, r.EnumForTag(t))
}

func (r *Registry) ParseInt(t Tag, s string) (int32, error) {
	return ParseInt(s, r.EnumForTag(t))
}

// ParseTag parses a string into Tag according the rules
// in the KMIP Profiles regarding encoding tag values.
// Returns TagNone if not found.
// Returns error if s is a malformed hex string, or a hex string of incorrect length
func (r *Registry) ParseTag(s string) (Tag, error) {
	return ParseTag(s, &r.tags)
}

func (r *Registry) ParseType(s string) (Type, error) {
	return ParseType(s, &r.types)
}

// uint32Slice attaches the methods of Interface to []int, sorting in increasing order.
type uint32Slice []uint32

func (p uint32Slice) Len() int           { return len(p) }
func (p uint32Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p uint32Slice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
