package cbor

import (
	"fmt"
	"io"
)

var (
	_ quickWriter = &quickWriterStream{}
)

// quickWriter is implements several methods that are specificly useful to the performance
// needs of our encoders, and abstracts writing to a byte array or to an io.Writer.
type quickWriter interface {
	writeb([]byte)
	writestr(string)
	writen1(byte)
	writen2(byte, byte)
	checkErr() error
	clearErr()
}

// Interface used to detect if efficient string writing is supported.
// Same as in stdlib 'io' pkg; also not exported there, so we declare it again ourselves.
type stringWriter interface {
	WriteString(s string) (n int, err error)
}

// quickWriterStream is a quickWriter that routes bytes to an io.Writer.
// While this implementation does use some internal buffers, it's still advisable
// to use a buffered writer to avoid small operations for any external IO like disk or network.
type quickWriterStream struct {
	w  io.Writer
	ws stringWriter // nil if not available

	scratch  [2]byte
	scratch1 []byte
	scratch2 []byte
	err      error
}

func newQuickWriterStream(w io.Writer) *quickWriterStream {
	z := &quickWriterStream{w: w}
	if ws, ok := w.(stringWriter); ok {
		z.ws = ws
	}
	z.scratch1 = z.scratch[:1]
	z.scratch2 = z.scratch[:2]
	return z
}

func (z *quickWriterStream) writeb(bs []byte) {
	n, err := z.w.Write(bs)
	if err != nil && z.err == nil {
		z.err = err
	}
	if n < len(bs) && z.err == nil {
		z.err = fmt.Errorf("underwrite")
	}
}

func (z *quickWriterStream) writestr(s string) {
	var n int
	var err error
	if z.ws != nil {
		n, err = z.ws.WriteString(s)
	} else {
		n, err = z.w.Write([]byte(s)) // Notice: alloc!
	}
	if err != nil && z.err == nil {
		z.err = err
	}
	if n < len(s) && z.err == nil {
		z.err = fmt.Errorf("underwrite")
	}
}

func (z *quickWriterStream) writen1(b byte) {
	z.scratch1[0] = b
	n, err := z.w.Write(z.scratch1)
	if err != nil && z.err == nil {
		z.err = err
	}
	if n < 1 && z.err == nil {
		z.err = fmt.Errorf("underwrite")
	}
}

func (z *quickWriterStream) writen2(b1 byte, b2 byte) {
	z.scratch2[0] = b1
	z.scratch2[1] = b2
	n, err := z.w.Write(z.scratch2)
	if err != nil && z.err == nil {
		z.err = err
	}
	if n < 2 && z.err == nil {
		z.err = fmt.Errorf("underwrite")
	}
}

func (z *quickWriterStream) checkErr() error {
	return z.err
}

func (z *quickWriterStream) clearErr() {
	z.err = nil
}