package log

import (
	"fmt"
	"io"
	"log"
	"os"
)

const (
	LevelNone uint8 = iota
	LevelError
	LevelWarning
	LevelInfo
	LevelDebug
)

const (
	Flag = log.LstdFlags | log.Llongfile

	callDepth = 2
)

var (
	console      bool
	defaultLevel uint8

	debug   = log.New(os.Stdout, "[D] ", Flag)
	info    = log.New(os.Stdout, "[I] ", Flag)
	warning = log.New(os.Stdout, "[W] ", Flag)
	errorLg = log.New(os.Stdout, "[E] ", Flag)
	closer  io.Closer
)

func SetLevel(level uint8) {
	defaultLevel = level
}

func SetOutput(w io.WriteCloser) {
	lw := &loggerWrite{
		level:   defaultLevel,
		console: console,
		w:       w,
	}

	closer = lw
	debug.SetOutput(lw)
	info.SetOutput(lw)
	warning.SetOutput(lw)
	errorLg.SetOutput(lw)
}

func SetConsole(r bool) {
	console = r
}

func Close() error {
	if closer == nil {
		return nil
	}
	return closer.Close()
}

func Debug(f string, v ...interface{}) {
	if defaultLevel < LevelDebug {
		return
	}
	_ = debug.Output(callDepth, fmt.Sprintf(f, v...))
}

func Info(f string, v ...interface{}) {
	if defaultLevel < LevelInfo {
		return
	}
	_ = info.Output(callDepth, fmt.Sprintf(f, v...))
}

func Warning(f string, v ...interface{}) {
	if defaultLevel < LevelWarning {
		return
	}
	_ = warning.Output(callDepth, fmt.Sprintf(f, v...))
}

func Error(f string, v ...interface{}) {
	if defaultLevel < LevelError {
		return
	}
	_ = errorLg.Output(callDepth, fmt.Sprintf(f, v...))
}

func Panic(f string, v ...interface{}) {
	s := fmt.Sprintf(f, v...)
	_ = errorLg.Output(callDepth, s)
	_ = Close()
	panic(s)
}

func Fatal(f string, v ...interface{}) {
	_ = errorLg.Output(callDepth, fmt.Sprintf(f, v...))
	_ = Close()
	os.Exit(1)
}

type loggerWrite struct {
	level   uint8
	console bool
	closed  bool
	w       io.WriteCloser
}

func (l *loggerWrite) Write(p []byte) (n int, err error) {
	if l.closed {
		return 0, nil
	}
	if l.level == LevelWarning || l.level == LevelError || l.console {
		_, _ = os.Stdout.Write(p)
	}
	return l.w.Write(p)
}

func (l *loggerWrite) Close() error {
	if l.closed {
		return nil
	}
	return l.w.Close()
}

func init() {
	defaultLevel = LevelDebug
}
