package bufwriter

import (
	"bufio"
	"os"
	"sync"
)

// Writer is a mutex protected buffered writer
type Writer struct {
	file   *os.File
	writer *bufio.Writer
	mutex  *sync.Mutex
}

// New creates a new mutex protected buffered writer for a file
func New(file string) (*Writer, error) {
	output, err := os.Create(file)
	if err != nil {
		return nil, err
	}
	return &Writer{file: output, writer: bufio.NewWriter(output), mutex: &sync.Mutex{}}, nil
}

// Write writes a byte slice to the underlying file
//
// It also writes a newline if the last byte isn't a newline character.
func (w *Writer) Write(data []byte) error {
	if len(data) == 0 {
		return nil
	}
	w.mutex.Lock()
	defer w.mutex.Unlock()

	_, err := w.writer.Write(data)
	if err != nil {
		return err
	}
	if data[len(data)-1] != '\n' {
		_, err = w.writer.WriteRune('\n')
	}
	return err
}

// WriteString writes a string to the underlying file
//
// It also writes a newline if the last byte isn't a newline character.
func (w *Writer) WriteString(data string) error {
	if data == "" {
		return nil
	}
	w.mutex.Lock()
	defer w.mutex.Unlock()

	_, err := w.writer.WriteString(data)
	if err != nil {
		return err
	}
	if data[len(data)-1] != '\n' {
		_, err = w.writer.WriteRune('\n')
	}
	return err
}

// Close closes the underlying writer flushing everything to disk
func (w *Writer) Close() error {
	w.mutex.Lock()
	defer w.mutex.Unlock()

	w.writer.Flush()
	//nolint:errcheck // we don't care whether sync failed or succeeded.
	w.file.Sync()
	return w.file.Close()
}