package echelon

import "io"

type genericLogEntry struct {
	LogStarted  *LogScopeStarted
	LogFinished *LogScopeFinished
	LogEntry    *LogEntryMessage
}

type LogRendered interface {
	RenderScopeStarted(entry *LogScopeStarted)
	RenderScopeFinished(entry *LogScopeFinished)
	RenderMessage(entry *LogEntryMessage)
}

type Logger struct {
	maxLogLevel    LogLevel
	scopes         []string
	entriesChannel chan *genericLogEntry
	renderer       LogRendered
}

type loggerAsWriter struct {
	logger *Logger
	level  LogLevel
}

func (w *loggerAsWriter) Write(p []byte) (n int, err error) {
	if w.logger.IsLogLevelEnabled(w.level) {
		logEntryMessage := NewLogEntryMessage(w.logger.scopes, w.level, string(p))
		logEntryMessage.raw = true
		w.logger.entriesChannel <- &genericLogEntry{LogEntry: logEntryMessage}
	}
	return len(p), err
}

type FinishType int

const (
	FinishTypeSucceeded FinishType = iota
	FinishTypeFailed
	FinishTypeSkipped
)

func NewLogger(level LogLevel, renderer LogRendered) *Logger {
	logger := &Logger{
		maxLogLevel:    level,
		entriesChannel: make(chan *genericLogEntry),
		renderer:       renderer,
	}
	go logger.streamEntries(renderer)
	return logger
}

func (logger *Logger) Renderer() LogRendered {
	return logger.renderer
}

func (logger *Logger) Scoped(scope string) *Logger {
	result := &Logger{
		maxLogLevel:    logger.maxLogLevel,
		scopes:         append(logger.scopes, scope),
		entriesChannel: logger.entriesChannel,
	}
	result.entriesChannel <- &genericLogEntry{
		LogStarted: NewLogScopeStarted(result.scopes...),
	}
	return result
}

func (logger *Logger) streamEntries(renderer LogRendered) {
	for {
		entry := <-logger.entriesChannel
		if entry.LogStarted != nil {
			renderer.RenderScopeStarted(entry.LogStarted)
		}
		if entry.LogFinished != nil {
			renderer.RenderScopeFinished(entry.LogFinished)
		}
		if entry.LogEntry != nil {
			renderer.RenderMessage(entry.LogEntry)
		}
	}
}

func (logger *Logger) Tracef(format string, args ...interface{}) {
	logger.Logf(TraceLevel, format, args...)
}

func (logger *Logger) Debugf(format string, args ...interface{}) {
	logger.Logf(DebugLevel, format, args...)
}

func (logger *Logger) Infof(format string, args ...interface{}) {
	logger.Logf(InfoLevel, format, args...)
}

func (logger *Logger) Warnf(format string, args ...interface{}) {
	logger.Logf(WarnLevel, format, args...)
}

func (logger *Logger) Errorf(format string, args ...interface{}) {
	logger.Logf(ErrorLevel, format, args...)
}

func (logger *Logger) Logf(level LogLevel, format string, args ...interface{}) {
	if logger.IsLogLevelEnabled(level) {
		logger.entriesChannel <- &genericLogEntry{
			LogEntry: NewLogEntryMessage(logger.scopes, level, format, args...),
		}
	}
}

func (logger *Logger) AsWriter(level LogLevel) io.Writer {
	return &loggerAsWriter{logger: logger, level: level}
}

func (logger *Logger) Finish(success bool) {
	var finishType FinishType

	if success {
		finishType = FinishTypeSucceeded
	} else {
		finishType = FinishTypeFailed
	}

	logger.FinishWithType(finishType)
}

func (logger *Logger) FinishWithType(finishType FinishType) {
	logger.entriesChannel <- &genericLogEntry{
		LogFinished: NewLogScopeFinished(finishType, logger.scopes...),
	}
}

func (logger *Logger) IsLogLevelEnabled(level LogLevel) bool {
	return level <= logger.maxLogLevel
}
