#ifndef __ili9341_H
#define __ili9341_H

#include <f4ll_cpp/singleton.h>
#include <f4ll_cpp/dmahelper.h>
#include <inttypes.h>

namespace f4ll_cpp {

class Ili9341Fsmc : public Singleton<Ili9341Fsmc>, private DmaHelper
{
public:
	struct PixelPair {
		uint8_t g0, r0, r1, b0, b1, g1;
	};

	Ili9341Fsmc(volatile uint16_t *reg, volatile uint16_t *ram,
			DMA_TypeDef *dma, uint32_t dmaStream,
			bool horizontal = true);

	void SetScrollMode(bool on);
	void FillRect(uint16_t color, bool async = true);
	void FillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color, bool async = true);
	uint16_t Width() { return m_width; }
	uint16_t Height() { return m_height; }
	uint8_t ChrHeight() { return CHRHEIGHT; }
	uint8_t ChrWidth() { return CHRWIDTH; }
	void SetCursor(uint16_t x, uint16_t y, uint16_t fgColor, uint16_t bgColor);
	void SetCursor(uint16_t x, uint16_t y);
	void PrintChar(char c, uint16_t x, uint16_t y, uint16_t fgColor, uint16_t bgColor);
	void PrintChar(char c, bool transparent = false);
	void Print(char const *str, uint8_t len = 0, bool transparent = false);

	inline void WaitDmaIdle() { while(m_dmaEngineBusy); }

	void Test();

	static void HandleDmaIrq(Ili9341Fsmc *obj) { obj->HandleDmaIrq(); }

private:
	inline void WriteCmd(uint16_t cmd) { *m_reg = cmd; }
	inline void WriteCmd(uint16_t cmd, uint16_t param) { *m_reg = cmd; *m_ram = param; }
	void WriteCmd(uint16_t cmd, std::initializer_list<const uint16_t> params);
	void WriteCmd_(uint16_t cmd, uint16_t cnt, ...);
	void SetRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height);
	void WritePixels(void *src, uint32_t count, bool increment, bool async = true);
	void ReadGRam(PixelPair *dst, uint32_t count, bool async = true);
	void ReadPixels(uint16_t *dst, uint32_t count);
	void SetScrollOffset();

	volatile uint16_t * const	m_reg;
	volatile uint16_t * const	m_ram;

	void HandleDmaIrq();
	void SetupDmaSize(uint32_t size);
	uint16_t AdjustY(uint16_t y) { return (m_scrollOffset + y) % m_height; }

	volatile bool m_dmaEngineBusy = false;
	uint32_t	m_dmaRemainingPixels = 0;
	uint16_t	m_dmaColor = 0;


	// initialized in the constructor
	uint16_t const m_width;
	uint16_t const m_height;
	uint16_t       m_rectX;
	uint16_t       m_rectY;
	uint16_t       m_rectWidth;
	uint16_t       m_rectHeight;

	bool		m_scrollMode = false;
	uint16_t    m_scrollOffset = 0;

	uint16_t	m_xPos = 0;
	uint16_t	m_yPos = 0;
	uint16_t	m_fgColor = ILI9341_WHITE;
	uint16_t	m_bgColor = ILI9341_BLACK;

	enum Commands {
		ILI9341_NOP                                     = 0x00,
		ILI9341_RESET                                   = 0x01,
		ILI9341_READ_DISPLAY_IDENTIFICATION_INFORMATION = 0x04,
		ILI9341_READ_DISPLAY_STATUS                     = 0x09,
		ILI9341_READ_DISPLAY_POWER_MODE                 = 0x0A,
		ILI9341_READ_DISPLAY_MADCTL                     = 0x0B,
		ILI9341_READ_DISPLAY_PIXEL_FORMAT               = 0x0C,
		ILI9341_READ_DISPLAY_IMAGE_FORMAT               = 0x0D,
		ILI9341_READ_DISPLAY_SIGNAL_MODE                = 0x0E,
		ILI9341_READ_DISPLAY_SELF_DIAGNOSTIC_RESULT     = 0x0F,
		ILI9341_ENTER_SLEEP_MODE                        = 0x10,
		ILI9341_SLEEP_OUT                               = 0x11,
		ILI9341_PARTIAL_MODE_ON                         = 0x12,
		ILI9341_NORMAL_DISPLAY_MODE_ON                  = 0x13,
		ILI9341_DISPLAY_INVERSION_OFF                   = 0x20,
		ILI9341_DISPLAY_INVERSION_ON                    = 0x21,
		ILI9341_GAMMA                                   = 0x26,
		ILI9341_DISPLAY_OFF                             = 0x28,
		ILI9341_DISPLAY_ON                              = 0x29,
		ILI9341_COLUMN_ADDR                             = 0x2A,
		ILI9341_PAGE_ADDR                               = 0x2B,
		ILI9341_GRAM                                    = 0x2C,
		ILI9341_COLOR_SET                               = 0x2D,
		ILI9341_MEMORY_READ                             = 0x2E,
		ILI9341_PARTIAL_AREA                            = 0x30,
		ILI9341_VERTICAL_SCROLLING_DEFINITION           = 0x33,
		ILI9341_TEARING_EFFECT_LINE_OFF                 = 0x34,
		ILI9341_TEARING_EFFECT_LINE_ON                  = 0x35,
		ILI9341_MAC                                     = 0x36,
		ILI9341_VERTICAL_SCROLLING_START_ADDRESS        = 0x37,
		ILI9341_IDLE_MODE_OFF                           = 0x38,
		ILI9341_IDLE_MODE_ON                            = 0x39,
		ILI9341_PIXEL_FORMAT                            = 0x3A,
		ILI9341_WMC                                     = 0x3C,
		ILI9341_RMC                                     = 0x3E,
		ILI9341_SET_TEAR_SCANLINE                       = 0x44,
		ILI9341_WDB                                     = 0x51,
		ILI9341_READ_DISPLAY_BRIGHTNESS                 = 0x52,
		ILI9341_WCD                                     = 0x53,
		ILI9341_READ_CTRL_DISPLAY                       = 0x54,
		ILI9341_WCABC                                   = 0x55,
		ILI9341_RCABC                                   = 0x56,
		ILI9341_WCABCMB                                 = 0x5E,
		ILI9341_RCABCMB                                 = 0x5F,
		ILI9341_RGB_INTERFACE                           = 0xB0,
		ILI9341_FRC                                     = 0xB1,
		ILI9341_FRAME_CTRL_NM                           = 0xB2,
		ILI9341_FRAME_CTRL_IM                           = 0xB3,
		ILI9341_FRAME_CTRL_PM                           = 0xB4,
		ILI9341_BPC                                     = 0xB5,
		ILI9341_DFC                                     = 0xB6,
		ILI9341_ENTRY_MODE_SET                          = 0xB7,
		ILI9341_BACKLIGHT_CONTROL_1                     = 0xB8,
		ILI9341_BACKLIGHT_CONTROL_2                     = 0xB9,
		ILI9341_BACKLIGHT_CONTROL_3                     = 0xBA,
		ILI9341_BACKLIGHT_CONTROL_4                     = 0xBB,
		ILI9341_BACKLIGHT_CONTROL_5                     = 0xBC,
		ILI9341_BACKLIGHT_CONTROL_6                     = 0xBD,
		ILI9341_BACKLIGHT_CONTROL_7                     = 0xBE,
		ILI9341_BACKLIGHT_CONTROL_8                     = 0xBF,
		ILI9341_POWER1                                  = 0xC0,
		ILI9341_POWER2                                  = 0xC1,
		ILI9341_VCOM1                                   = 0xC5,
		ILI9341_VCOM2                                   = 0xC7,
		ILI9341_POWERA                                  = 0xCB,
		ILI9341_POWERB                                  = 0xCF,
		ILI9341_READ_ID1                                = 0xDA,
		ILI9341_READ_ID2                                = 0xDB,
		ILI9341_READ_ID3                                = 0xDC,
		ILI9341_READ_ID4                                = 0xD3,
		ILI9341_PGAMMA                                  = 0xE0,
		ILI9341_NGAMMA                                  = 0xE1,
		ILI9341_DTCA                                    = 0xE8,
		ILI9341_DTCB                                    = 0xEA,
		ILI9341_POWER_SEQ                               = 0xED,
		ILI9341_3GAMMA_EN                               = 0xF2,
		ILI9341_INTERFACE                               = 0xF6,
		ILI9341_PRC                                     = 0xF7,
	};

	static const uint8_t CHRHEIGHT = 8;		// SHOULD BE 8
	static const uint8_t CHRWIDTH = 6;
	static const uint8_t CHRCOUNT = 96;
	static const uint8_t m_font[CHRCOUNT][CHRWIDTH];

	uint16_t m_fontBkgBuffer[CHRHEIGHT][CHRWIDTH];

