package starlog

import (
	"errors"
	"fmt"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/mattn/go-colorable"
)

const (
	LvDebug = iota
	LvInfo
	LvNotice
	LvWarning
	LvError
	LvCritical
	LvPanic
	LvFatal
)

var (
	levels = map[int]string{
		LvDebug:    "DEBUG",
		LvInfo:     "INFO",
		LvNotice:   "NOTICE",
		LvWarning:  "WARNING",
		LvError:    "ERROR",
		LvCritical: "CRITICAL",
		LvPanic:    "PANIC",
		LvFatal:    "FATAL",
	}
	Colors = map[int][]Attr{
		LvDebug:    []Attr{FgWhite},
		LvInfo:     []Attr{FgGreen},
		LvNotice:   []Attr{FgBlue},
		LvWarning:  []Attr{FgYellow},
		LvError:    []Attr{FgMagenta},
		LvCritical: []Attr{FgRed, Bold},
		LvPanic:    []Attr{FgRed, Bold},
		LvFatal:    []Attr{FgRed},
	}
)

type StarLogger struct {
	mu         *sync.Mutex
	lock       *sync.Mutex
	Flag       string
	out        io.Writer
	ShowLine   bool
	ShowLevel  bool
	ShowFunc   bool
	ShowThread bool
	DoWrite    bool
	DoShow     bool
	switching  bool
	isStd      bool
	//HandleFunc
	waiting      chan int
	outshow      io.Writer
	LogLevel     int
	HandleFunc   func([]Attr, string)
	StdFuncColor bool
	logbuf       string
	BufSize      int
}

var Std = New(nil)
var StdFile *os.File

func init() {
	Std.DoShow = true
	Std.Flag = "MAN"
	Std.isStd = true
}
func Close() error {
	if StdFile == nil {
		return errors.New("File not Open")
	}
	if err := Std.Flush(); err != nil {
		return err
	}
	return StdFile.Close()
}

func New(out io.Writer) *StarLogger {
	rand.Seed(time.Now().UnixNano())
	logger := new(StarLogger)
	logger.DoShow = false
	logger.mu = new(sync.Mutex)
	logger.lock = new(sync.Mutex)
	logger.ShowLevel = true
	logger.ShowLine = true
	logger.DoWrite = true
	logger.ShowThread = true
	logger.ShowFunc = false
	logger.out = out
	logger.outshow = colorable.NewColorableStdout()
	logger.StdFuncColor = true
	logger.BufSize = 0
	logger.Flag = "MAN"
	return logger
}

func (logger *StarLogger) Flush() error {
	logger.mu.Lock()
	defer logger.mu.Unlock()
	logger.write(logger.logbuf)
	logger.logbuf = ""
	return nil
}

func NewFlag() *StarLogger {
	return Std.NewFlag()
}

func (logger *StarLogger) NewFlag() *StarLogger {
	logger2 := new(StarLogger)
	logger2.out = logger.out
	logger2.ShowThread = logger.ShowThread
	logger2.ShowFunc = logger.ShowFunc
	logger2.DoShow = logger.DoShow
	logger2.ShowLevel = logger.ShowLevel
	logger2.ShowLine = logger.ShowLine
	logger2.DoWrite = logger.DoWrite
	logger2.outshow = logger.outshow
	logger2.mu = logger.mu
	logger2.lock = logger.lock
	logger2.Flag = string([]byte{uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65), uint8(rand.Intn(26) + 65)})
	if logger2.Flag == "MAN" {
		logger2.Flag = "RYZ"
	}
	return logger2
}

func (logger *StarLogger) write(logstr string) {
	if (!logger.DoWrite) || (logger.out == nil) {
		return
	}
	i := 0
	for logger.switching {
		time.Sleep(time.Millisecond * 100)
		i++
		if i > 20 {
			return
		}
	}
	if &(logger.out) == nil {
		return
	}
	logger.out.Write([]byte(logstr))
}

func (logger *StarLogger) OutPut(logstr string) {
	logger.out.Write([]byte(logstr))
}

