// Package pvdata implements the EPICS pvData serialisation layer.
//
// pvData defines a rich, self-describing type system used by PV Access (PVA).
// Every value that flows over a PVA connection is encoded according to a
// FieldDesc (field description / introspection interface), which is itself
// serialised on the wire before the payload.
//
// Reference: EPICS pvData Specification r1.1
// https://epics-pvdata.sourceforge.net/pvData.html
package pvdata

import (
	"encoding/binary"
	"fmt"
	"io"
	"math"
)

// ---- Type codes -------------------------------------------------------
//
// pvData uses a one-byte "type code" to identify scalar and array element
// types.  Values from the spec §3.1.

const (
	TypeCodeBoolean   byte = 0x00
	TypeCodeByte      byte = 0x20 // int8
	TypeCodeShort     byte = 0x21 // int16
	TypeCodeInt       byte = 0x22 // int32
	TypeCodeLong      byte = 0x23 // int64
	TypeCodeUByte     byte = 0x24 // uint8
	TypeCodeUShort    byte = 0x25 // uint16
	TypeCodeUInt      byte = 0x26 // uint32
	TypeCodeULong     byte = 0x27 // uint64
	TypeCodeFloat     byte = 0x42 // float32
	TypeCodeDouble    byte = 0x43 // float64
	TypeCodeString    byte = 0x60
	TypeCodeStruct    byte = 0x80
	TypeCodeUnion     byte = 0x81
	TypeCodeBoolArr   byte = 0x08
	TypeCodeByteArr   byte = 0x28
	TypeCodeShortArr  byte = 0x29
	TypeCodeIntArr    byte = 0x2A
	TypeCodeLongArr   byte = 0x2B
	TypeCodeUByteArr  byte = 0x2C
	TypeCodeUShortArr byte = 0x2D
	TypeCodeUIntArr   byte = 0x2E
	TypeCodeULongArr  byte = 0x2F
	TypeCodeFloatArr  byte = 0x4A
	TypeCodeDoubleArr byte = 0x4B
	TypeCodeStringArr byte = 0x68
	TypeCodeStructArr byte = 0x88
	TypeCodeUnionArr  byte = 0x89
	TypeCodeVariant   byte = 0x82 // any
	TypeCodeNull      byte = 0xFF
)

// ---- Compact size encoding --------------------------------------------
//
// pvData encodes array/string lengths as a "size" using 1, 4, or 8 bytes
// (spec §3.2).

// ReadSize reads a compact-format size from r.
// 0x00–0xFE → 1-byte value
// 0xFF       → read 4-byte int32 (negative = 8-byte int64 follows)
func ReadSize(r io.Reader) (int64, error) {
	var b [1]byte
	if _, err := io.ReadFull(r, b[:]); err != nil {
		return 0, err
	}
	if b[0] != 0xFF {
		return int64(b[0]), nil
	}
	var v int32
	if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
		return 0, err
	}
	if v >= 0 {
		return int64(v), nil
	}
	// extended 8-byte size
	var v8 int64
	if err := binary.Read(r, binary.LittleEndian, &v8); err != nil {
		return 0, err
	}
	return v8, nil
}

// WriteSize writes n as a compact-format size to w.
func WriteSize(w io.Writer, n int64) error {
	if n < 0xFF {
		_, err := w.Write([]byte{byte(n)})
		return err
	}
	if n <= math.MaxInt32 {
		if _, err := w.Write([]byte{0xFF}); err != nil {
			return err
		}
		return binary.Write(w, binary.LittleEndian, int32(n))
	}
	// rare: >2 GiB array
	if _, err := w.Write([]byte{0xFF}); err != nil {
		return err
	}
	if err := binary.Write(w, binary.LittleEndian, int32(-1)); err != nil {
		return err
	}
	return binary.Write(w, binary.LittleEndian, n)
}

