// 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 export holds the definition of the telemetry Exporter interface,
// along with some simple implementations.
// Larger more complex exporters are in sub packages of their own.
package export

import (
	"context"
	"os"
	"sync"
	"time"

	"golang.org/x/tools/internal/telemetry"
)

type Exporter interface {
	StartSpan(context.Context, *telemetry.Span)
	FinishSpan(context.Context, *telemetry.Span)

	// Log is a function that handles logging events.
	// Observers may use information in the context to decide what to do with a
	// given log event. They should return true if they choose to handle the
	Log(context.Context, telemetry.Event)

	Metric(context.Context, telemetry.MetricData)

	Flush()
}

var (
	exporterMu sync.Mutex
	exporter   = LogWriter(os.Stderr, true)
)

func AddExporters(e ...Exporter) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	exporter = Multi(append([]Exporter{exporter}, e...)...)
}

func StartSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	span.Start = at
	exporter.StartSpan(ctx, span)
}

func FinishSpan(ctx context.Context, span *telemetry.Span, at time.Time) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	span.Finish = at
	exporter.FinishSpan(ctx, span)
}

func Tag(ctx context.Context, at time.Time, tags telemetry.TagList) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	// If context has a span we need to add the tags to it
	span := telemetry.GetSpan(ctx)
	if span == nil {
		return
	}
	if span.Start.IsZero() {
		// span still being created, tag it directly
		span.Tags = append(span.Tags, tags...)
		return
	}
	// span in progress, add an event to the span
	span.Events = append(span.Events, telemetry.Event{
		At:   at,
		Tags: tags,
	})
}

func Log(ctx context.Context, event telemetry.Event) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	// If context has a span we need to add the event to it
	span := telemetry.GetSpan(ctx)
	if span != nil {
		span.Events = append(span.Events, event)
	}
	// and now also hand the event of to the current observer
	exporter.Log(ctx, event)
}

func Metric(ctx context.Context, data telemetry.MetricData) {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	exporter.Metric(ctx, data)
}

func Flush() {
	exporterMu.Lock()
	defer exporterMu.Unlock()
	exporter.Flush()
}
