// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package telemetry

import (
	crand "crypto/rand"
	"encoding/binary"
	"fmt"
	"math/rand"
	"sync"
	"sync/atomic"
)

type TraceID [16]byte
type SpanID [8]byte

func (t TraceID) String() string {
	return fmt.Sprintf("%02x", t[:])
}

func (s SpanID) String() string {
	return fmt.Sprintf("%02x", s[:])
}

func (s SpanID) IsValid() bool {
	return s != SpanID{}
}

var (
	generationMu sync.Mutex
	nextSpanID   uint64
	spanIDInc    uint64

	traceIDAdd  [2]uint64
	traceIDRand *rand.Rand
)

func initGenerator() {
	var rngSeed int64
	for _, p := range []interface{}{
		&rngSeed, &traceIDAdd, &nextSpanID, &spanIDInc,
	} {
		binary.Read(crand.Reader, binary.LittleEndian, p)
	}
	traceIDRand = rand.New(rand.NewSource(rngSeed))
	spanIDInc |= 1
}

func NewTraceID() TraceID {
	generationMu.Lock()
	defer generationMu.Unlock()
	if traceIDRand == nil {
		initGenerator()
	}
	var tid [16]byte
	binary.LittleEndian.PutUint64(tid[0:8], traceIDRand.Uint64()+traceIDAdd[0])
	binary.LittleEndian.PutUint64(tid[8:16], traceIDRand.Uint64()+traceIDAdd[1])
	return tid
}

func NewSpanID() SpanID {
	var id uint64
	for id == 0 {
		id = atomic.AddUint64(&nextSpanID, spanIDInc)
	}
	var sid [8]byte
	binary.LittleEndian.PutUint64(sid[:], id)
	return sid
}