public:
	enum Colors {
		ILI9341_BLACK =       0x0000,      /*   0,   0,   0 */
		ILI9341_NAVY =        0x000F,      /*   0,   0, 128 */
		ILI9341_DARKGREEN =   0x03E0,      /*   0, 128,   0 */
		ILI9341_DARKCYAN =    0x03EF,      /*   0, 128, 128 */
		ILI9341_MAROON =      0x7800,      /* 128,   0,   0 */
		ILI9341_PURPLE =      0x780F,      /* 128,   0, 128 */
		ILI9341_OLIVE =       0x7BE0,      /* 128, 128,   0 */
		ILI9341_LIGHTGREY =   0xC618,      /* 192, 192, 192 */
		ILI9341_DARKGREY =    0x7BEF,      /* 128, 128, 128 */
		ILI9341_BLUE =        0x001F,      /*   0,   0, 255 */
		ILI9341_GREEN =       0x07E0,      /*   0, 255,   0 */
		ILI9341_CYAN =        0x07FF,      /*   0, 255, 255 */
		ILI9341_RED =         0xF800,      /* 255,   0,   0 */
		ILI9341_MAGENTA =     0xF81F,      /* 255,   0, 255 */
		ILI9341_YELLOW =      0xFFE0,      /* 255, 255,   0 */
		ILI9341_WHITE =       0xFFFF,      /* 255, 255, 255 */
		ILI9341_ORANGE =      0xFD20,      /* 255, 165,   0 */
		ILI9341_GREENYELLOW = 0xAFE5,      /* 173, 255,  47 */
		ILI9341_PINK = 	      0xF81F,
	};
};

}	//	f4ll_cpp

#endif