// ReadString reads a pvData string (compact size + UTF-8 bytes).
func ReadString(r io.Reader) (string, error) {
	n, err := ReadSize(r)
	if err != nil {
		return "", err
	}
	if n == 0 {
		return "", nil
	}
	buf := make([]byte, n)
	if _, err := io.ReadFull(r, buf); err != nil {
		return "", err
	}
	return string(buf), nil
}

// WriteString writes a pvData string to w.
func WriteString(w io.Writer, s string) error {
	if err := WriteSize(w, int64(len(s))); err != nil {
		return err
	}
	_, err := io.WriteString(w, s)
	return err
}

// ---- Field description (introspection) --------------------------------

// Kind classifies a FieldDesc at a high level.
type Kind byte

const (
	KindScalar    Kind = iota // single scalar value
	KindScalarArr             // variable-length array of scalars
	KindString                // pvData string (treated specially)
	KindStringArr
	KindStruct // structure
	KindStructArr
	KindUnion // discriminated union
	KindUnionArr
	KindVariant // any (variant)
)

// FieldDesc describes the type of a single field in a pvData structure.
type FieldDesc struct {
	Kind     Kind
	TypeCode byte    // scalar/array type code; 0 for struct/union
	TypeID   string  // optional structure/union type ID string
	Fields   []Field // child fields for struct/union
}

// Field is a named FieldDesc (one member of a struct or union).
type Field struct {
	Name string
	Desc FieldDesc
}

// ---- FieldDesc codec --------------------------------------------------

// ReadFieldDesc decodes a FieldDesc from r.
// This follows the pvData introspection encoding (spec §4).
func ReadFieldDesc(r io.Reader) (FieldDesc, error) {
	var tc [1]byte
	if _, err := io.ReadFull(r, tc[:]); err != nil {
		return FieldDesc{}, err
	}
	return readFieldDescFromTypeCode(r, tc[0])
}

func readFieldDescFromTypeCode(r io.Reader, tc byte) (FieldDesc, error) {
	switch {
	case tc == TypeCodeNull:
		return FieldDesc{TypeCode: TypeCodeNull}, nil

	case tc == TypeCodeString:
		return FieldDesc{Kind: KindString, TypeCode: tc}, nil

	case tc == TypeCodeStringArr:
		return FieldDesc{Kind: KindStringArr, TypeCode: tc}, nil

	case tc == TypeCodeVariant:
		return FieldDesc{Kind: KindVariant, TypeCode: tc}, nil

	case tc == TypeCodeStruct || tc == TypeCodeUnion:
		kind := KindStruct
		if tc == TypeCodeUnion {
			kind = KindUnion
		}
		typeID, err := ReadString(r)
		if err != nil {
			return FieldDesc{}, err
		}
		nfields, err := ReadSize(r)
		if err != nil {
			return FieldDesc{}, err
		}
		fields := make([]Field, nfields)
		for i := range fields {
			name, err := ReadString(r)
			if err != nil {
				return FieldDesc{}, err
			}
			desc, err := ReadFieldDesc(r)
			if err != nil {
				return FieldDesc{}, err
			}
			fields[i] = Field{Name: name, Desc: desc}
		}
		return FieldDesc{Kind: kind, TypeCode: tc, TypeID: typeID, Fields: fields}, nil

	case tc == TypeCodeStructArr || tc == TypeCodeUnionArr:
		kind := KindStructArr
		if tc == TypeCodeUnionArr {
			kind = KindUnionArr
		}
		// element descriptor follows
		elem, err := ReadFieldDesc(r)
		if err != nil {
			return FieldDesc{}, err
		}
		return FieldDesc{Kind: kind, TypeCode: tc, TypeID: elem.TypeID, Fields: elem.Fields}, nil

	// scalar array types: 0x28–0x2F, 0x4A–0x4B, 0x08, 0x68
	case isScalarArrayCode(tc):
		return FieldDesc{Kind: KindScalarArr, TypeCode: tc}, nil

	// plain scalars and boolean
	default:
		return FieldDesc{Kind: KindScalar, TypeCode: tc}, nil
	}
}

