#ifndef _TFT_HPP
#define _TFT_HPP

#include "daisy.h"

#include "scope.hpp"
#include "audioc.hpp"

using namespace daisy;

#define ILI9341_TFTWIDTH 240  ///< ILI9341 max TFT width
#define ILI9341_TFTHEIGHT 320 ///< ILI9341 max TFT height

#define ILI9341_NOP 0x00     ///< No-op register
#define ILI9341_SWRESET 0x01 ///< Software reset register
#define ILI9341_RDDID 0x04   ///< Read display identification information
#define ILI9341_RDDST 0x09   ///< Read Display Status

#define ILI9341_SLPIN 0x10  ///< Enter Sleep Mode
#define ILI9341_SLPOUT 0x11 ///< Sleep Out
#define ILI9341_PTLON 0x12  ///< Partial Mode ON
#define ILI9341_NORON 0x13  ///< Normal Display Mode ON

#define ILI9341_RDMODE 0x0A     ///< Read Display Power Mode
#define ILI9341_RDMADCTL 0x0B   ///< Read Display MADCTL
#define ILI9341_RDPIXFMT 0x0C   ///< Read Display Pixel Format
#define ILI9341_RDIMGFMT 0x0D   ///< Read Display Image Format
#define ILI9341_RDSELFDIAG 0x0F ///< Read Display Self-Diagnostic Result

#define ILI9341_INVOFF 0x20   ///< Display Inversion OFF
#define ILI9341_INVON 0x21    ///< Display Inversion ON
#define ILI9341_GAMMASET 0x26 ///< Gamma Set
#define ILI9341_DISPOFF 0x28  ///< Display OFF
#define ILI9341_DISPON 0x29   ///< Display ON

#define ILI9341_CASET 0x2A ///< Column Address Set
#define ILI9341_PASET 0x2B ///< Page Address Set
#define ILI9341_RAMWR 0x2C ///< Memory Write
#define ILI9341_RAMRD 0x2E ///< Memory Read

#define ILI9341_PTLAR 0x30    ///< Partial Area
#define ILI9341_VSCRDEF 0x33  ///< Vertical Scrolling Definition
#define ILI9341_MADCTL 0x36   ///< Memory Access Control
#define ILI9341_VSCRSADD 0x37 ///< Vertical Scrolling Start Address
#define ILI9341_PIXFMT 0x3A   ///< COLMOD: Pixel Format Set
#define ILI9341_WRCONT 0x3C   ///< Continue Memory Write

#define ILI9341_DBRIGHT 0x51

#define ILI9341_FRMCTR1                                                        \
  0xB1 ///< Frame Rate Control (In Normal Mode/Full Colors)
#define ILI9341_FRMCTR2 0xB2 ///< Frame Rate Control (In Idle Mode/8 colors)
#define ILI9341_FRMCTR3                                                        \
  0xB3 ///< Frame Rate control (In Partial Mode/Full Colors)
#define ILI9341_INVCTR 0xB4  ///< Display Inversion Control
#define ILI9341_DFUNCTR 0xB6 ///< Display Function Control

#define ILI9341_PWCTR1 0xC0 ///< Power Control 1
#define ILI9341_PWCTR2 0xC1 ///< Power Control 2
#define ILI9341_PWCTR3 0xC2 ///< Power Control 3
#define ILI9341_PWCTR4 0xC3 ///< Power Control 4
#define ILI9341_PWCTR5 0xC4 ///< Power Control 5
#define ILI9341_VMCTR1 0xC5 ///< VCOM Control 1
#define ILI9341_VMCTR2 0xC7 ///< VCOM Control 2

#define ILI9341_RDID1 0xDA ///< Read ID 1
#define ILI9341_RDID2 0xDB ///< Read ID 2
#define ILI9341_RDID3 0xDC ///< Read ID 3
#define ILI9341_RDID4 0xDD ///< Read ID 4

#define ILI9341_GMCTRP1 0xE0 ///< Positive Gamma Correction
#define ILI9341_GMCTRN1 0xE1 ///< Negative Gamma Correction
//#define ILI9341_PWCTR6     0xFC

