package at

import (
	"fmt"
	"io"
	"log"
	"sync"
	"time"

	"go.bug.st/serial"
)

// Some constants
const (
	ReadTimeout  = time.Second
	InputBufSize = 512
)

type atPort struct {
	logger *log.Logger

	mutex sync.Mutex // Mutex for all operation with serial port

	baudrate int
	portName string
	port     serial.Port
	inputBuf []byte
}

type Port interface {
	GetName() string
	GetBaudrate() int
	SerialPort() serial.Port // For extra need
	Mutex() sync.Locker      // retruns pointer to mutex for advanced use like readign NMEA reports

	Connect() error
	Disconnect() error
	IsConnected() bool

	RawSend(msg string) (string, error)
	Send(cmd string) (Resp, error)

	io.Closer
}

func New(logger *log.Logger, portName string, baudrate int) Port {
	return &atPort{
		logger:   logger,
		portName: portName,
		baudrate: baudrate,
		inputBuf: make([]byte, InputBufSize),
	}
}

func (p *atPort) GetName() string {
	return p.portName
}

func (p *atPort) GetBaudrate() int {
	return p.baudrate
}

func (p *atPort) SerialPort() serial.Port {
	return p.port
}

func (p *atPort) Mutex() sync.Locker {
	return &p.mutex
}

func (p *atPort) Connect() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	p.logger.Println("Connecting to", p.portName, "...")
	s, err := serial.Open(p.portName, &serial.Mode{BaudRate: p.baudrate})
	if err != nil {
		return fmt.Errorf("open port: %w", err)
	}
	// s.Close() There is no open f
	// s.Open()
	p.port = s
	p.port.SetReadTimeout(ReadTimeout)
	p.port.ResetInputBuffer()
	p.port.ResetOutputBuffer()
	return nil
}

func (p *atPort) Disconnect() error {
	p.mutex.Lock()
	defer func() {
		p.port = nil
		p.mutex.Unlock()
	}()
	if err := p.port.Close(); err != nil {
		return fmt.Errorf("close port: %w", err)
	}
	return nil
}

func (p *atPort) IsConnected() bool {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	return p.port != nil
}

// Low level write/read function
func (p *atPort) RawSend(msg string) (string, error) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	// Write
	if _, err := p.port.Write([]byte(msg)); err != nil {
		return "", fmt.Errorf("serial port write: %w", err)
	}
	// time.Sleep(time.Millisecond)
	// Read
	readLen, err := p.port.Read(p.inputBuf)
	// p.logger.Println(msg, "\x1b[38;2;150;150;150mRAWREAD:", string(p.inputBuf[:readLen]), "\x1b[38;2;255;255;255m")
	if err != nil {
		return "", fmt.Errorf("port read: %w", err)
	}

	return string(p.inputBuf[:readLen]), nil
}

func (p *atPort) Send(cmd string) (Resp, error) {
	cmd += "\r\n"
	rawResp, err := p.RawSend(cmd)
	if err != nil {
		return RespNil, fmt.Errorf("make request: %w", err)
	}
	if len(rawResp) <= 4 {
		return RespNil, fmt.Errorf("read too small msg: %d byte - %s", len(rawResp), string(rawResp))
	}
	resp := rawResp[2 : len(rawResp)-2] // Cut \r\n

	return Resp(resp), nil
}

func (p *atPort) Close() error {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	return p.port.Close()
}