package log

import (
	"fmt"
	"os"
	"time"

	"go_dreamfactory/lego/utils/pools"
)

func newSys(options *Options) (sys *Logger, err error) {
	hook := LogFileOut{
		Filename:   options.FileName,                               //日志文件路径
		MaxAge:     options.MaxAgeTime,                             //备份日志保存天数
		CupTime:    time.Duration(options.CupTimeTime) * time.Hour, //日志切割间隔时间
		Compress:   options.Compress,                               //是否压缩 disabled by default
		MaxBackups: options.MaxBackups,                             //最大备份数
		MaxSize:    options.MaxSize,                                //最大日志文件大小
		LocalTime:  true,                                           //使用本地时间
		UniqueLog:  options.UniqueLog,                              //是否唯一文件
	}
	if !options.IsDebug {
		if err = hook.openNew(); err != nil {
			return
		}
	}
	out := make(writeTree, 0, 2)
	out = append(out, AddSync(&hook))
	if options.IsDebug {
		out = append(out, Lock(os.Stdout))
	}
	sys = &Logger{
		config:     NewDefEncoderConfig(),
		formatter:  NewConsoleEncoder(),
		name:       options.Alias,
		out:        out,
		level:      options.Loglevel,
		addCaller:  options.ReportCaller,
		callerSkip: options.CallerSkip,
		addStack:   FatalLevel,
	}
	return
}

type Logger struct {
	config     *EncoderConfig //编码配置
	level      LevelEnabler   //日志输出级别
	formatter  Formatter      //日志格式化
	name       string         //日志标签
	out        IWrite         //日志输出
	addCaller  LevelEnabler   //是否打印堆栈信息
	addStack   LevelEnabler   //堆栈信息输出级别
	callerSkip int            //堆栈输出深度
}

func (this *Logger) Clone(name string, skip int) ILogger {
	_name := ""
	if name == "" {
		_name = this.name
	} else {
		if this.name == "" {
			_name = name
		} else {
			_name = fmt.Sprintf("%s:%s", this.name, name)
		}
	}
	return &Logger{
		config:     this.config,
		formatter:  this.formatter,
		name:       _name,
		out:        this.out,
		level:      this.level,
		addCaller:  this.addCaller,
		callerSkip: skip,
		addStack:   this.addStack,
	}
}
func (this *Logger) SetName(name string) {
	this.name = name
}
func (this *Logger) Enabled(lvl Loglevel) bool {
	return this.level.Enabled(lvl)
}
func (this *Logger) Debug(msg string, args ...Field) {
	if this.level.Enabled(DebugLevel) {
		this.logWithFields(DebugLevel, msg, args...)
	}
}
func (this *Logger) Info(msg string, args ...Field) {
	if this.level.Enabled(InfoLevel) {
		this.logWithFields(InfoLevel, msg, args...)
	}
}
func (this *Logger) Print(msg string, args ...Field) {
	if this.level.Enabled(InfoLevel) {
		this.logWithFields(InfoLevel, msg, args...)
	}
}
func (this *Logger) Warn(msg string, args ...Field) {
	if this.level.Enabled(WarnLevel) {
		this.logWithFields(WarnLevel, msg, args...)
	}
}
func (this *Logger) Error(msg string, args ...Field) {
	if this.level.Enabled(ErrorLevel) {
		this.logWithFields(ErrorLevel, msg, args...)
	}
}
func (this *Logger) Panic(msg string, args ...Field) {
	if this.level.Enabled(PanicLevel) {
		this.logWithFields(PanicLevel, msg, args...)
	}
}
func (this *Logger) Fatal(msg string, args ...Field) {
	if this.level.Enabled(FatalLevel) {
		this.logWithFields(ErrorLevel, msg, args...)
	}
	os.Exit(1)
}

