package appenders

import (
	"fmt"
	"github.com/ian-kent/go-log/layout"
	"github.com/ian-kent/go-log/levels"
	"os"
	"strconv"
	"strings"
	"sync"
)

type rollingFileAppender struct {
	Appender
	layout         layout.Layout
	MaxFileSize    int64
	MaxBackupIndex int

	filename   string
	file       *os.File
	append     bool
	writeMutex sync.Mutex

	bytesWritten int64
}

func RollingFile(filename string, append bool) *rollingFileAppender {
	a := &rollingFileAppender{
		layout:         layout.Default(),
		MaxFileSize:    104857600,
		MaxBackupIndex: 1,
		append:         append,
		bytesWritten:   0,
	}
	err := a.SetFilename(filename)
	if err != nil {
		fmt.Printf("Error opening file: %s\n", err)
		return nil
	}
	return a
}

func (a *rollingFileAppender) Close() {
	if a.file != nil {
		a.file.Close()
		a.file = nil
	}
}

func (a *rollingFileAppender) Write(level levels.LogLevel, message string, args ...interface{}) {
	m := a.Layout().Format(level, message, args...)
	if !strings.HasSuffix(m, "\n") {
		m += "\n"
	}

	a.writeMutex.Lock()
	a.file.Write([]byte(m))

	a.bytesWritten += int64(len(m))
	if a.bytesWritten >= a.MaxFileSize {
		a.bytesWritten = 0
		a.rotateFile()
	}

	a.writeMutex.Unlock()
}

func (a *rollingFileAppender) Layout() layout.Layout {
	return a.layout
}

func (a *rollingFileAppender) SetLayout(layout layout.Layout) {
	a.layout = layout
}

func (a *rollingFileAppender) Filename() string {
	return a.filename
}

func (a *rollingFileAppender) SetFilename(filename string) error {
	if a.filename != filename || a.file == nil {
		a.closeFile()
		a.filename = filename
		err := a.openFile()
		return err
	}
	return nil
}

func (a *rollingFileAppender) rotateFile() {
	a.closeFile()

	lastFile := a.filename + "." + strconv.Itoa(a.MaxBackupIndex)
	if _, err := os.Stat(lastFile); err == nil {
		os.Remove(lastFile)
	}

	for n := a.MaxBackupIndex; n > 0; n-- {
		f1 := a.filename + "." + strconv.Itoa(n)
		f2 := a.filename + "." + strconv.Itoa(n+1)
		os.Rename(f1, f2)
	}

	os.Rename(a.filename, a.filename+".1")

	a.openFile()
}
func (a *rollingFileAppender) closeFile() {
	if a.file != nil {
		a.file.Close()
		a.file = nil
	}
}
func (a *rollingFileAppender) openFile() error {
	mode := os.O_WRONLY | os.O_APPEND | os.O_CREATE
	if !a.append {
		mode = os.O_WRONLY | os.O_CREATE
	}
	f, err := os.OpenFile(a.filename, mode, 0666)
	a.file = f
	return err
}