package bufio

import (
	"io"
	"sync"

	"github.com/metacubex/sing/common"
	"github.com/metacubex/sing/common/buf"
)

type BufferedWriter struct {
	upstream io.Writer
	buffer   *buf.Buffer
	access   sync.Mutex
}

func NewBufferedWriter(upstream io.Writer, buffer *buf.Buffer) *BufferedWriter {
	return &BufferedWriter{
		upstream: upstream,
		buffer:   buffer,
	}
}

func (w *BufferedWriter) Write(p []byte) (n int, err error) {
	w.access.Lock()
	defer w.access.Unlock()
	if w.buffer == nil {
		return w.upstream.Write(p)
	}
	for {
		var writeN int
		writeN, err = w.buffer.Write(p[n:])
		n += writeN
		if n == len(p) {
			return
		}
		_, err = w.upstream.Write(w.buffer.Bytes())
		if err != nil {
			return
		}
		w.buffer.Reset()
	}
}

func (w *BufferedWriter) WriteByte(c byte) error {
	w.access.Lock()
	defer w.access.Unlock()
	if w.buffer == nil {
		return common.Error(w.upstream.Write([]byte{c}))
	}
	for {
		err := w.buffer.WriteByte(c)
		if err == nil {
			return nil
		}
		_, err = w.upstream.Write(w.buffer.Bytes())
		if err != nil {
			return err
		}
		w.buffer.Reset()
	}
}

func (w *BufferedWriter) Fallthrough() error {
	w.access.Lock()
	defer w.access.Unlock()
	if w.buffer == nil {
		return nil
	}
	if !w.buffer.IsEmpty() {
		_, err := w.upstream.Write(w.buffer.Bytes())
		if err != nil {
			return err
		}
	}
	w.buffer.Release()
	w.buffer = nil
	return nil
}

func (w *BufferedWriter) WriterReplaceable() bool {
	return w.buffer == nil
}