func isScalarArrayCode(tc byte) bool {
	switch tc {
	case TypeCodeBoolArr,
		TypeCodeByteArr, TypeCodeShortArr, TypeCodeIntArr, TypeCodeLongArr,
		TypeCodeUByteArr, TypeCodeUShortArr, TypeCodeUIntArr, TypeCodeULongArr,
		TypeCodeFloatArr, TypeCodeDoubleArr,
		TypeCodeStringArr:
		return true
	}
	return false
}

// WriteFieldDesc encodes fd into w.
func WriteFieldDesc(w io.Writer, fd FieldDesc) error {
	if _, err := w.Write([]byte{fd.TypeCode}); err != nil {
		return err
	}
	switch fd.Kind {
	case KindStruct, KindUnion:
		if err := WriteString(w, fd.TypeID); err != nil {
			return err
		}
		if err := WriteSize(w, int64(len(fd.Fields))); err != nil {
			return err
		}
		for _, f := range fd.Fields {
			if err := WriteString(w, f.Name); err != nil {
				return err
			}
			if err := WriteFieldDesc(w, f.Desc); err != nil {
				return err
			}
		}
	case KindStructArr, KindUnionArr:
		// write element descriptor
		elemTC := TypeCodeStruct
		if fd.Kind == KindUnionArr {
			elemTC = TypeCodeUnion
		}
		if err := WriteFieldDesc(w, FieldDesc{
			Kind: KindStruct, TypeCode: byte(elemTC),
			TypeID: fd.TypeID, Fields: fd.Fields,
		}); err != nil {
			return err
		}
	}
	return nil
}

// ---- Value codec -------------------------------------------------------

// Value is a decoded pvData value.  We use interface{} for flexibility;
// callers type-assert to concrete types listed below.
//
// Scalar types:
//
//	bool, int8, int16, int32, int64,
//	uint8, uint16, uint32, uint64,
//	float32, float64, string
//
// Array types: []T for each scalar T above; []string.
// Struct:      StructValue
// Union:       UnionValue
// Variant:     VariantValue
// Null:        nil
type Value = interface{}

// StructValue holds the fields of a decoded pvData structure.
type StructValue struct {
	TypeID string
	Fields []FieldValue
}

// FieldValue is a named value within a StructValue.
type FieldValue struct {
	Name  string
	Value Value
}

// UnionValue holds a selected field from a discriminated union.
type UnionValue struct {
	Selected int    // index into union's Fields slice
	Name     string // field name
	Value    Value
}

// VariantValue wraps an any-typed value together with its runtime descriptor.
type VariantValue struct {
	Desc  FieldDesc
	Value Value
}

// ReadValue decodes a value conforming to desc from r.
func ReadValue(r io.Reader, desc FieldDesc) (Value, error) {
	switch desc.Kind {
	case KindScalar:
		return readScalar(r, desc.TypeCode)
	case KindScalarArr:
		return readScalarArray(r, desc.TypeCode)
	case KindString:
		return ReadString(r)
	case KindStringArr:
		n, err := ReadSize(r)
		if err != nil {
			return nil, err
		}
		ss := make([]string, n)
		for i := range ss {
			ss[i], err = ReadString(r)
			if err != nil {
				return nil, err
			}
		}
		return ss, nil
	case KindStruct:
		return readStruct(r, desc)
	case KindStructArr:
		return readStructArr(r, desc)
	case KindUnion:
		return readUnion(r, desc)
	case KindUnionArr:
		return readUnionArr(r, desc)
	case KindVariant:
		return readVariant(r)
	default:
		return nil, fmt.Errorf("pvdata: unknown kind %d", desc.Kind)
	}
}

