package timing

import (
	"fmt"
	"time"

	"code.justin.tv/common/golibs/statsd"
	"golang.org/x/net/context"
)

// The key type is unexported to prevent collisions with context keys defined in other packages
type key int

const xactKey key = 0

func XactContext(ctx context.Context, xact *Xact) context.Context {
	return context.WithValue(ctx, xactKey, xact)
}

func XactFromContext(ctx context.Context) (*Xact, bool) {
	xact, ok := ctx.Value(xactKey).(*Xact)
	return xact, ok
}

type Xact struct {
	Name  string
	Stats statsd.Stats

	timings map[string]time.Duration
	timer
}

func (xact *Xact) End(stat string) time.Duration {
	if xact.pending() {
		xact.timer.End()
		xact.Stats.Timing(fmt.Sprintf("%s.%s.total", xact.Name, stat), xact.duration())
		if xact.timings != nil {
			for group, duration := range xact.timings {
				xact.Stats.Timing(fmt.Sprintf("%s.%s.%s", xact.Name, stat, group), duration)
			}
		}
	}
	return xact.duration()
}

func (xact *Xact) Sub(group string) *SubXact {
	return &SubXact{
		parent: xact,
		group:  group,
	}
}

func (xact *Xact) addTiming(group string, d time.Duration) {
	if xact.timings == nil {
		xact.timings = make(map[string]time.Duration)
	}
	old := xact.timings[group]
	xact.timings[group] = old + d
}

type SubXact struct {
	parent *Xact
	group  string
	timer
}

func (sub *SubXact) End() time.Duration {
	if sub.pending() {
		sub.timer.End()
		sub.parent.addTiming(sub.group, sub.duration())
	}
	return sub.duration()
}

type timer struct {
	start time.Time
	end   time.Time
}

func (t *timer) Start() time.Time {
	if !t.pending() {
		t.start = time.Now()
	}
	return t.start
}

func (t *timer) End() time.Duration {
	if t.pending() {
		t.end = time.Now()
	}
	return t.duration()
}

func (t *timer) pending() bool {
	return !t.start.IsZero() && t.end.IsZero()
}

func (t *timer) duration() time.Duration {
	if !t.end.IsZero() {
		return t.end.Sub(t.start)
	} else {
		return 0
	}
}
