package log

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

func NewFileWriter(tag, path string) io.WriteCloser {
	return &file{
		Tag:  tag,
		Path: path,
	}
}

func NewLogger(dept int, w ...io.Writer) *Log {
	return New("", dept+2, w...)
}

func New(prefix string, dept int, w ...io.Writer) *Log {
	if len(w) == 0 {
		return Discard()
	}
	if prefix != "" {
		prefix = buildPrefix(prefix) + " "
	}
	return NewLog(w, prefix, dept, 0)
}

func Console() *Log {
	return ConsoleWith("", 0)
}

func ConsoleWith(prefix string, dept int) *Log {
	return NewLog([]io.Writer{os.Stdout}, prefix, dept+2, 0)
}

func Discard() *Log {
	return NewLog([]io.Writer{io.Discard}, "", 0, 0)
}

func Fork(l Logger, subPath, tag string) Logger {
	return rebuild(l, subPath, tag, true)
}

func Part(l Logger, subPath, tag string) Logger {
	return rebuild(l, subPath, tag, false)
}

func rebuild(l Logger, subPath, tag string, withMain bool) Logger {
	switch old := l.(type) {
	case *Log:
		pool := make([]io.Writer, 0, len(old.wPool))
		for _, w := range old.wPool {
			if f, o := w.(*file); o {
				pool = append(pool,
					NewFileWriter(tag, filepath.Join(f.Path, subPath)),
				)
				if withMain {
					pool = append(pool, f)
				}
			} else {
				pool = append(pool, w)
			}
		}
		return NewLog(pool, old.prefix, old.depth, old.buf)
	case MultiLogger:
		part := make(MultiLogger, len(old))
		for i, ol := range old {
			if lg, ok := ol.(*Log); ok {
				part[i] = rebuild(lg, subPath, tag, withMain)
			} else {
				part[i] = ol
			}
		}
		return part
	default:
		return l
	}
}

const (
	LevelError uint8 = iota
	LevelWarn
	LevelInfo
	LevelDebug
)

const (
	LevelsError = "[E]"
	LevelsWarn  = "[W]"
	LevelsInfo  = "[I]"
	LevelsDebug = "[D]"
)

const (
	PrintFlags = log.LstdFlags | log.Llongfile
)

const (
	dateLayout = "2006_01_02"
)

func buildPrefix(s string) string {
	return "[" + strings.ToUpper(s) + "]"
}

func spitPrefix(s string) string {
	idx := strings.Index(s, " ")
	if idx == -1 {
		return s
	}
	s = strings.ToLower(s[:idx])
	s = strings.TrimPrefix(s, "[")
	s = strings.TrimSuffix(s, "]")
	return s
}

type file struct {
	Tag  string    // svc
	Path string    // /var/log
	date time.Time // 2006_01_02
	fi   *os.File
}

func (f *file) Write(b []byte) (n int, err error) {
	if err = f.check(); err != nil {
		return 0, err
	}
	return f.fi.Write(b)
}

func (f *file) Close() error {
	return f.fi.Close()
}

func (f *file) createDir() error {
	if _, err := os.Stat(f.Path); err != nil {
		if os.IsNotExist(err) {
			// 这里文件夹权限即使设置为 ModePerm, Linux 系统权限也是 755
			if err = os.MkdirAll(f.Path, os.ModePerm); err != nil {
				return err
			}
		}
		return err
	}
	return nil
}

func (f *file) openFile(date string) (*os.File, error) {
	return os.OpenFile(f.name(date), os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.ModePerm) // 创建文件
}

func (f *file) name(date string) string {
	path := fmt.Sprintf("%s_%s%s", f.Tag, date, ".log")
	if f.Tag == "" {
		path = date + ".log"
	}
	// /var/log/svc_2006_01_02.log
	return filepath.Join(f.Path, path)
}

func (f *file) checkDate(cur time.Time) bool {
	curY, curM, curD := cur.Date()
	oldY, oldM, oldD := f.date.Date()
	if curY == oldY && curM == oldM && curD == oldD {
		return true
	}
	return false
}

func (f *file) check() error {
	if f.fi == nil {
		if err := f.createDir(); err != nil {
			return err
		}
	}
	cur := time.Now()
	if f.checkDate(cur) {
		return nil
	}
	if f.fi != nil {
		_ = f.fi.Close()
	}
	fi, err := f.openFile(cur.Format(dateLayout))
	if err != nil {
		return err
	}
	f.fi = fi
	f.date = cur
	return nil
}

type Log struct {
	depth  int // 2
	prefix string
	buf    int
	wPool  []io.Writer
	logs   []*log.Logger

	mu sync.Mutex
}

func NewLog(writers []io.Writer, prefix string, depth int, buf int) *Log {
	if len(writers) == 0 {
		writers = []io.Writer{io.Discard}
	}
	if depth < 0 {
		depth = 2
	}
	l := new(Log)
	l.prefix = prefix
	l.depth = depth
	l.wPool = writers
	l.buf = buf
	l.logs = make([]*log.Logger, len(l.wPool))
	for i := 0; i < len(l.wPool); i++ {
		w := l.wPool[i]
		if buf > 0 {
			w = bufio.NewWriterSize(w, buf)
		}
		l.logs[i] = log.New(w, prefix, func() int {
			if depth <= 0 {
				return log.LstdFlags
			}
			return PrintFlags
		}())
	}
	return l
}
func (l *Log) CallDepthPlus() {
	l.depth++
}
func (l *Log) CallDepthMinus() {
	l.depth--
}
func (l *Log) Write(b []byte) (int, error) {
	l.mu.Lock()
	n, err := bytes.NewReader(b).WriteTo(io.MultiWriter(l.wPool...))
	l.mu.Unlock()
	return int(n), err
}

func (l *Log) Prefix(prefix string, f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, prefix)
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Println(f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, "")
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

// Logger start
func (l *Log) Error(f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsError)
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Warn(f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsWarn)
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Info(f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsInfo)
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

func (l *Log) Debug(f string, v ...any) {
	l.mu.Lock()
	for _, lg := range l.logs {
		l.setPrefixFmt(lg, LevelsDebug)
		_ = lg.Output(l.depth, fmt.Sprintf(f, v...))
	}
	l.mu.Unlock()
}

// Logger end

func (l *Log) setPrefixFmt(logger *log.Logger, s string) {
	prefix := s + " "
	if logger.Prefix() == prefix {
		return
	}
	logger.SetPrefix(prefix)
}
