#pragma once

#include "common.h"

#include <USB.h>
#include <HardwareSerial.h>
#include <FreeRTOS.h>
#include <freertos/stream_buffer.h>

#include <atomic>

void tty_init();
void tty_ping();

//
// This is needed because there isn't a common serial device
// class in Arduino, sigh...
//
struct tty_packet_hdr {
    uint8_t  stx;
    uint8_t  len;
    uint16_t resv;
    uint32_t offs;
    uint32_t crc;
} __attribute__((packed));

class TTY {
public:
    TTY(Stream &port);
    ~TTY();
    void reset();
    int  rxdata(void *buf, size_t len);
    static int rxdata(token_t me, void *buf, size_t len);

    // Global events
    static void init();
    static void ping();

private:
    Stream *_port;		// Android stream

    void _onrx();
    void _onerr();
    void _onbreak();
    void _onconnect();
    void _ondisconnect();
    void (*_flush)();

public:
    inline void flush() { _flush(); }
    inline Stream & port() { return *_port; }
    inline operator Stream & () { return port(); }

private:
    // Event handler dispatchers
    static void usb_task_handler(void *);
    static void usb_onevent(void *arg, esp_event_base_t event_base,
			    int32_t event_id, void *event_data);
    static void uart_onrx();
    static void uart_onerr(hardwareSerial_error_t);

    enum rx_state {
	normal,		// Normal console mode
	stxwait,	// Waiting for header STX
	hdr,		// Getting header
	data		// Getting data packet
    };

    void _upload_begin();
    void _update_window(bool rst = false);
    int _decode_data(int);

    // Packet receive state machine
    struct {
	enum rx_state state;
	uint8_t b64_buf;
	uint8_t b64_bits;
	union {
	    struct tty_packet_hdr hdr;
	    uint8_t hdr_raw[sizeof(struct tty_packet_hdr)];
	};
	size_t rlen;
	uint32_t last_ack;
    } rx;
    StreamBufferHandle_t rx_sbuf;
    uint32_t tx_credits;
    volatile bool tx_credits_reset;
    volatile uint32_t last_rx;
    uint8_t rx_data[256];
};
