package trace

import (
	"strconv"
	"strings"

	"github.com/pkg/errors"
)

const (
	flagSampled = 0x01
	flagDebug   = 0x02
)

var (
	errEmptyTracerString   = errors.New("trace: cannot convert empty string to spancontext")
	errInvalidTracerString = errors.New("trace: string does not match spancontext string format")
)

// SpanContext implements opentracing.SpanContext
type spanContext struct {
	// TraceID represents globally unique ID of the trace.
	// Usually generated as a random number.
	TraceID uint64

	// SpanID represents span ID that must be unique within its trace,
	// but does not have to be globally unique.
	SpanID uint64

	// ParentID refers to the ID of the parent span.
	// Should be 0 if the current span is a root span.
	ParentID uint64

	// Flags is a bitmap containing such bits as 'sampled' and 'debug'.
	Flags byte

	// Probability
	Probability float32

	// Level current level
	Level int
}

func (c spanContext) isSampled() bool {
	return (c.Flags & flagSampled) == flagSampled
}

func (c spanContext) isDebug() bool {
	return (c.Flags & flagDebug) == flagDebug
}

// IsValid check spanContext valid
func (c spanContext) IsValid() bool {
	return c.TraceID != 0 && c.SpanID != 0
}

// emptyContext emptyContext
var emptyContext = spanContext{}

// String convert spanContext to String
// {TraceID}:{SpanID}:{ParentID}:{flags}:[extend...]
// TraceID: uint64 base16
// SpanID: uint64 base16
// ParentID: uint64 base16
// flags:
// - :0 sampled flag
// - :1 debug flag
// extend:
// sample-rate: s-{base16(BigEndian(float32))}
func (c spanContext) String() string {
	base := make([]string, 4)
	base[0] = strconv.FormatUint(uint64(c.TraceID), 16)
	base[1] = strconv.FormatUint(uint64(c.SpanID), 16)
	base[2] = strconv.FormatUint(uint64(c.ParentID), 16)
	base[3] = strconv.FormatUint(uint64(c.Flags), 16)
	return strings.Join(base, ":")
}

// ContextFromString parse spanContext form string
func contextFromString(value string) (spanContext, error) {
	if value == "" {
		return emptyContext, errEmptyTracerString
	}
	items := strings.Split(value, ":")
	if len(items) < 4 {
		return emptyContext, errInvalidTracerString
	}
	parseHexUint64 := func(hexs []string) ([]uint64, error) {
		rets := make([]uint64, len(hexs))
		var err error
		for i, hex := range hexs {
			rets[i], err = strconv.ParseUint(hex, 16, 64)
			if err != nil {
				break
			}
		}
		return rets, err
	}
	rets, err := parseHexUint64(items[0:4])
	if err != nil {
		return emptyContext, errInvalidTracerString
	}
	sctx := spanContext{
		TraceID:  rets[0],
		SpanID:   rets[1],
		ParentID: rets[2],
		Flags:    byte(rets[3]),
	}
	return sctx, nil
}