// Package rotate provides an io.Writer that changes the file it writes to based
// on the current date. It always writes to a file inside of the directory it is
// given, and only switches files once it has finished writing a line and the
// date has changed. It is intended to be used with log.SetOutput().
package rotate

import "os"
import "io"
import "time"
import "errors"
import "path/filepath"

type logger struct {
	directory   string
	file        io.WriteCloser
	previousDay int
	closed      bool
}

// New returns a new logger that will always log to directory/YYYY-MM-DD.log.
// When its close method is called, it will close any open file and stop
// accepting calls to Write. A closed logger may not be re-opened.
func New (directory string) (io.WriteCloser, error) {
	logger := &logger { directory: directory }
	err := logger.updateCurrentFile()
	if err != nil { return nil, err }
	return logger, nil
}

func (logger *logger) Write (buffer []byte) (n int, err error) {
	if logger.closed {
		return 0, errors.New("rotate: attempt to write to closed logger")
	}
	
	if logger.file == nil {
		err = logger. updateCurrentFile()
		if err != nil { return }
	}

	for buffer != nil {
		var outgoing  []byte
		var justWrote int
		outgoing, buffer = splitLine(buffer)
		justWrote, err = logger.file.Write(outgoing)
		n += justWrote
		if err != nil { return }

		err = logger.updateCurrentFile()
		if err != nil { return }
	}
	
	return
}

func (logger *logger) Close () error {
	return logger.file.Close()
}

func (logger *logger) updateCurrentFile() (err error) {
	if logger.closed { return }

	// yes this is not a correct measure of days since 1970 but all we need
	// to know is "has the date changed?"
	now := time.Now()
	currentDay := (now.Year() - 1970) * 366 + now.YearDay()

	if currentDay > logger.previousDay || logger.file == nil {
		if logger.file != nil { logger.file.Close() }

		logger.file, err = os.OpenFile(
			filepath.Join(logger.directory, now.Format("2006-01-02.log")),
			os.O_WRONLY | os.O_APPEND | os.O_CREATE,
			0660)
		if err != nil { return }
	}

	logger.previousDay = currentDay
	return
}

func splitLine (buffer []byte) (left, right []byte) {
	index := indexOf(buffer, '\n')
	if index < 1 { return buffer, nil }
	index += 1
	return buffer[:index], buffer[index:]
}

func indexOf[T comparable] (haystack []T, needle T) int {
	for index, element := range haystack {
		if element == needle {
			return index
		}
	}
	return -1
}

type lineWriter struct {
	destination func (line []byte) (int, error)
}

func LineWriter (destination func (line []byte) (int, error)) io.Writer {
	return &lineWriter { destination: destination }
}

func (writer lineWriter) Write (buffer []byte) (n int, err error) {
	for buffer != nil {
		var outgoing  []byte
		var justWrote int
		outgoing, buffer = splitLine(buffer)
		justWrote, err = writer.destination(outgoing)
		n += justWrote
		if err != nil { return }
	}
	
	return
}
