package flume

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/ansel1/merry"
	"go.uber.org/zap/zapcore"
	"io"
	"strconv"
	"strings"
)

type (
	// Logger is the basic logging interface.  Construct instances of Logger with a Factory,
	// or with the package functions (which use a package level Factory).
	Logger interface {
		Debug(msg string, args ...interface{})
		Info(msg string, args ...interface{})
		Error(msg string, args ...interface{})

		IsDebug() bool
		IsInfo() bool

		// With creates a new Logger with some context already attached.  All
		// entries logged with the child logger will include this context.
		With(args ...interface{}) Logger
	}

	// Level is a log level
	Level zapcore.Level
)

const (
	// OffLevel disables all logs
	OffLevel = Level(127)
	// DebugLevel should be used for low-level, non-production logs.  Typically intended only for developers.
	DebugLevel = Level(zapcore.DebugLevel)
	// InfoLevel should be used for production level logs.  Typically intended for end-users and developers.
	InfoLevel = Level(zapcore.InfoLevel)
	// ErrorLevel should be used for errors.  Generally, this should be reserved for events which truly
	// need to be looked at by an admin, and might be reported to an error-tracking system.
	ErrorLevel = Level(zapcore.ErrorLevel)
)

var pkgFactory = NewFactory()

// New creates a new Logger
func New(name string) Logger {
	return pkgFactory.NewLogger(name)
}

// NewCore returns a new Core
func NewCore(name string, options ...CoreOption) *Core {
	return pkgFactory.NewCore(name, options...)
}

// ConfigString configures the package level Factory.  The
// string can either be a JSON-serialized Config object, or
// just a LevelsString (see Factory.LevelsString for format).
//
// Note: this will reconfigure the logging levels for all
// loggers.
func ConfigString(s string) error {
	if strings.HasPrefix(strings.TrimSpace(s), "{") {
		// it's json, treat it like a full config string
		cfg := Config{}
		err := json.Unmarshal([]byte(s), &cfg)
		if err != nil {
			return err
		}
		return Configure(cfg)
	}
	return pkgFactory.LevelsString(s)
}

// Configure configures the package level Factory from
// the settings in the Config object.  See Config for
// details.
//
// Note: this will reconfigure the logging levels for all
// loggers.
func Configure(cfg Config) error {
	return pkgFactory.Configure(cfg)
}

// SetOut sets the output writer for all logs produced by the default factory.
// Returns a function which sets the output writer back to the prior setting.
func SetOut(w io.Writer) func() {
	return pkgFactory.SetOut(w)
}

// SetDefaultLevel sets the default log level on the package-level Factory.
func SetDefaultLevel(l Level) {
	pkgFactory.SetDefaultLevel(l)
}

// SetLevel sets a log level for a named logger on the package-level Factory.
func SetLevel(name string, l Level) {
	pkgFactory.SetLevel(name, l)
}

// SetAddCaller enables/disables call site logging on the package-level Factory
func SetAddCaller(b bool) {
	pkgFactory.SetAddCaller(b)
}

// SetEncoder sets the encoder for the package-level Factory
func SetEncoder(e Encoder) {
	pkgFactory.SetEncoder(e)
}

// Hooks adds hooks to the package-level Factory.
func Hooks(hooks ...HookFunc) {
	pkgFactory.Hooks(hooks...)
}

// ClearHooks clears all hooks from the package-level Factory.
func ClearHooks() {
	pkgFactory.ClearHooks()
}

// SetDevelopmentDefaults sets useful default settings on the package-level Factory
// which are appropriate for a development setting.  Default log level is
// set to INF, all loggers are reset to the default level, call site information
// is logged, and the encoder is a colorized, multi-line friendly console
// encoder with a simplified time stamp format.
func SetDevelopmentDefaults() error {
	return Configure(Config{
		Development: true,
	})
}

// String implements stringer and a few other interfaces.
func (l Level) String() string {
	switch l {
	case DebugLevel:
		return "DBG"
	case InfoLevel:
		return "INF"
	case ErrorLevel:
		return "ERR"
	case OffLevel:
		return "OFF"
	default:
		return fmt.Sprintf("Level(%d)", l)
	}
}

// MarshalText implements encoding.TextMarshaler
func (l Level) MarshalText() ([]byte, error) {
	return []byte(l.String()), nil
}

// UnmarshalText implements encoding.TextUnmarshaler
func (l *Level) UnmarshalText(text []byte) error {
	if l == nil {
		return merry.New("can't unmarshal a nil *Level")
	}
	if !l.unmarshalText(text) {
		return fmt.Errorf("unrecognized level: %q", text)
	}
	return nil
}

func (l *Level) unmarshalText(text []byte) bool {
	text = bytes.ToLower(text)
	switch string(text) {
	case "debug", "dbg", "all":
		*l = DebugLevel
	case "info", "inf", "": // make the zero value useful
		*l = InfoLevel
	case "error", "err":
		*l = ErrorLevel
	case "off":
		*l = OffLevel
	default:
		if i, err := strconv.Atoi(string(text)); err != nil {
			if i >= -127 && i <= 127 {
				*l = Level(i)
			} else {
				return false
			}
		}
		return false
	}
	return true
}

// Set implements flags.Value
func (l *Level) Set(s string) error {
	return l.UnmarshalText([]byte(s))
}

// Get implements flag.Getter
func (l *Level) Get() interface{} {
	return *l
}
