package log

import (
	"fmt"
	"os"
	"strconv"
	"sync"
	"time"
)

type File struct {
	Filename    string `json:"filename"`
	MaxSize     int64  `json:"max_size"`
	MaxLogFiles int    `json:"max_log_files"`
	Format      string `json:"format"`
	Level       int    `json:"level"`
	locker      sync.RWMutex
	buf         []byte
	fp          *os.File
	prefix      string
	size        int64
}

func itoa(buf *[]byte, i int, wid int) {
	// Assemble decimal in reverse order.
	var b [20]byte
	bp := len(b) - 1
	for i >= 10 || wid > 1 {
		wid--
		q := i / 10
		b[bp] = byte('0' + i - q*10)
		bp--
		i = q
	}
	// i < 10
	b[bp] = byte('0' + i)
	*buf = append(*buf, b[bp:]...)
}

func (log *File) SetLevel(lv int) {
	log.Level = lv
}

func (log *File) Prefix(s string) {
	log.prefix = s
}

func (log *File) Print(i ...interface{}) {
	log.write(TraceLevel, fmt.Sprint(i...))
}

func (log *File) Printf(format string, args ...interface{}) {
	log.write(TraceLevel, fmt.Sprintf(format, args...))
}

func (log *File) Debug(i ...interface{}) {
	log.write(DebugLevel, fmt.Sprint(i...))
}

func (log *File) Debugf(format string, args ...interface{}) {
	log.write(DebugLevel, fmt.Sprintf(format, args...))
}

func (log *File) Info(i ...interface{}) {
	log.write(InfoLevel, fmt.Sprint(i...))
}

func (log *File) Infof(format string, args ...interface{}) {
	log.write(InfoLevel, fmt.Sprintf(format, args...))
}

func (log *File) Warn(i ...interface{}) {
	log.write(WarnLevel, fmt.Sprint(i...))
}

func (log *File) Warnf(format string, args ...interface{}) {
	log.write(WarnLevel, fmt.Sprintf(format, args...))
}

func (log *File) Error(i ...interface{}) {
	log.write(ErrorLevel, fmt.Sprint(i...))
}

func (log *File) Errorf(format string, args ...interface{}) {
	log.write(ErrorLevel, fmt.Sprintf(format, args...))
}

func (log *File) Fatal(i ...interface{}) {
	log.write(FatalLevel, fmt.Sprint(i...))
}

func (log *File) Fatalf(format string, args ...interface{}) {
	log.write(FatalLevel, fmt.Sprintf(format, args...))
}

func (log *File) Panic(i ...interface{}) {
	log.write(PanicLevel, fmt.Sprint(i...))
}

func (log *File) Panicf(format string, args ...interface{}) {
	log.write(PanicLevel, fmt.Sprintf(format, args...))
}

func (log *File) format(buf *[]byte, level int, s string) (err error) {
	t := time.Now()
	year, month, day := t.Date()
	itoa(buf, year, 4)
	*buf = append(*buf, '-')
	itoa(buf, int(month), 2)
	*buf = append(*buf, '-')
	itoa(buf, day, 2)
	*buf = append(*buf, ' ')
	hour, min, sec := t.Clock()
	itoa(buf, hour, 2)
	*buf = append(*buf, ':')
	itoa(buf, min, 2)
	*buf = append(*buf, ':')
	itoa(buf, sec, 2)
	*buf = append(*buf, ' ')
	*buf = append(*buf, '[')
	*buf = append(*buf, getLevelText(level)...)
	*buf = append(*buf, ']')
	*buf = append(*buf, ' ')
	*buf = append(*buf, s...)
	return
}

func (log *File) write(level int, s string) {
	var (
		n   int
		err error
	)
	if log.Level > level {
		return
	}
	log.locker.Lock()
	log.buf = log.buf[:0]
	if err = log.format(&log.buf, level, s); err != nil {
		log.locker.Unlock()
		return
	}
	log.buf = append(log.buf, '\n')
	if n, err = log.fp.Write(log.buf); err != nil {
		log.locker.Unlock()
		return
	}
	log.locker.Unlock()
	//write br
	log.size += int64(n)
	if log.MaxSize > 0 && log.size >= log.MaxSize {
		if err = log.rotate(); err != nil {
			return
		}
		log.size = 0
	}
}

func (log *File) isExists(filename string) bool {
	if _, err := os.Stat(filename); err == nil {
		return true
	} else {
		return false
	}
}

func (log *File) rotate() (err error) {
	log.locker.Lock()
	defer log.locker.Unlock()
	if err = log.close(); err != nil {
		return
	}
	for i := log.MaxLogFiles; i >= 0; i-- {
		filename := log.Filename
		if i > 0 {
			filename += "." + strconv.Itoa(i)
		}
		if i == log.MaxLogFiles {
			if log.isExists(filename) {
				if err = os.Remove(filename); err != nil {
					return
				}
			}
		} else {
			if log.isExists(filename) {
				if err = os.Rename(filename, log.Filename+"."+strconv.Itoa(i+1)); err != nil {
					return
				}
			}
		}
	}
	err = log.open()
	return
}

func (log *File) Reload() (err error) {
	log.locker.Lock()
	_ = log.close()
	err = log.open()
	log.locker.Unlock()
	return
}

func (log *File) open() (err error) {
	if log.fp, err = os.OpenFile(log.Filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600); err != nil {
		return
	}
	return
}

func (log *File) Open() (err error) {
	if err = log.open(); err != nil {
		return
	}
	if info, err := os.Stat(log.Filename); err == nil {
		log.size = info.Size()
	}
	return
}

func (log *File) close() (err error) {
	if log.fp != nil {
		err = log.fp.Close()
	}
	return
}

func (log *File) Close() (err error) {
	err = log.close()
	return
}

func NewFileLogger(filename string) *File {
	lg := &File{Filename: filename, buf: make([]byte, 1024)}
	return lg
}
