package log

import (
	"time"

	"github.com/go-stack/stack"
	"github.com/signalfx/golib/v3/timekeeper"
)

// Dynamic values are evaluated at Log() time, not when they are added to the context. They are also only evaulated by
// Context{} objects
type Dynamic interface {
	LogValue() interface{}
}

// DynamicFunc wraps a function to make it Dynamic
type DynamicFunc func() interface{}

// LogValue calls the wrapped function
func (d DynamicFunc) LogValue() interface{} {
	return d()
}

func copyIfDynamic(keyvals []interface{}) []interface{} {
	var newArray []interface{}
	for i := range keyvals {
		if v, ok := keyvals[i].(Dynamic); ok {
			if newArray == nil {
				newArray = make([]interface{}, len(keyvals))
				copy(newArray, keyvals[0:i])
			}
			newArray[i] = v.LogValue()
			continue
		}
		if newArray != nil {
			newArray[i] = keyvals[i]
		}
	}
	if newArray == nil {
		return keyvals
	}
	return newArray
}

// Caller returns line in the stack trace at Depth stack depth
type Caller struct {
	Depth int
}

// LogValue returs the call stack at Depth depth
func (c *Caller) LogValue() interface{} {
	return stack.Caller(c.Depth)
}

// TimeDynamic returns a time.Time() or time string of when the log message happened
type TimeDynamic struct {
	Layout     string
	TimeKeeper timekeeper.TimeKeeper
	UTC        bool
	AsString   bool
}

var _ Dynamic = &TimeDynamic{}

// LogValue returns a timestamp as described by parameters
func (t *TimeDynamic) LogValue() interface{} {
	var now time.Time
	if t.TimeKeeper == nil {
		now = time.Now()
	} else {
		now = t.TimeKeeper.Now()
	}
	if t.UTC {
		now = now.UTC()
	}
	if !t.AsString {
		return now
	}
	if t.Layout == "" {
		return now.Format(time.RFC3339)
	}
	return now.Format(t.Layout)
}

// TimeSince logs the time since the start of the program
type TimeSince struct {
	Start      time.Time
	TimeKeeper timekeeper.TimeKeeper
}

var defaultStartTime = time.Now()

// LogValue returs the time since Start time
func (c *TimeSince) LogValue() interface{} {
	nowFunc := time.Now
	if c.TimeKeeper != nil {
		nowFunc = c.TimeKeeper.Now
	}
	if c.Start.IsZero() {
		return nowFunc().Sub(defaultStartTime)
	}
	return nowFunc().Sub(c.Start)
}

var (
	// DefaultTimestamp returns the local time as a string
	DefaultTimestamp = &TimeDynamic{AsString: true}
	// DefaultTimestampUTC returns local UTC time as a string
	DefaultTimestampUTC = &TimeDynamic{UTC: true, AsString: true}
	// DefaultCaller is what you probably want when using a context
	DefaultCaller = &Caller{Depth: 3}
)