func readScalar(r io.Reader, tc byte) (Value, error) {
	switch tc {
	case TypeCodeBoolean:
		var b [1]byte
		_, err := io.ReadFull(r, b[:])
		return b[0] != 0, err
	case TypeCodeByte:
		var v int8
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeShort:
		var v int16
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeInt:
		var v int32
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeLong:
		var v int64
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeUByte:
		var v uint8
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeUShort:
		var v uint16
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeUInt:
		var v uint32
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeULong:
		var v uint64
		return v, binary.Read(r, binary.LittleEndian, &v)
	case TypeCodeFloat:
		var v uint32
		if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
			return nil, err
		}
		return math.Float32frombits(v), nil
	case TypeCodeDouble:
		var v uint64
		if err := binary.Read(r, binary.LittleEndian, &v); err != nil {
			return nil, err
		}
		return math.Float64frombits(v), nil
	default:
		return nil, fmt.Errorf("pvdata: unknown scalar type code 0x%02X", tc)
	}
}

func readScalarArray(r io.Reader, tc byte) (Value, error) {
	n, err := ReadSize(r)
	if err != nil {
		return nil, err
	}
	count := int(n)
	switch tc {
	case TypeCodeBoolArr:
		v := make([]bool, count)
		for i := range v {
			b := [1]byte{}
			if _, err := io.ReadFull(r, b[:]); err != nil {
				return nil, err
			}
			v[i] = b[0] != 0
		}
		return v, nil
	case TypeCodeByteArr:
		v := make([]int8, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeShortArr:
		v := make([]int16, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeIntArr:
		v := make([]int32, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeLongArr:
		v := make([]int64, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeUByteArr:
		v := make([]uint8, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeUShortArr:
		v := make([]uint16, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeUIntArr:
		v := make([]uint32, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeULongArr:
		v := make([]uint64, count)
		return v, binary.Read(r, binary.LittleEndian, v)
	case TypeCodeFloatArr:
		bits := make([]uint32, count)
		if err := binary.Read(r, binary.LittleEndian, bits); err != nil {
			return nil, err
		}
		v := make([]float32, count)
		for i, b := range bits {
			v[i] = math.Float32frombits(b)
		}
		return v, nil
	case TypeCodeDoubleArr:
		bits := make([]uint64, count)
		if err := binary.Read(r, binary.LittleEndian, bits); err != nil {
			return nil, err
		}
		v := make([]float64, count)
		for i, b := range bits {
			v[i] = math.Float64frombits(b)
		}
		return v, nil
	default:
		return nil, fmt.Errorf("pvdata: unknown array type code 0x%02X", tc)
	}
}

func readStruct(r io.Reader, desc FieldDesc) (StructValue, error) {
	sv := StructValue{TypeID: desc.TypeID, Fields: make([]FieldValue, len(desc.Fields))}
	for i, f := range desc.Fields {
		v, err := ReadValue(r, f.Desc)
		if err != nil {
			return StructValue{}, fmt.Errorf("field %q: %w", f.Name, err)
		}
		sv.Fields[i] = FieldValue{Name: f.Name, Value: v}
	}
	return sv, nil
}

func readStructArr(r io.Reader, desc FieldDesc) ([]StructValue, error) {
	n, err := ReadSize(r)
	if err != nil {
		return nil, err
	}
	arr := make([]StructValue, n)
	for i := range arr {
		sv, err := readStruct(r, desc)
		if err != nil {
			return nil, err
		}
		arr[i] = sv
	}
	return arr, nil
}

func readUnion(r io.Reader, desc FieldDesc) (UnionValue, error) {
	idx, err := ReadSize(r)
	if err != nil {
		return UnionValue{}, err
	}
	if idx < 0 || int(idx) >= len(desc.Fields) {
		return UnionValue{Selected: int(idx)}, nil // null selector
	}
	f := desc.Fields[idx]
	v, err := ReadValue(r, f.Desc)
	if err != nil {
		return UnionValue{}, err
	}
	return UnionValue{Selected: int(idx), Name: f.Name, Value: v}, nil
}

func readUnionArr(r io.Reader, desc FieldDesc) ([]UnionValue, error) {
	n, err := ReadSize(r)
	if err != nil {
		return nil, err
	}
	arr := make([]UnionValue, n)
	for i := range arr {
		uv, err := readUnion(r, desc)
		if err != nil {
			return nil, err
		}
		arr[i] = uv
	}
	return arr, nil
}

func readVariant(r io.Reader) (VariantValue, error) {
	desc, err := ReadFieldDesc(r)
	if err != nil {
		return VariantValue{}, err
	}
	v, err := ReadValue(r, desc)
	if err != nil {
		return VariantValue{}, err
	}
	return VariantValue{Desc: desc, Value: v}, nil
}

// WriteValue encodes v (which must match desc) into w.
func WriteValue(w io.Writer, desc FieldDesc, v Value) error {
	switch desc.Kind {
	case KindScalar:
		return writeScalar(w, desc.TypeCode, v)
	case KindScalarArr:
		return writeScalarArray(w, desc.TypeCode, v)
	case KindString:
		return WriteString(w, v.(string))
	case KindStringArr:
		ss := v.([]string)
		if err := WriteSize(w, int64(len(ss))); err != nil {
			return err
		}
		for _, s := range ss {
			if err := WriteString(w, s); err != nil {
				return err
			}
		}
		return nil
	case KindStruct:
		return writeStruct(w, desc, v.(StructValue))
	case KindUnion:
		return writeUnion(w, v.(UnionValue))
	case KindVariant:
		vv := v.(VariantValue)
		if err := WriteFieldDesc(w, vv.Desc); err != nil {
			return err
		}
		return WriteValue(w, vv.Desc, vv.Value)
	default:
		return fmt.Errorf("pvdata: WriteValue unsupported kind %d", desc.Kind)
	}
}

func writeScalar(w io.Writer, tc byte, v Value) error {
	switch tc {
	case TypeCodeBoolean:
		b := byte(0)
		if v.(bool) {
			b = 1
		}
		_, err := w.Write([]byte{b})
		return err
	case TypeCodeFloat:
		return binary.Write(w, binary.LittleEndian, math.Float32bits(v.(float32)))
	case TypeCodeDouble:
		return binary.Write(w, binary.LittleEndian, math.Float64bits(v.(float64)))
	default:
		return binary.Write(w, binary.LittleEndian, v)
	}
}

func writeScalarArray(w io.Writer, tc byte, v Value) error {
	switch tc {
	case TypeCodeDoubleArr:
		arr := v.([]float64)
		if err := WriteSize(w, int64(len(arr))); err != nil {
			return err
		}
		bits := make([]uint64, len(arr))
		for i, f := range arr {
			bits[i] = math.Float64bits(f)
		}
		return binary.Write(w, binary.LittleEndian, bits)
	case TypeCodeFloatArr:
		arr := v.([]float32)
		if err := WriteSize(w, int64(len(arr))); err != nil {
			return err
		}
		bits := make([]uint32, len(arr))
		for i, f := range arr {
			bits[i] = math.Float32bits(f)
		}
		return binary.Write(w, binary.LittleEndian, bits)
	default:
		// for integer arrays, v is already []intN or []uintN
		if err := WriteSize(w, int64(arrayLen(v))); err != nil {
			return err
		}
		return binary.Write(w, binary.LittleEndian, v)
	}
}

func writeStruct(w io.Writer, desc FieldDesc, sv StructValue) error {
	for i, fv := range sv.Fields {
		if err := WriteValue(w, desc.Fields[i].Desc, fv.Value); err != nil {
			return fmt.Errorf("field %q: %w", fv.Name, err)
		}
	}
	return nil
}

func writeUnion(w io.Writer, uv UnionValue) error {
	if err := WriteSize(w, int64(uv.Selected)); err != nil {
		return err
	}
	// caller must supply desc to encode value; not needed if Selected is null
	return nil
}

// arrayLen returns reflect-free length for the supported array types.
func arrayLen(v Value) int {
	switch x := v.(type) {
	case []bool:
		return len(x)
	case []int8:
		return len(x)
	case []int16:
		return len(x)
	case []int32:
		return len(x)
	case []int64:
		return len(x)
	case []uint8:
		return len(x)
	case []uint16:
		return len(x)
	case []uint32:
		return len(x)
	case []uint64:
		return len(x)
	default:
		return 0
	}
}
