package trace

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
)

// An ID identifies an inter-process request tree, and optionally identifies a
// particular call within that tree.
type ID struct {
	txID [2]uint64
	path []uint32
}

// String formats the provided ID in the standard format. It includes the
// Trace transaction ID of the current inter-process request tree, and the
// span identifier of a particular call within the tree.
func (id *ID) String() string {
	return id.Transaction() + id.pathString()
}

func (id *ID) pathString() string {
	if id == nil {
		return ""
	}

	var dst []byte
	for _, seg := range id.path {
		dst = append(dst, '.')
		dst = strconv.AppendUint(dst, uint64(seg), 10)
	}
	return string(dst)
}

// Transaction returns the Trace transaction ID of the current inter-process
// request tree. It is formatted in the standard format, hex-encoded with the
// least significant byte first.
func (id *ID) Transaction() string {
	if id == nil {
		return ""
	}

	var txid [16]byte
	binary.LittleEndian.PutUint64(txid[:8], id.txID[0])
	binary.LittleEndian.PutUint64(txid[8:], id.txID[1])
	return fmt.Sprintf("%02x", txid)
}

// ParseID parses s as a Trace transaction ID and optional span specifier,
// returning the result. If s is not a valid textual representation of a
// transaction ID, or transaction ID and span specifier, ParseID returns nil.
func ParseID(s string) *ID {
	var id ID

	const txidLen = len(id.txID) * 16
	if len(s) < txidLen {
		return nil
	}
	hexPrefix, rest := s[:txidLen], s[txidLen:]

	for i := range id.txID {
		b, err := hex.DecodeString(hexPrefix[i*16 : (i+1)*16])
		if err != nil {
			return nil
		}
		id.txID[i] = binary.LittleEndian.Uint64(b)
	}

	for i, seg := range strings.Split(rest, ".") {
		if i == 0 {
			if seg != "" {
				return nil
			}
			continue
		}
		seg, err := strconv.ParseUint(seg, 10, 32)
		if err != nil {
			return nil
		}
		id.path = append(id.path, uint32(seg))
	}

	return &id
}
