package cli

import (
	"bytes"
	"encoding/binary"
	"git.nspix.com/golang/micro/helper/unsafestr"
	"io"
)

var (
	feature = []byte{67, 76, 73}
)

const (
	PacketTypeCompleter byte = 0x01
	PacketTypeData           = 0x02
	PacketTypeEcho           = 0x03
)

type Frame struct {
	Feature   []byte
	Type      byte   `json:"type"`
	Data      []byte `json:"data"`
	Error     string `json:"error"`
	Timestamp int64  `json:"timestamp"`
}

func readFrame(r io.Reader) (frame *Frame, err error) {
	var (
		n           int
		dataLength  int16
		errorLength int16
		errBuf      []byte
	)
	frame = &Frame{Feature: make([]byte, 3)}
	if _, err = io.ReadFull(r, frame.Feature); err != nil {
		return
	}
	if !bytes.Equal(frame.Feature, feature) {
		err = io.ErrUnexpectedEOF
		return
	}
	if err = binary.Read(r, binary.LittleEndian, &frame.Type); err != nil {
		return
	}
	if err = binary.Read(r, binary.LittleEndian, &frame.Timestamp); err != nil {
		return
	}
	if err = binary.Read(r, binary.LittleEndian, &dataLength); err != nil {
		return
	}
	if err = binary.Read(r, binary.LittleEndian, &errorLength); err != nil {
		return
	}
	if dataLength > 0 {
		frame.Data = make([]byte, dataLength)
		if n, err = io.ReadFull(r, frame.Data); err == nil {
			if n < int(dataLength) {
				err = io.ErrShortBuffer
			}
		}
	}
	if errorLength > 0 {
		errBuf = make([]byte, errorLength)
		if n, err = io.ReadFull(r, errBuf); err == nil {
			if n < int(dataLength) {
				err = io.ErrShortBuffer
			} else {
				frame.Error = unsafestr.BytesToString(errBuf)
			}
		}
	}
	return
}

func writeFrame(w io.Writer, frame *Frame) (err error) {
	var (
		n           int
		dataLength  int16
		errorLength int16
		errBuf      []byte
	)
	if _, err = w.Write(feature); err != nil {
		return
	}
	if frame.Data != nil {
		dataLength = int16(len(frame.Data))
	}
	if frame.Error != "" {
		errBuf = unsafestr.StringToBytes(frame.Error)
		errorLength = int16(len(errBuf))
	}
	if err = binary.Write(w, binary.LittleEndian, frame.Type); err != nil {
		return
	}
	if err = binary.Write(w, binary.LittleEndian, frame.Timestamp); err != nil {
		return
	}
	if err = binary.Write(w, binary.LittleEndian, dataLength); err != nil {
		return
	}
	if err = binary.Write(w, binary.LittleEndian, errorLength); err != nil {
		return
	}
	if dataLength > 0 {
		if n, err = w.Write(frame.Data); err == nil {
			if n < int(dataLength) {
				err = io.ErrShortWrite
			}
		}
	}
	if errorLength > 0 {
		if n, err = w.Write(errBuf); err == nil {
			if n < int(errorLength) {
				err = io.ErrShortWrite
			}
		}
	}
	return
}
