// Copyright (C) 2017 Michael J. Fromberger. All Rights Reserved.

package channel

import (
	"bufio"
	"bytes"
	"errors"
	"io"
)

// Line is a framing discipline for messages terminated by a Unicode LF
// (10). This framing has the constraint that records may not contain LF.
var Line = Split('\n')

// Split returns a framing in which each message is terminated by the specified
// byte value. The framing has the constraint that outbound records may not
// contain the split byte internally.
func Split(b byte) Framing {
	return func(r io.Reader, wc io.WriteCloser) Channel {
		return split{split: b, wc: wc, buf: bufio.NewReader(r)}
	}
}

// split implements Channel in which messages are terminated by occurrences of
// the specified byte. Outbound messages may not contain the split byte.
type split struct {
	split byte
	wc    io.WriteCloser
	buf   *bufio.Reader
}

// Send implements part of the Channel interface.  It reports an error if msg
// contains a split byte.
func (c split) Send(msg []byte) error {
	if bytes.IndexByte(msg, c.split) >= 0 {
		return errors.New("message contains split byte")
	}
	out := append(msg, c.split)
	_, err := c.wc.Write(out)
	return err
}

// Recv implements part of the Channel interface.
func (c split) Recv() ([]byte, error) {
	var buf bytes.Buffer
	for {
		chunk, err := c.buf.ReadSlice(c.split)
		buf.Write(chunk)
		if err == bufio.ErrBufferFull {
			continue // incomplete line
		}
		line := buf.Bytes()
		if n := len(line) - 1; n >= 0 {
			return line[:n], err
		}
		return nil, err
	}
}

// Close implements part of the Channel interface.
func (c split) Close() error { return c.wc.Close() }