// Color definitions
#define ILI9341_BLACK 0x0000       ///<   0,   0,   0
#define ILI9341_NAVY 0x000F        ///<   0,   0, 123
#define ILI9341_DARKGREEN 0x03E0   ///<   0, 125,   0
#define ILI9341_DARKCYAN 0x03EF    ///<   0, 125, 123
#define ILI9341_MAROON 0x7800      ///< 123,   0,   0
#define ILI9341_PURPLE 0x780F      ///< 123,   0, 123
#define ILI9341_OLIVE 0x7BE0       ///< 123, 125,   0
#define ILI9341_LIGHTGREY 0xC618   ///< 198, 195, 198
#define ILI9341_DARKGREY 0x7BEF    ///< 123, 125, 123
#define ILI9341_BLUE 0x001F        ///<   0,   0, 255
#define ILI9341_GREEN 0x07E0       ///<   0, 255,   0
#define ILI9341_CYAN 0x07FF        ///<   0, 255, 255
#define ILI9341_RED 0xF800         ///< 255,   0,   0
#define ILI9341_MAGENTA 0xF81F     ///< 255,   0, 255
#define ILI9341_YELLOW 0xFFE0      ///< 255, 255,   0
#define ILI9341_WHITE 0xFFFF       ///< 255, 255, 255
#define ILI9341_ORANGE 0xFD20      ///< 255, 165,   0
#define ILI9341_GREENYELLOW 0xAFE5 ///< 173, 255,  41
#define ILI9341_PINK 0xFC18        ///< 255, 130, 198


struct __attribute__((packed)) tft_color {
	uint8_t		red;
	uint8_t		green;
	uint8_t		blue;
	// uint16_t c16;

	tft_color() : red(0x00), green(0), blue(0x00) {};
	tft_color(uint8_t r, uint8_t g, uint8_t b) :
		red(r), green(g), blue(b) {};

	inline uint16_t u16(void) const
	{
		uint16_t ret = 0;
		ret |= ((uint16_t)(red & 0xf8) << 8);
		ret |= ((uint16_t)(green & 0xfc)) << 5;
		ret |= ((uint16_t)blue) >> 3;
		return (ret);
	}
};

struct tft_dma_cmd {
	size_t	size;
	uint8_t	buf[];
};

#define TFT_CMD_PARAMS_MAXLEN	16
struct tft_cmd {
	uint8_t cmd;
	uint8_t _param_bytes[TFT_CMD_PARAMS_MAXLEN];
	size_t param_len;
	uint8_t *param_bytes_ext;
	int post_delay;

	tft_cmd(uint8_t c, std::initializer_list<uint8_t> params,
	    int dly = -1)
	       	: cmd(c), param_len(params.size()), post_delay(dly)
	{
		size_t i;

		AUDIOC_ASSERT(param_len <= sizeof(_param_bytes));

		i = 0;
		for (auto par : params) {
			_param_bytes[i++] = par;
		}
	}
	tft_cmd(uint8_t c, int dly = -1)
	       	: cmd(c), param_len(0), post_delay(dly) {};

	inline const uint8_t *param_bytes(void) const
	{
		return (_param_bytes);
	}
};

#define TFT_DRIVER_ILI9341_NBLOCKS	8

struct tft_driver_ili9341 {
	typedef void (*async_on_finish_f)(void *);

	enum class async_status {
		INIT,
		READY,
		TX_NONE,
		TX_ACTIVE,
		TX_INACTIVE,
		DONE
	};

	enum class async_wr {
		START,
		END
	};

	enum class async_hint {
		NONE,
		FULL_UPDATE_RENDER,
		PARTIAL_UPDATE_RENDER
	};

	SpiHandle		spi;
	SpiHandle::Config	spi_conf;

	Pin			dcx_pin;
	GPIO			dcx;

	async_hint			_async_hint;
	volatile async_status		_async_stat;
	volatile async_wr		_async_wr;
	volatile async_on_finish_f	_async_cb_fun;
	volatile void			*_async_cb_ctx;
	int				_async_id;