func (logger *StarLogger) output(level int, logstr string) {
	var logs string
	var skip int = 2
	logger.mu.Lock()
	defer logger.mu.Unlock()
	if level < logger.LogLevel {
		return
	}
	if logger.isStd {
		skip++
	}
	logger.isStd = false
	pc, fname, line, ok := runtime.Caller(skip)
	if !ok {
		return
	}
	funcname := runtime.FuncForPC(pc).Name()
	funcname = filepath.Ext(funcname)
	funcname = strings.TrimPrefix(funcname, ".")
	fname = filepath.Base(fname)
	date := time.Now().Format("2006-01-02 15:04:05.000 Mon")
	logs = date
	if logger.ShowLine {
		logs += " " + fname + ":" + strconv.Itoa(line)
	}
	if logger.ShowFunc {
		logs += " <" + funcname + ">"
	}
	if logger.ShowThread {
		logs += " |" + logger.Flag + "|"
	}
	if logger.ShowLevel {
		logs += " " + `[` + levels[level] + `]`
	}
	logs += " " + logstr
	if logger.DoShow {
		logcolor := NewColor(Colors[level]...)
		logcolor.Fprint(logger.outshow, logs)
	}
	if logger.HandleFunc != nil {
		go logger.HandleFunc(Colors[level], logs)
	}
	if logger.DoWrite {
		if logger.BufSize == 0 {
			logger.write(logs)
		} else {
			logger.logbuf += logs
			if len(logger.logbuf) >= logger.BufSize {
				logger.write(logs)
			}
		}
	}
}

func (logger *StarLogger) Debug(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvDebug, strs)
}

func Debug(str ...interface{}) {
	Std.isStd = true
	Std.Debug(str...)
}

func (logger *StarLogger) Debugf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvDebug, strs)
}

func Debugf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Debugf(format, str...)
}

func (logger *StarLogger) Debugln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvDebug, strs)
}

func Debugln(str ...interface{}) {
	Std.isStd = true
	Std.Debugln(str...)
}

func (logger *StarLogger) Info(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvInfo, strs)
}

func Info(str ...interface{}) {
	Std.isStd = true
	Std.Info(str...)
}

func (logger *StarLogger) Infof(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvInfo, strs)
}

func Infof(format string, str ...interface{}) {
	Std.isStd = true
	Std.Infof(format, str...)
}

func (logger *StarLogger) Infoln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvInfo, strs)
}

func Infoln(str ...interface{}) {
	Std.isStd = true
	Std.Infoln(str...)
}

func (logger *StarLogger) Notice(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvNotice, strs)
}

func Notice(str ...interface{}) {
	Std.isStd = true
	Std.Notice(str...)
}

func (logger *StarLogger) Noticef(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvNotice, strs)
}

func Noticef(format string, str ...interface{}) {
	Std.isStd = true
	Std.Noticef(format, str...)
}

func (logger *StarLogger) Noticeln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvNotice, strs)
}

func Noticeln(str ...interface{}) {
	Std.isStd = true
	Std.Noticeln(str...)
}

func (logger *StarLogger) Warning(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvWarning, strs)
}

func Warning(str ...interface{}) {
	Std.isStd = true
	Std.Warning(str...)
}

func (logger *StarLogger) Warningf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvWarning, strs)
}

func Warningf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Warningf(format, str...)
}

func (logger *StarLogger) Warningln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvWarning, strs)
}

func Warningln(str ...interface{}) {
	Std.isStd = true
	Std.Warningln(str...)
}

func (logger *StarLogger) Error(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvError, strs)
}

func Error(str ...interface{}) {
	Std.isStd = true
	Std.Error(str...)
}

func (logger *StarLogger) Errorf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvError, strs)
}

func Errorf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Errorf(format, str...)
}

func (logger *StarLogger) Errorln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvError, strs)
}

func Errorln(str ...interface{}) {
	Std.isStd = true
	Std.Errorln(str...)
}

func (logger *StarLogger) Critical(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvCritical, strs)
}

func Critical(str ...interface{}) {
	Std.isStd = true
	Std.Critical(str...)
}

func (logger *StarLogger) Criticalf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvCritical, strs)
}

