package uart

import (
	"bytes"
	"io"
	"strings"
	"time"

	"github.com/tarm/serial"
)

const (
	BUFFERSIZE  = 4096
	READTIMEOUT = 50 * time.Millisecond
	IDLE        = 200 * time.Millisecond
)

type UART struct {
	port *serial.Port
}

func New(device string, baud int) (*UART, error) {
	c := &serial.Config{
		Name:        device,
		Baud:        baud,
		ReadTimeout: READTIMEOUT,
	}

	p, err := serial.OpenPort(c)
	if err != nil {
		return nil, err
	}

	return &UART{port: p}, nil
}

func (u *UART) Write(cmd string) error {
	_, err := u.port.Write([]byte(strings.TrimSpace(cmd) + "\r"))
	return err
}

func (u *UART) ReadResponse() (string, error) {
	buf := make([]byte, BUFFERSIZE)
	var out bytes.Buffer

	received := false
	lastData := time.Now()
	for {
		n, err := u.port.Read(buf)

		if err != nil && err != io.EOF && !errorsIsTimeout(err) {
			return "", err
		}

		if n > 0 {
			received = true
			lastData = time.Now()
			out.Write(buf[:n])
		}

		if received && time.Since(lastData) >= IDLE {
			return strings.TrimSpace(out.String()), nil
		}
	}
}

func errorsIsTimeout(err error) bool {
	return strings.Contains(strings.ToLower(err.Error()), "timeout")
}

func (u *UART) Exec(cmd string) (string, error) {
	if err := u.Write(cmd); err != nil {
		return "", err
	}
	return u.ReadResponse()
}

func (u *UART) Close() error {
	return u.port.Close()
}