	uint8_t			*_async_dma_buf;
	size_t			_async_dma_sz;

	volatile tft_dma_cmd		*_async_curr_wr;
	volatile const tft_dma_cmd	*_async_curr_rd;

	/* general */
	inline uint16_t width(void) { return (ILI9341_TFTWIDTH); }
	inline uint16_t height(void) { return (ILI9341_TFTHEIGHT); }

	tft_driver_ili9341(void) : _async_hint(async_hint::NONE),
		_async_stat(async_status::INIT), _async_wr(async_wr::END),
		_async_cb_fun(NULL), _async_cb_ctx(NULL),
		_async_id(-1),
		_async_dma_buf(NULL), _async_dma_sz(0),
		_async_curr_wr(NULL), _async_curr_rd(NULL) {};

	int init(uint8_t *dma_buf, size_t dma_sz);

	/* syncronous */
	int fill_screen(const tft_color *c);
	void set_addr_window(uint16_t col_st, uint16_t width, uint16_t row_st,
	    uint16_t height);

	int	send_sync(const tft_cmd *cmd);
	int	send_sync(uint8_t cmd, const uint8_t *buf, size_t len);
	int	send_raw_sync(const uint8_t *buf, size_t len);
	ssize_t	recv_raw_sync(const uint8_t *buf, size_t len);

	/* asyncronous */
	void async_on_finish(async_on_finish_f cb, void *ctx);

	int async_start(void);
	int async_end(void);

	async_status async_stat(void);

	void async_flush(void);
	void async_tx(void);
	void async_tx_try(void);
	void async_finalize(void);

	int async_ready(void);

	inline int async_id(void) { return (_async_id); }
	inline int async_id_next(void) { 
		_async_id++;
		_async_id &= 0xffff;
		return (_async_id);
       	}

	int async_cmd_hint(async_hint hint);
	int async_hline_row_bg_fill(int async_id, tft_color *bg, tft_color *fg,
	    uint16_t hline_idx_st, uint16_t hline_idx_end, uint16_t row_idx);

	static void async_tx_cmd_scb(void *ctx);
	static void async_tx_cmd_ecb(void *ctx, SpiHandle::Result res);
	static void async_tx_param_scb(void *ctx);
	static void async_tx_param_ecb(void *ctx, SpiHandle::Result res);

	inline int
	async_send_raw(const uint8_t *dma_buf, size_t dma_bufsz,
	    SpiHandle::StartCallbackFunctionPtr scb,
	    SpiHandle::EndCallbackFunctionPtr cb,
	    void *ctx) {
		return (spi.DmaTransmit((uint8_t *)dma_buf, dma_bufsz, scb,
		    cb, ctx) == SpiHandle::Result::OK ? 0 : -1);
	}

	size_t dma_buf_sz_tx_pend(void);
	size_t dma_buf_sz_used(void);

	int dma_buf_reset(void);

	tft_dma_cmd *dma_buf_curr_wr(void);
	tft_dma_cmd *dma_buf_next_wr(size_t curr_sz);

	const tft_dma_cmd *dma_buf_curr_rd(void);
	const tft_dma_cmd *dma_buf_next_rd(void);

	/* rendering */
	int render_dma_buf_hline(const tft_color *c, uint16_t col_s,
	    uint16_t col_e, uint16_t row);
	int render_dma_buf_memwr(const tft_color *col, size_t num);
	int render_dma_buf_memwr_cont(const tft_color *col, size_t num);
	int render_dma_buf_set_addr_window(uint16_t col_st,
	    uint16_t col_end, uint16_t row_st, uint16_t row_end);
	int render_dma_buf_hline_row_bg_fill(const tft_color *bg,
	    const tft_color *fg, uint16_t col_s, uint16_t col_e, uint16_t row);

	/* gpio control */
	inline void
	dcx_low(void)
	{
		dcx.Write(0);
	}

	inline void
	dcx_high(void)
	{
		dcx.Write(1);
	}
};

#endif