func (this *Logger) Debugf(format string, args ...interface{}) {
	this.Logf(DebugLevel, format, args...)
}
func (this *Logger) Infof(format string, args ...interface{}) {
	this.Logf(InfoLevel, format, args...)
}
func (this *Logger) Printf(format string, args ...interface{}) {
	this.Logf(InfoLevel, format, args...)
}
func (this *Logger) Warnf(format string, args ...interface{}) {
	this.Logf(WarnLevel, format, args...)
}
func (this *Logger) Errorf(format string, args ...interface{}) {
	this.Logf(ErrorLevel, format, args...)
}
func (this *Logger) Fatalf(format string, args ...interface{}) {
	this.Logf(FatalLevel, format, args...)
	os.Exit(1)
}
func (this *Logger) Panicf(format string, args ...interface{}) {
	this.Logf(PanicLevel, format, args...)
}
func (this *Logger) Logf(level Loglevel, format string, args ...interface{}) {
	if this.level.Enabled(level) {
		this.log(level, fmt.Sprintf(format, args...))
	}
}
func (this *Logger) Debugln(args ...interface{}) {
	this.Logln(DebugLevel, args...)
}
func (this *Logger) Infoln(args ...interface{}) {
	this.Logln(InfoLevel, args...)
}
func (this *Logger) Println(args ...interface{}) {
	this.Logln(InfoLevel, args...)
}
func (this *Logger) Warnln(args ...interface{}) {
	this.Logln(WarnLevel, args...)
}
func (this *Logger) Errorln(args ...interface{}) {
	this.Logln(ErrorLevel, args...)
}
func (this *Logger) Fatalln(args ...interface{}) {
	this.Logln(FatalLevel, args...)
	os.Exit(1)
}
func (this *Logger) Panicln(args ...interface{}) {
	this.Logln(PanicLevel, args...)
}
func (this *Logger) Logln(level Loglevel, args ...interface{}) {
	if this.level.Enabled(level) {
		this.log(level, this.sprintlnn(args...))
	}
}

func (this *Logger) logWithFields(level Loglevel, msg string, args ...Field) {
	entry := this.checkWithFields(level, msg, args...)
	this.write(entry)
	if level <= PanicLevel {
		panic(entry)
	}
	putEntry(entry)
}

func (this *Logger) log(level Loglevel, msg string) {
	entry := this.check(level, msg)
	this.write(entry)
	if level <= PanicLevel {
		panic(entry)
	}
	putEntry(entry)
}

func (this *Logger) checkWithFields(level Loglevel, msg string, args ...Field) (entry *Entry) {
	e := this.check(level, msg)
	e.WithFields(args...)
	return e
}

func (this *Logger) check(level Loglevel, msg string) (entry *Entry) {
	entry = getEntry()
	entry.Name = this.name
	entry.Time = time.Now()
	entry.Level = level
	entry.Message = msg
	// entry.WithFields(args)
	addStack := this.addStack.Enabled(level)
	addCaller := this.addCaller.Enabled(level)
	if !addCaller && !addStack {
		return
	}
	stackDepth := stacktraceFirst
	if addStack {
		stackDepth = stacktraceFull
	}
	stack := captureStacktrace(this.callerSkip+callerSkipOffset, stackDepth)
	defer stack.Free()
	if stack.Count() == 0 {
		if addCaller {
			if entry.Err != "" {
				entry.Err = entry.Err + ",error: failed to get caller"
			} else {
				entry.Err = "error:failed to get caller"
			}
		}
		return
	}
	frame, more := stack.Next()
	if addCaller {
		entry.Caller.Defined = frame.PC != 0
		entry.Caller.PC = frame.PC
		entry.Caller.File = frame.File
		entry.Caller.Line = frame.Line
		entry.Caller.Function = frame.Function
		entry.Caller.Stack = ""
	}
	if addStack {
		buffer := pools.BufferPoolGet()
		defer buffer.Free()
		stackfmt := newStackFormatter(buffer)
		stackfmt.FormatFrame(frame)
		if more {
			stackfmt.FormatStack(stack)
		}
		entry.Caller.Stack = buffer.String()
	}
	return
}

func (this *Logger) write(entry *Entry) {
	buf, err := this.formatter.Format(this.config, entry)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
		return
	}
	err = this.out.WriteTo(buf.Bytes())
	buf.Free()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to obtain write, %v\n", err)
		return
	}
	if entry.Level < ErrorLevel {
		this.out.Sync()
	}
	return
}

func (this *Logger) sprintlnn(args ...interface{}) string {
	msg := fmt.Sprintln(args...)
	return msg[:len(msg)-1]
}
