package level

import (
	"errors"
	"strings"

	"github.com/go-kit/log"
)

// ErrInvalidLevelString is returned whenever an invalid string is passed to Parse.
var ErrInvalidLevelString = errors.New("invalid level string")

// Error returns a logger that includes a Key/ErrorValue pair.
func Error(logger log.Logger) log.Logger {
	return log.WithPrefix(logger, Key(), ErrorValue())
}

// Warn returns a logger that includes a Key/WarnValue pair.
func Warn(logger log.Logger) log.Logger {
	return log.WithPrefix(logger, Key(), WarnValue())
}

// Info returns a logger that includes a Key/InfoValue pair.
func Info(logger log.Logger) log.Logger {
	return log.WithPrefix(logger, Key(), InfoValue())
}

// Debug returns a logger that includes a Key/DebugValue pair.
func Debug(logger log.Logger) log.Logger {
	return log.WithPrefix(logger, Key(), DebugValue())
}

// NewFilter wraps next and implements level filtering. See the commentary on
// the Option functions for a detailed description of how to configure levels.
// If no options are provided, all leveled log events created with Debug,
// Info, Warn or Error helper methods are squelched and non-leveled log
// events are passed to next unmodified.
func NewFilter(next log.Logger, options ...Option) log.Logger {
	l := &logger{
		next: next,
	}
	for _, option := range options {
		option(l)
	}
	return l
}

type logger struct {
	next           log.Logger
	allowed        level
	squelchNoLevel bool
	errNotAllowed  error
	errNoLevel     error
}

func (l *logger) Log(keyvals ...interface{}) error {
	var hasLevel, levelAllowed bool
	for i := 1; i < len(keyvals); i += 2 {
		if v, ok := keyvals[i].(*levelValue); ok {
			hasLevel = true
			levelAllowed = l.allowed&v.level != 0
			break
		}
	}
	if !hasLevel && l.squelchNoLevel {
		return l.errNoLevel
	}
	if hasLevel && !levelAllowed {
		return l.errNotAllowed
	}
	return l.next.Log(keyvals...)
}

// Option sets a parameter for the leveled logger.
type Option func(*logger)

// Allow the provided log level to pass.
func Allow(v Value) Option {
	switch v {
	case debugValue:
		return AllowDebug()
	case infoValue:
		return AllowInfo()
	case warnValue:
		return AllowWarn()
	case errorValue:
		return AllowError()
	default:
		return AllowNone()
	}
}

// AllowAll is an alias for AllowDebug.
func AllowAll() Option {
	return AllowDebug()
}

// AllowDebug allows error, warn, info and debug level log events to pass.
func AllowDebug() Option {
	return allowed(levelError | levelWarn | levelInfo | levelDebug)
}

// AllowInfo allows error, warn and info level log events to pass.
func AllowInfo() Option {
	return allowed(levelError | levelWarn | levelInfo)
}

// AllowWarn allows error and warn level log events to pass.
func AllowWarn() Option {
	return allowed(levelError | levelWarn)
}

// AllowError allows only error level log events to pass.
func AllowError() Option {
	return allowed(levelError)
}

// AllowNone allows no leveled log events to pass.
func AllowNone() Option {
	return allowed(0)
}

func allowed(allowed level) Option {
	return func(l *logger) { l.allowed = allowed }
}

// Parse a string to its corresponding level value. Valid strings are "debug",
// "info", "warn", and "error". Strings are normalized via strings.TrimSpace and
// strings.ToLower.
func Parse(level string) (Value, error) {
	switch strings.TrimSpace(strings.ToLower(level)) {
	case debugValue.name:
		return debugValue, nil
	case infoValue.name:
		return infoValue, nil
	case warnValue.name:
		return warnValue, nil
	case errorValue.name:
		return errorValue, nil
	default:
		return nil, ErrInvalidLevelString
	}
}

// ParseDefault calls Parse and returns the default Value on error.
func ParseDefault(level string, def Value) Value {
	v, err := Parse(level)
	if err != nil {
		return def
	}
	return v
}

// ErrNotAllowed sets the error to return from Log when it squelches a log
// event disallowed by the configured Allow[Level] option. By default,
// ErrNotAllowed is nil; in this case the log event is squelched with no
// error.
func ErrNotAllowed(err error) Option {
	return func(l *logger) { l.errNotAllowed = err }
}

// SquelchNoLevel instructs Log to squelch log events with no level, so that
// they don't proceed through to the wrapped logger. If SquelchNoLevel is set
// to true and a log event is squelched in this way, the error value
// configured with ErrNoLevel is returned to the caller.
func SquelchNoLevel(squelch bool) Option {
	return func(l *logger) { l.squelchNoLevel = squelch }
}

// ErrNoLevel sets the error to return from Log when it squelches a log event
// with no level. By default, ErrNoLevel is nil; in this case the log event is
// squelched with no error.
func ErrNoLevel(err error) Option {
	return func(l *logger) { l.errNoLevel = err }
}

// NewInjector wraps next and returns a logger that adds a Key/level pair to
// the beginning of log events that don't already contain a level. In effect,
// this gives a default level to logs without a level.
func NewInjector(next log.Logger, level Value) log.Logger {
	return &injector{
		next:  next,
		level: level,
	}
}

type injector struct {
	next  log.Logger
	level interface{}
}

func (l *injector) Log(keyvals ...interface{}) error {
	for i := 1; i < len(keyvals); i += 2 {
		if _, ok := keyvals[i].(*levelValue); ok {
			return l.next.Log(keyvals...)
		}
	}
	kvs := make([]interface{}, len(keyvals)+2)
	kvs[0], kvs[1] = key, l.level
	copy(kvs[2:], keyvals)
	return l.next.Log(kvs...)
}

// Value is the interface that each of the canonical level values implement.
// It contains unexported methods that prevent types from other packages from
// implementing it and guaranteeing that NewFilter can distinguish the levels
// defined in this package from all other values.
type Value interface {
	String() string
	levelVal()
}

// Key returns the unique key added to log events by the loggers in this
// package.
func Key() interface{} { return key }

// ErrorValue returns the unique value added to log events by Error.
func ErrorValue() Value { return errorValue }

// WarnValue returns the unique value added to log events by Warn.
func WarnValue() Value { return warnValue }

// InfoValue returns the unique value added to log events by Info.
func InfoValue() Value { return infoValue }

// DebugValue returns the unique value added to log events by Debug.
func DebugValue() Value { return debugValue }

var (
	// key is of type interface{} so that it allocates once during package
	// initialization and avoids allocating every time the value is added to a
	// []interface{} later.
	key interface{} = "level"

	errorValue = &levelValue{level: levelError, name: "error"}
	warnValue  = &levelValue{level: levelWarn, name: "warn"}
	infoValue  = &levelValue{level: levelInfo, name: "info"}
	debugValue = &levelValue{level: levelDebug, name: "debug"}
)

type level byte

const (
	levelDebug level = 1 << iota
	levelInfo
	levelWarn
	levelError
)

type levelValue struct {
	name string
	level
}

func (v *levelValue) String() string { return v.name }
func (v *levelValue) levelVal()      {}