func Criticalf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Criticalf(format, str...)
}

func (logger *StarLogger) Criticalln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvCritical, strs)
}

func Criticalln(str ...interface{}) {
	Std.isStd = true
	Std.Criticalln(str...)
}

func (logger *StarLogger) Fatal(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvFatal, strs)
	os.Exit(9)
}

func Fatal(str ...interface{}) {
	Std.isStd = true
	Std.Fatal(str...)
}

func (logger *StarLogger) Fatalf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvFatal, strs)
	os.Exit(9)
}

func Fatalf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Fatalf(format, str...)
}

func (logger *StarLogger) Fatalln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvFatal, strs)
	os.Exit(9)
}

func Fatalln(str ...interface{}) {
	Std.isStd = true
	Std.Fatalln(str...)
}

func (logger *StarLogger) Panic(str ...interface{}) {
	strs := fmt.Sprint(str...)
	logger.output(LvPanic, strs)
	panic(str)
}

func Panic(str ...interface{}) {
	Std.isStd = true
	Std.Panic(str...)
}

func (logger *StarLogger) Panicf(format string, str ...interface{}) {
	strs := fmt.Sprintf(format, str...)
	logger.output(LvPanic, strs)
	panic(fmt.Sprintf(format, str...))
}

func Panicf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Panicf(format, str...)
}

func (logger *StarLogger) Panicln(str ...interface{}) {
	strs := fmt.Sprintln(str...)
	logger.output(LvPanic, strs)
	panic(fmt.Sprintln(str...))
}

func Panicln(str ...interface{}) {
	Std.isStd = true
	Std.Panicln(str...)
}

func (logger *StarLogger) Print(str ...interface{}) {
	logger.lock.Lock()
	defer logger.lock.Unlock()
	strs := fmt.Sprint(str...)
	logger.OutPut(strs)
}

func Print(str ...interface{}) {
	Std.isStd = true
	Std.Print(str...)
}

func (logger *StarLogger) Printf(format string, str ...interface{}) {
	logger.lock.Lock()
	defer logger.lock.Unlock()
	strs := fmt.Sprintf(format, str...)
	logger.OutPut(strs)
}

func Printf(format string, str ...interface{}) {
	Std.isStd = true
	Std.Printf(format, str...)
}

func (logger *StarLogger) Println(str ...interface{}) {
	logger.lock.Lock()
	defer logger.lock.Unlock()
	strs := fmt.Sprintln(str...)
	logger.OutPut(strs)
}

func Println(str ...interface{}) {
	Std.isStd = true
	Std.Println(str...)
}

func StdPrint(c []Attr, str ...interface{}) {
	Std.lock.Lock()
	defer Std.lock.Unlock()
	colorstr := NewColor(c...)
	colorstr.Print(str...)
	if Std.StdFuncColor && Std.HandleFunc != nil {
		go Std.HandleFunc(c, fmt.Sprint(str...))
	}
}

func StdPrintf(c []Attr, format string, str ...interface{}) {
	Std.lock.Lock()
	defer Std.lock.Unlock()
	colorstr := NewColor(c...)
	colorstr.Printf(format, str...)
	if Std.StdFuncColor && Std.HandleFunc != nil {
		go Std.HandleFunc(c, fmt.Sprintf(format, str...))
	}
}

func StdPrintln(c []Attr, str ...interface{}) {
	Std.lock.Lock()
	defer Std.lock.Unlock()
	colorstr := NewColor(c...)
	colorstr.Println(str...)
	if Std.StdFuncColor && Std.HandleFunc != nil {
		go Std.HandleFunc(c, fmt.Sprintln(str...))
	}
}

func (logger *StarLogger) SwitchOut(out io.Writer) {
	logger.lock.Lock()
	defer logger.lock.Unlock()
	logger.switching = true
	logger.out = out
	logger.switching = false
}

func SetLogFile(path string) error {
	var err error
	StdFile, err = os.Create(path)
	Std.out = StdFile
	Std.switching = false
	return err
}

func SwitchFile(path string) error {
	Std.switching = true
	return SetLogFile(path)
}
