package starlog

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"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
	out       io.Writer
	ShowLine  bool
	ShowLevel bool
	DoWrite   bool
	DoShow    bool
	switching bool
	//HandleFunc
	waiting      chan int
	outshow      io.Writer
	LogLevel     int
	HandleFunc   func([]Attr, string)
	StdFuncColor bool
}

var Std = New(nil)

func init() {
	Std.DoShow = true
}

func New(out io.Writer) *StarLogger {
	logger := new(StarLogger)
	logger.DoShow = false
	logger.ShowLevel = true
	logger.ShowLine = true
	logger.DoWrite = true
	logger.out = out
	logger.outshow = colorable.NewColorableStdout()
	logger.StdFuncColor = true
	return logger
}

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
	logger.mu.Lock()
	defer logger.mu.Unlock()
	if level < logger.LogLevel {
		return
	}
	_, fname, line, _ := runtime.Caller(3)
	fname = filepath.Base(fname)
	date := time.Now().Format("2006-01-02 15:04:05.000 Mon")
	if logger.ShowLine && logger.ShowLevel {
		logs = fmt.Sprintf("%s %s %s %s", date, fname+":"+strconv.Itoa(line), `[`+levels[level]+`]`, logstr)
	} else if logger.ShowLine && !logger.ShowLevel {
		logs = fmt.Sprintf("%s %s %s", date, fname+":"+strconv.Itoa(line), logstr)
	} else if !logger.ShowLine && logger.ShowLevel {
		logs = fmt.Sprintf("%s %s %s", date, `[`+levels[level]+`]`, logstr)
	} else {
		logs = fmt.Sprintf("%s %s", date, 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 {
		logger.write(logs)
	}
}

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

func Debug(str ...interface{}) {
	Std.Debug(str...)
}

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

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

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

func Debugln(str ...interface{}) {
	Std.Debugln(str...)
}

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

func Info(str ...interface{}) {
	Std.Info(str...)
}

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

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

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

func Infoln(str ...interface{}) {
	Std.Infoln(str...)
}

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

func Notice(str ...interface{}) {
	Std.Notice(str...)
}

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

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

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

func Noticeln(str ...interface{}) {
	Std.Noticeln(str...)
}

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

func Warning(str ...interface{}) {
	Std.Warning(str...)
}

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

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

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

func Warningln(str ...interface{}) {
	Std.Warningln(str...)
}

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

func Error(str ...interface{}) {
	Std.Error(str...)
}

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

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

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

func Errorln(str ...interface{}) {
	Std.Errorln(str...)
}

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

func Critical(str ...interface{}) {
	Std.Critical(str...)
}

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

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

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

func Criticalln(str ...interface{}) {
	Std.Criticalln(str...)
}

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

func Fatal(str ...interface{}) {
	Std.Fatal(str...)
}

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

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

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

func Fatalln(str ...interface{}) {
	Std.Fatalln(str...)
}

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

func Panic(str ...interface{}) {
	Std.Panic(str...)
}

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

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

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

func Panicln(str ...interface{}) {
	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.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.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.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
	Std.out, err = os.Create(path)
	Std.switching = false
	return err
}

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