package speedtest

import (
	"encoding/binary"
	"io"
	"time"
)

const (
	typeDownload = 0x1
	typeUpload   = 0x2
)

// DownloadRequest format:
// 0x1 (byte)
// Request data length (uint32 BE)

func readDownloadRequest(r io.Reader) (uint32, error) {
	var l uint32
	err := binary.Read(r, binary.BigEndian, &l)
	return l, err
}

func writeDownloadRequest(w io.Writer, l uint32) error {
	buf := make([]byte, 5)
	buf[0] = typeDownload
	binary.BigEndian.PutUint32(buf[1:], l)
	_, err := w.Write(buf)
	return err
}

// DownloadResponse format:
// Status (byte, 0=ok, 1=error)
// Message length (uint16 BE)
// Message (bytes)

func readDownloadResponse(r io.Reader) (bool, string, error) {
	var status [1]byte
	if _, err := io.ReadFull(r, status[:]); err != nil {
		return false, "", err
	}
	var msgLen uint16
	if err := binary.Read(r, binary.BigEndian, &msgLen); err != nil {
		return false, "", err
	}
	// No message is fine
	if msgLen == 0 {
		return status[0] == 0, "", nil
	}
	msgBuf := make([]byte, msgLen)
	_, err := io.ReadFull(r, msgBuf)
	if err != nil {
		return false, "", err
	}
	return status[0] == 0, string(msgBuf), nil
}

func writeDownloadResponse(w io.Writer, ok bool, msg string) error {
	sz := 1 + 2 + len(msg)
	buf := make([]byte, sz)
	if ok {
		buf[0] = 0
	} else {
		buf[0] = 1
	}
	binary.BigEndian.PutUint16(buf[1:], uint16(len(msg)))
	copy(buf[3:], msg)
	_, err := w.Write(buf)
	return err
}

// UploadRequest format:
// 0x2 (byte)
// Upload data length (uint32 BE)

func readUploadRequest(r io.Reader) (uint32, error) {
	var l uint32
	err := binary.Read(r, binary.BigEndian, &l)
	return l, err
}

func writeUploadRequest(w io.Writer, l uint32) error {
	buf := make([]byte, 5)
	buf[0] = typeUpload
	binary.BigEndian.PutUint32(buf[1:], l)
	_, err := w.Write(buf)
	return err
}

// UploadResponse format:
// Status (byte, 0=ok, 1=error)
// Message length (uint16 BE)
// Message (bytes)

func readUploadResponse(r io.Reader) (bool, string, error) {
	var status [1]byte
	if _, err := io.ReadFull(r, status[:]); err != nil {
		return false, "", err
	}
	var msgLen uint16
	if err := binary.Read(r, binary.BigEndian, &msgLen); err != nil {
		return false, "", err
	}
	// No message is fine
	if msgLen == 0 {
		return status[0] == 0, "", nil
	}
	msgBuf := make([]byte, msgLen)
	_, err := io.ReadFull(r, msgBuf)
	if err != nil {
		return false, "", err
	}
	return status[0] == 0, string(msgBuf), nil
}

func writeUploadResponse(w io.Writer, ok bool, msg string) error {
	sz := 1 + 2 + len(msg)
	buf := make([]byte, sz)
	if ok {
		buf[0] = 0
	} else {
		buf[0] = 1
	}
	binary.BigEndian.PutUint16(buf[1:], uint16(len(msg)))
	copy(buf[3:], msg)
	_, err := w.Write(buf)
	return err
}

// UploadSummary format:
// Duration (in milliseconds, uint32 BE)
// Received data length (uint32 BE)

func readUploadSummary(r io.Reader) (time.Duration, uint32, error) {
	var duration uint32
	if err := binary.Read(r, binary.BigEndian, &duration); err != nil {
		return 0, 0, err
	}
	var l uint32
	if err := binary.Read(r, binary.BigEndian, &l); err != nil {
		return 0, 0, err
	}
	return time.Duration(duration) * time.Millisecond, l, nil
}

func writeUploadSummary(w io.Writer, duration time.Duration, l uint32) error {
	buf := make([]byte, 8)
	binary.BigEndian.PutUint32(buf, uint32(duration/time.Millisecond))
	binary.BigEndian.PutUint32(buf[4:], l)
	_, err := w.Write(buf)
	return err
}