package log

import (
	"context"
	"fmt"
	"runtime"
	"sync"
	"sync/atomic"
	"time"
)

// ContextTracerKey is the key used for the context key/value storage.
type ContextTracerKey struct{}

// ContextTracer is attached to a context in order bind logs to a context.
type ContextTracer struct {
	sync.Mutex
	logs []*logLine
}

var key = ContextTracerKey{}

// AddTracer adds a ContextTracer to the returned Context. Will return a nil ContextTracer if logging level is not set to trace. Will return a nil ContextTracer if one already exists. Will return a nil ContextTracer in case of an error. Will return a nil context if nil.
func AddTracer(ctx context.Context) (context.Context, *ContextTracer) {
	if ctx != nil && fastcheck(TraceLevel) {
		// Check log level.
		if atomic.LoadUint32(logLevel) > uint32(TraceLevel) {
			return ctx, nil
		}

		// check for existing tracer
		_, ok := ctx.Value(key).(*ContextTracer)
		if !ok {
			// add and return new tracer
			tracer := &ContextTracer{}
			return context.WithValue(ctx, key, tracer), tracer
		}
	}
	return ctx, nil
}

// Tracer returns the ContextTracer previously added to the given Context.
func Tracer(ctx context.Context) *ContextTracer {
	if ctx != nil {
		tracer, ok := ctx.Value(key).(*ContextTracer)
		if ok {
			return tracer
		}
	}
	return nil
}

// Submit collected logs on the context for further processing/outputting. Does nothing if called on a nil ContextTracer.
func (tracer *ContextTracer) Submit() {
	if tracer == nil {
		return
	}

	if !started.IsSet() {
		// a bit resource intense, but keeps logs before logging started.
		// TODO: create option to disable logging
		go func() {
			<-startedSignal
			tracer.Submit()
		}()
		return
	}

	if len(tracer.logs) == 0 {
		return
	}

	// extract last line as main line
	mainLine := tracer.logs[len(tracer.logs)-1]
	tracer.logs = tracer.logs[:len(tracer.logs)-1]

	// create log object
	log := &logLine{
		msg:       mainLine.msg,
		tracer:    tracer,
		level:     mainLine.level,
		timestamp: mainLine.timestamp,
		file:      mainLine.file,
		line:      mainLine.line,
	}

	// send log to processing
	select {
	case logBuffer <- log:
	default:
	forceEmptyingLoop:
		// force empty buffer until we can send to it
		for {
			select {
			case forceEmptyingOfBuffer <- struct{}{}:
			case logBuffer <- log:
				break forceEmptyingLoop
			}
		}
	}

	// wake up writer if necessary
	if logsWaitingFlag.SetToIf(false, true) {
		logsWaiting <- struct{}{}
	}
}

func (tracer *ContextTracer) log(level Severity, msg string) {
	// get file and line
	_, file, line, ok := runtime.Caller(2)
	if !ok {
		file = ""
		line = 0
	} else {
		if len(file) > 3 {
			file = file[:len(file)-3]
		} else {
			file = ""
		}
	}

	tracer.Lock()
	defer tracer.Unlock()
	tracer.logs = append(tracer.logs, &logLine{
		timestamp: time.Now(),
		level:     level,
		msg:       msg,
		file:      file,
		line:      line,
	})
}

// Trace is used to log tiny steps. Log traces to context if you can!
func (tracer *ContextTracer) Trace(msg string) {
	switch {
	case tracer != nil:
		tracer.log(TraceLevel, msg)
	case fastcheck(TraceLevel):
		log(TraceLevel, msg, nil)
	}
}

// Tracef is used to log tiny steps. Log traces to context if you can!
func (tracer *ContextTracer) Tracef(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(TraceLevel, fmt.Sprintf(format, things...))
	case fastcheck(TraceLevel):
		log(TraceLevel, fmt.Sprintf(format, things...), nil)
	}
}

// Debug is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func (tracer *ContextTracer) Debug(msg string) {
	switch {
	case tracer != nil:
		tracer.log(DebugLevel, msg)
	case fastcheck(DebugLevel):
		log(DebugLevel, msg, nil)
	}
}

// Debugf is used to log minor errors or unexpected events. These occurrences are usually not worth mentioning in itself, but they might hint at a bigger problem.
func (tracer *ContextTracer) Debugf(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(DebugLevel, fmt.Sprintf(format, things...))
	case fastcheck(DebugLevel):
		log(DebugLevel, fmt.Sprintf(format, things...), nil)
	}
}

// Info is used to log mildly significant events. Should be used to inform about somewhat bigger or user affecting events that happen.
func (tracer *ContextTracer) Info(msg string) {
	switch {
	case tracer != nil:
		tracer.log(InfoLevel, msg)
	case fastcheck(InfoLevel):
		log(InfoLevel, msg, nil)
	}
}

// Infof is used to log mildly significant events. Should be used to inform about somewhat bigger or user affecting events that happen.
func (tracer *ContextTracer) Infof(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(InfoLevel, fmt.Sprintf(format, things...))
	case fastcheck(InfoLevel):
		log(InfoLevel, fmt.Sprintf(format, things...), nil)
	}
}

// Warning is used to log (potentially) bad events, but nothing broke (even a little) and there is no need to panic yet.
func (tracer *ContextTracer) Warning(msg string) {
	switch {
	case tracer != nil:
		tracer.log(WarningLevel, msg)
	case fastcheck(WarningLevel):
		log(WarningLevel, msg, nil)
	}
}

// Warningf is used to log (potentially) bad events, but nothing broke (even a little) and there is no need to panic yet.
func (tracer *ContextTracer) Warningf(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(WarningLevel, fmt.Sprintf(format, things...))
	case fastcheck(WarningLevel):
		log(WarningLevel, fmt.Sprintf(format, things...), nil)
	}
}

// Error is used to log errors that break or impair functionality. The task/process may have to be aborted and tried again later. The system is still operational. Maybe User/Admin should be informed.
func (tracer *ContextTracer) Error(msg string) {
	switch {
	case tracer != nil:
		tracer.log(ErrorLevel, msg)
	case fastcheck(ErrorLevel):
		log(ErrorLevel, msg, nil)
	}
}

// Errorf is used to log errors that break or impair functionality. The task/process may have to be aborted and tried again later. The system is still operational.
func (tracer *ContextTracer) Errorf(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(ErrorLevel, fmt.Sprintf(format, things...))
	case fastcheck(ErrorLevel):
		log(ErrorLevel, fmt.Sprintf(format, things...), nil)
	}
}

// Critical is used to log events that completely break the system. Operation connot continue. User/Admin must be informed.
func (tracer *ContextTracer) Critical(msg string) {
	switch {
	case tracer != nil:
		tracer.log(CriticalLevel, msg)
	case fastcheck(CriticalLevel):
		log(CriticalLevel, msg, nil)
	}
}

// Criticalf is used to log events that completely break the system. Operation connot continue. User/Admin must be informed.
func (tracer *ContextTracer) Criticalf(format string, things ...interface{}) {
	switch {
	case tracer != nil:
		tracer.log(CriticalLevel, fmt.Sprintf(format, things...))
	case fastcheck(CriticalLevel):
		log(CriticalLevel, fmt.Sprintf(format, things...), nil)
	}
}
