/***************************************************
  This is a library for the Adafruit 1.8" SPI display.
  This library works with the Adafruit 1.8" TFT Breakout w/SD card
  ----> http://www.adafruit.com/products/358
  as well as Adafruit raw 1.8" TFT display
  ----> http://www.adafruit.com/products/618

  Check out the links above for our tutorials and wiring diagrams
  These displays use SPI to communicate, 4 or 5 pins are required to
  interface (RST is optional)
  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.
  MIT license, all text above must be included in any redistribution
 ****************************************************/

#ifndef _BMC_ST7735_t3_H_
#define _BMC_ST7735_t3_H_


#include "Arduino.h"
#include "DMAChannel.h"
#ifdef __cplusplus
#include <SPI.h>
#endif

// #include "utility/BMC-Tools.h"
#include "display/drivers/BMC-ILI9341_fonts.h"
#include "display/drivers/BMC-GFXFonts.h"
#include "display/drivers/BMC-DisplayStruct.h"

#ifndef DISABLE_ST77XX_FRAMEBUFFER
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
#define ENABLE_ST77XX_FRAMEBUFFER
#elif defined(__IMXRT1062__)
#define ENABLE_ST77XX_FRAMEBUFFER
#endif
// Lets allow the user to define if they want T3.2 to enable frame buffer.
// it will only work on subset of displays due to memory
#define ENABLE_ST77XX_FRAMEBUFFER_T32
#if defined(__MK20DX256__) && defined(ENABLE_ST77XX_FRAMEBUFFER_T32)
#define ENABLE_ST77XX_FRAMEBUFFER
#endif
#endif


#define ST7735_SPICLOCK 24000000
//#define ST7735_SPICLOCK 16000000

// some flags for initR() :(
#define INITR_GREENTAB 0x0
#define INITR_REDTAB   0x1
#define INITR_BLACKTAB 0x2

#define INITR_18GREENTAB    INITR_GREENTAB
#define INITR_18REDTAB      INITR_REDTAB
#define INITR_18BLACKTAB    INITR_BLACKTAB
#define INITR_144GREENTAB   0x1
#define INITR_144GREENTAB_OFFSET   0x4
#define INITR_MINI160x80  0x05
#define INITR_MINI160x80_ST7735S 0x06

#define INIT_ST7789_TABCOLOR 42  // Not used except as a indicator to the code... 

#define ST7735_TFTWIDTH  128
#define ST7735_TFTWIDTH_80     80 // for mini
// for 1.44" display
#define ST7735_TFTHEIGHT_144 128
// for 1.8" display and mini
#define ST7735_TFTHEIGHT_160  160 // for 1.8" and mini display

#define ST7735_NOP     0x00
#define ST7735_SWRESET 0x01
#define ST7735_RDDID   0x04
#define ST7735_RDDST   0x09

#define ST7735_SLPIN   0x10
#define ST7735_SLPOUT  0x11
#define ST7735_PTLON   0x12
#define ST7735_NORON   0x13

#define ST7735_INVOFF  0x20
#define ST7735_INVON   0x21
#define ST7735_DISPOFF 0x28
#define ST7735_DISPON  0x29
#define ST7735_CASET   0x2A
#define ST7735_RASET   0x2B
#define ST7735_RAMWR   0x2C
#define ST7735_RAMRD   0x2E

#define ST7735_PTLAR   0x30
#define ST7735_COLMOD  0x3A
#define ST7735_MADCTL  0x36

#define ST7735_FRMCTR1 0xB1
#define ST7735_FRMCTR2 0xB2
#define ST7735_FRMCTR3 0xB3
#define ST7735_INVCTR  0xB4
#define ST7735_DISSET5 0xB6

#define ST7735_PWCTR1  0xC0
#define ST7735_PWCTR2  0xC1
#define ST7735_PWCTR3  0xC2
#define ST7735_PWCTR4  0xC3
#define ST7735_PWCTR5  0xC4
#define ST7735_VMCTR1  0xC5

#define ST7735_RDID1   0xDA
#define ST7735_RDID2   0xDB
#define ST7735_RDID3   0xDC
#define ST7735_RDID4   0xDD

#define ST7735_PWCTR6  0xFC

#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1

// Color definitions
#define ST7735_BLACK   0x0000
#define ST7735_BLUE    0x001F
#define ST7735_RED     0xF800
#define ST7735_GREEN   0x07E0
#define ST7735_CYAN    0x07FF
#define ST7735_MAGENTA 0xF81F
#define ST7735_YELLOW  0xFFE0
#define ST7735_WHITE   0xFFFF

// Also define them in a non specific ST77XX specific name
#define ST77XX_BLACK      0x0000
#define ST77XX_WHITE      0xFFFF
#define ST77XX_RED        0xF800
#define ST77XX_GREEN      0x07E0
#define ST77XX_BLUE       0x001F
#define ST77XX_CYAN       0x07FF
#define ST77XX_MAGENTA    0xF81F
#define ST77XX_YELLOW     0xFFE0
#define ST77XX_ORANGE     0xFC00
#define ST77XX_PINK       0xF81F

// Map fonts that were modified back to the ILI9341 font
// #define ST7735_t3_font_t BMC_ILI9341_t3_font_t
#define BMC_ST7735_t3_font_t BMC_ILI9341_t3_font_t

// Lets see about supporting Adafruit fonts as well?
#if __has_include(<gfxfont.h>)
  #include <gfxfont.h>
#endif

#ifndef _GFXFONT_H_
#define _GFXFONT_H_

/// Font data stored PER GLYPH
typedef struct {
	uint16_t bitmapOffset;     ///< Pointer into GFXfont->bitmap
	uint8_t  width;            ///< Bitmap dimensions in pixels
    uint8_t  height;           ///< Bitmap dimensions in pixels
	uint8_t  xAdvance;         ///< Distance to advance cursor (x axis)
	int8_t   xOffset;          ///< X dist from cursor pos to UL corner
    int8_t   yOffset;          ///< Y dist from cursor pos to UL corner
} GFXglyph;

/// Data stored for FONT AS A WHOLE
typedef struct { 
	uint8_t  *bitmap;      ///< Glyph bitmaps, concatenated
	GFXglyph *glyph;       ///< Glyph array
	uint8_t   first;       ///< ASCII extents (first char)
    uint8_t   last;        ///< ASCII extents (last char)
	uint8_t   yAdvance;    ///< Newline distance (y axis)
} GFXfont;

#endif // _GFXFONT_H_


#ifndef st7735_swap
#define st7735_swap(a, b) { typeof(a) t = a; a = b; b = t; }
#endif

#ifdef __cplusplus

#if defined(__IMXRT1062__)  // Teensy 4.x
// Also define these in lower memory so as to make sure they are not cached...
// try work around DMA memory cached.  So have a couple of buffers we copy frame buffer into
// as to move it out of the memory that is cached...
#define ST77XX_DMA_BUFFER_SIZE 512
typedef struct {
  DMASetting      _dmasettings[3];
  DMAChannel      _dmatx;
} BMC_ST7735DMA_Data;
#endif

#ifndef CL
#define CL(_r,_g,_b) ((((_r)&0xF8)<<8)|(((_g)&0xFC)<<3)|((_b)>>3))
#endif

//These enumerate the text plotting alignment (reference datum point)
#define TL_DATUM 0 // Top left (default)
#define TC_DATUM 1 // Top centre
#define TR_DATUM 2 // Top right
#define ML_DATUM 3 // Middle left
#define CL_DATUM 3 // Centre left, same as above
#define MC_DATUM 4 // Middle centre
#define CC_DATUM 4 // Centre centre, same as above
#define MR_DATUM 5 // Middle right
#define CR_DATUM 5 // Centre right, same as above
#define BL_DATUM 6 // Bottom left
#define BC_DATUM 7 // Bottom centre
#define BR_DATUM 8 // Bottom right
//#define L_BASELINE  9 // Left character baseline (Line the 'A' character would sit on)
//#define C_BASELINE 10 // Centre character baseline
//#define R_BASELINE 11 // Right character baseline



class BMC_ST7735_t3 : public Print
{

 public:

  BMC_ST7735_t3(uint8_t CS, uint8_t RS, uint8_t SID, uint8_t SCLK, uint8_t RST = -1);
  BMC_ST7735_t3(uint8_t CS, uint8_t RS, uint8_t RST = -1);
  BMC_ST7735_t3();
  void begin(uint8_t CS, uint8_t RS, uint8_t RST = -1);
  void begin(uint8_t CS, uint8_t RS, uint8_t SID, uint8_t SCLK, uint8_t RST = -1);

  void     initB(void),                             // for ST7735B displays
           initR(uint8_t options = INITR_GREENTAB), // for ST7735R
           setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1),
           pushColor(uint16_t color, boolean last_pixel=false),
           fillScreen(uint16_t color),
           drawPixel(int16_t x, int16_t y, uint16_t color),
           drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color),
           drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color),
           fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
   inline void fillWindow(uint16_t color) {fillScreen(color);}
  virtual void setRotation(uint8_t r);
  void     invertDisplay(boolean i);
  void     setRowColStart(uint16_t x, uint16_t y);
  uint16_t  rowStart() {return _rowstart;}
  uint16_t  colStart() {return _colstart;}




  // int16_t strPixelLen(const char * str){
  //   if(!str){
  //     return 0;
  //   }
  //   int16_t x1, y1;
	// 	uint16_t w1, h1;
	// 	getTextBounds(str, 0, 0, &x1, &y1, &w1, &h1);
  //   return w1;
  // }
  BMCTextPixelSize strPixelSize(const char * str){
    BMCTextPixelSize t;
    if(!str){
      return t;
    }
		getTextBounds(str, 0, 0, &t.x, &t.y, &t.w, &t.h);
    return t;
  }

  BMCTextPixelSize strPixelSize(const char * str, uint16_t t_x, uint16_t t_y){
    BMCTextPixelSize t;
    if(!str){
      return t;
    }
		getTextBounds(str, t_x, t_y, &t.x, &t.y, &t.w, &t.h);
    return t;
  }
  void printCenteredXY(int n, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding, uint16_t t_color, uint16_t t_background){
    char str[16] = "";
    sprintf(str, "%d", n);
    printCenteredXY(str, t_x, t_y, t_w, t_h, t_padding, t_color, t_background);
  }
  // @str the string to print
  // @t_x the x position where the text will start
  // @t_y the y position where the text will start
  //      these custom fonts set the y position at the bottom of the text
  // @t_w the width of the area to print in
  // @t_h the height of the area to print in
  // @t_padding amount of space that will be removed from @t_h to keep
  //            the text from printing over that area.
  void printCenteredXY(const char * str, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding, uint16_t t_color, uint16_t t_background){
    char buff[strlen(str)+1] = "";
    strcpy(buff, str);
    printCenteredXY(buff, t_x, t_y, t_w, t_h, t_padding, t_color, t_background);
  }
  void printCenteredXY(char * str, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding, uint16_t t_color, uint16_t t_background){
    BMCTextPixelSize t = getCenteredXY(str, t_x, t_y, t_w, t_h, t_padding);
    if(t.w > t_w){
      strShorten(str, true);
      uint8_t len = strlen(str)-1;
      while(strPixelLen(str)>t_w){
        str[len--] = 0;
      }
      t.x = t_x;
    }
    
    fillRect(t_x, t_y, t_w, t_h, t_background);
    setCursor(t.x, t.y);
    setTextColor(t_color);
    setTextWrap(false);
    print(str);
  }
  void strShorten(char * str, bool removeSpaces=false){
    // removes all vowels as well
    // if the first character of the string is a vowel leave it
    // removes all spaces from string if @removeSpaces is true
    uint16_t len = strlen(str);
    if(len < 5){
      return;
    }
    char buff[len+1] = "";
    for(uint8_t i = 1, e = 0 ; i < len ; i++){
      if((removeSpaces && str[i] == 32) || (str[i] == 65 || str[i] == 69 || str[i] == 73 ||
         str[i] == 79 || str[i] == 85 || str[i] == 97 || str[i] == 101 ||
         str[i] == 105 || str[i] == 111 || str[i] == 117)
      ){
        continue;
      } else if(str[i] == 0){
        break;
      }
      buff[e++] = str[i];
    }
    if(strlen(buff)>0){
      strcpy(str, buff);
    }
  }

  BMCTextPixelSize getCenteredXY(int n, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding){
    char str[16] = "";
    sprintf(str, "%d", n);
    return getCenteredXY(str, t_x, t_y, t_w, t_h, t_padding);
  }
  // @str the string to print
  // @t_x the x position where the text will start
  // @t_y the y position where the text will start
  //      these custom fonts set the y position at the bottom of the text
  // @t_w the width of the area to print in
  // @t_h the height of the area to print in
  // @t_padding amount of space that will be removed from @t_h to keep
  //            the text from printing over that area.
  BMCTextPixelSize getCenteredXY(const char * str, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding){
    char buff[strlen(str)+1];
    strcpy(buff, str);
    return getCenteredXY(buff,t_x,t_y,t_w,t_h,t_padding);
  }
  BMCTextPixelSize getCenteredXY(char * str, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding){
    setTextSize(1);
    BMCTextPixelSize t;
    for(int i = 0 ; i < 6 ; i++){
      // only 4 font sizes are available
      if(i == 0){
        if(t_h < 64){
          continue;
        }
        setFont(BMC_FONT_XXL);
      } else if(i == 1){
        setFont(BMC_FONT_XL);
      } else if(i == 2){
        setFont(BMC_FONT_LG);
      } else if(i == 3){
        setFont(BMC_FONT_MD);
      } else if(i == 4){
        setFont(BMC_FONT_SM);
      } else if(i == 5){
        setFont(BMC_FONT_XS);
      }
      t = strPixelSize(str, 0, 0);
      if((t.w) <= (t_w-t_padding) && ((t.h+(t_padding*2))+((t.y) + (t.h))) <= (t_h-t_padding)){
        // Serial.print("ILI ");
        // Serial.print(str);
        // Serial.print(" using font ");
        // Serial.println(i);
        break;
      }
    }
    t.x = t_x + (((t_w-t.x)-(t.w))/2);
    t.y = (t_y + ((t_h) - ((t_h-t.h)/2.0))) - (((t.y) + (t.h)));
    if(t.y >= (t_h+t_y)){
      t.y = t_y + (t_h-1);
    }
    return t;
  }

  BMCTextPixelSize getCurrentCenteredXY(int n, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding){
    char str[16] = "";
    sprintf(str, "%d", n);
    return getCurrentCenteredXY(str, t_x, t_y, t_w, t_h, t_padding);
  }
  // @str the string to print
  // @t_x the x position where the text will start
  // @t_y the y position where the text will start
  //      these custom fonts set the y position at the bottom of the text
  // @t_w the width of the area to print in
  // @t_h the height of the area to print in
  // @t_padding amount of space that will be removed from @t_h to keep
  //            the text from printing over that area.
  BMCTextPixelSize getCurrentCenteredXY(const char * str, uint16_t t_x, uint16_t t_y, uint16_t t_w, uint16_t t_h, uint8_t t_padding){
    setTextSize(1);
    BMCTextPixelSize t = strPixelSize(str, 0, 0);
    t.x = t_x + (((t_w-t.x)-(t.w))/2);
    t.y = (t_y + ((t_h) - ((t_h-t.h)/2.0))) - (((t.y) + (t.h)));
    if(t.y >= (t_h+t_y)){
      t.y = t_y + (t_h-1);
    }
    return t;
  }














  

  void setAddr(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
    __attribute__((always_inline)) {
        writecommand(ST7735_CASET); // Column addr set
        writedata16(x0+_xstart);   // XSTART 
        writedata16(x1+_xstart);   // XEND
        writecommand(ST7735_RASET); // Row addr set
        writedata16(y0+_ystart);   // YSTART
        writedata16(y1+_ystart);   // YEND
  }

  ////
  	// from Adafruit_GFX.h
	int16_t width(void) const { return _width; };
	int16_t height(void) const { return _height; }
	uint8_t getRotation(void);
	
	void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
	void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint16_t color);
	void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
	void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint16_t color);
	void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);
	void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);
	void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color);
	void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color);
	void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
	void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
	void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
	void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size_x, uint8_t size_y);
	void inline drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg, uint8_t size) 
	    { drawChar(x, y, c, color, bg, size, size);}
	
	static const int16_t CENTER = 9998;
	void setCursor(int16_t x, int16_t y, bool autoCenter=false);
    void getCursor(int16_t *x, int16_t *y);
	void setTextColor(uint16_t c);
	void setTextColor(uint16_t c, uint16_t bg);
    void setTextSize(uint8_t sx, uint8_t sy);
	void inline setTextSize(uint8_t s) { setTextSize(s,s); }
	uint8_t getTextSizeX();
	uint8_t getTextSizeY();
	uint8_t getTextSize();
	void setTextWrap(boolean w);
	boolean getTextWrap();
	
	//////
	virtual size_t write(uint8_t);		
	virtual size_t write(const uint8_t *buffer, size_t size);
	int16_t getCursorX(void) const { return cursor_x; }
	int16_t getCursorY(void) const { return cursor_y; }
	void setFont(const BMC_ILI9341_t3_font_t &f);
    void setFont(const GFXfont *f = NULL);
	void setFontAdafruit(void) { setFont(); }
	void drawFontChar(unsigned int c);
	void drawGFXFontChar(unsigned int c);

    void getTextBounds(const uint8_t *buffer, uint16_t len, int16_t x, int16_t y,
      int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
    void getTextBounds(const char *string, int16_t x, int16_t y,
      int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
    void getTextBounds(const String &str, int16_t x, int16_t y,
      int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h);
	int16_t strPixelLen(const char * str);

	// added support for drawing strings/numbers/floats with centering
	// modified from tft_ili9341_ESP github library
	// Handle numbers
	int16_t  drawNumber(long long_num,int poX, int poY);
	int16_t  drawFloat(float floatNumber,int decimal,int poX, int poY);   
	// Handle char arrays
	int16_t drawString(const String& string, int poX, int poY);
	int16_t drawString1(char string[], int16_t len, int poX, int poY);

	void setTextDatum(uint8_t datum);
	
	// added support for scrolling text area
	// https://github.com/vitormhenrique/ILI9341_t3
	// Discussion regarding this optimized version:
    //http://forum.pjrc.com/threads/26305-Highly-optimized-ILI9341-%28320x240-TFT-color-display%29-library
	//	
	void setScrollTextArea(int16_t x, int16_t y, int16_t w, int16_t h);
	void setScrollBackgroundColor(uint16_t color);
	void enableScroll(void);
	void disableScroll(void);
	void scrollTextArea(uint8_t scrollSize);
	void resetScrollBackgroundColor(uint16_t color);
	uint16_t readPixel(int16_t x, int16_t y);
	void readRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t *pcolors);

	// setOrigin sets an offset in display pixels where drawing to (0,0) will appear
	// for example: setOrigin(10,10); drawPixel(5,5); will cause a pixel to be drawn at hardware pixel (15,15)
	void setOrigin(int16_t x = 0, int16_t y = 0) { 
		_originx = x; _originy = y; 
		//if (Serial) Serial.printf("Set Origin %d %d\n", x, y);
		updateDisplayClip();
	}
	void getOrigin(int16_t* x, int16_t* y) { *x = _originx; *y = _originy; }

	// setClipRect() sets a clipping rectangle (relative to any set origin) for drawing to be limited to.
	// Drawing is also restricted to the bounds of the display

	void setClipRect(int16_t x1, int16_t y1, int16_t w, int16_t h) 
		{ _clipx1 = x1; _clipy1 = y1; _clipx2 = x1+w; _clipy2 = y1+h; 
			//if (Serial) Serial.printf("Set clip Rect %d %d %d %d\n", x1, y1, w, h);
			updateDisplayClip();
		}
	void setClipRect() {
			 _clipx1 = 0; _clipy1 = 0; _clipx2 = _width; _clipy2 = _height; 
			//if (Serial) Serial.printf("clear clip Rect\n");
			updateDisplayClip(); 
		}	
////
	
  void sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes);



  // Pass 8-bit (each) R,G,B, get back 16-bit packed color
  inline uint16_t Color565(uint8_t r, uint8_t g, uint8_t b) {
           return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3);
  }
  void setBitrate(uint32_t n);

  /* These are not for current use, 8-bit protocol only!
  uint8_t  readdata(void),
           readcommand8(uint8_t);
  uint16_t readcommand16(uint8_t);
  uint32_t readcommand32(uint8_t);
  void     dummyclock(void);
  */
  // Useful methods added from ili9341_t3 
  void writeRect(int16_t x, int16_t y, int16_t w, int16_t h, const uint16_t *pcolors);
  void writeSubImageRect(int16_t x, int16_t y, int16_t w, int16_t h, 
                        int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height, 
                        const uint16_t *pcolors);
  void writeSubImageRectBytesReversed(int16_t x, int16_t y, int16_t w, int16_t h, 
                        int16_t image_offset_x, int16_t image_offset_y, int16_t image_width, int16_t image_height, 
                        const uint16_t *pcolors);
  // writeRect8BPP -  write 8 bit per pixel paletted bitmap
  //          bitmap data in array at pixels, one byte per
  //pixel
  //          color palette data in array at palette
  void writeRect8BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect4BPP -  write 4 bit per pixel paletted bitmap
  //          bitmap data in array at pixels, 4 bits per
  //pixel
  //          color palette data in array at palette
  //          width must be at least 2 pixels
  void writeRect4BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect2BPP -  write 2 bit per pixel paletted bitmap
  //          bitmap data in array at pixels, 4 bits per
  //pixel
  //          color palette data in array at palette
  //          width must be at least 4 pixels
  void writeRect2BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRect1BPP -  write 1 bit per pixel paletted bitmap
  //          bitmap data in array at pixels, 4 bits per
  //pixel
  //          color palette data in array at palette
  //          width must be at least 8 pixels
  void writeRect1BPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     const uint8_t *pixels, const uint16_t *palette);

  // writeRectNBPP -  write N(1, 2, 4, 8) bit per pixel paletted bitmap
  //          bitmap data in array at pixels
  //  Currently writeRect1BPP, writeRect2BPP, writeRect4BPP use this to do all
  //  of the work.
  //
  void writeRectNBPP(int16_t x, int16_t y, int16_t w, int16_t h,
                     uint8_t bits_per_pixel, const uint8_t *pixels,
                     const uint16_t *palette);


// Frame buffer support
#ifdef ENABLE_ST77XX_FRAMEBUFFER
  enum {ST77XX_DMA_INIT=0x01, 
        ST77XX_DMA_EVER_INIT = 0x08,
        ST77XX_DMA_CONT=0x02, 
        ST77XX_DMA_FINISH=0x04,
        ST77XX_DMA_ACTIVE=0x80};

  // added support to use optional Frame buffer
  void  setFrameBuffer(uint16_t *frame_buffer);
  uint8_t useFrameBuffer(boolean b);    // use the frame buffer?  First call will allocate
  void  freeFrameBuffer(void);      // explicit call to release the buffer
  void  updateScreen(void);       // call to say update the screen now. 
  bool  updateScreenAsync(bool update_cont = false);  // call to say update the screen optinoally turn into continuous mode. 
  void  waitUpdateAsyncComplete(void);
  void  endUpdateAsync();      // Turn of the continueous mode fla
  void  dumpDMASettings();
  uint16_t *getFrameBuffer() {return _pfbtft;}
  uint32_t frameCount() {return _dma_frame_count; }
  uint16_t subFrameCount() { return _dma_sub_frame_count; }
  boolean asyncUpdateActive(void)  {return (_dma_state & ST77XX_DMA_ACTIVE);}
  void  initDMASettings(void);
  void setFrameCompleteCB(void (*pcb)(), bool fCallAlsoHalfDone = false);
  #else
  // added support to use optional Frame buffer
  void  setFrameBuffer(uint16_t *frame_buffer) {return;}
  uint8_t useFrameBuffer(boolean b) {return 0;};    // use the frame buffer?  First call will allocate
  void  freeFrameBuffer(void) {return;}      // explicit call to release the buffer
  void  updateScreen(void) {return;}       // call to say update the screen now. 
  bool  updateScreenAsync(bool update_cont = false) {return false;}  // call to say update the screen optinoally turn into continuous mode. 
  void  waitUpdateAsyncComplete(void) {return;}
  void  endUpdateAsync() {return;}      // Turn of the continueous mode fla
  void  dumpDMASettings() {return;}

  uint32_t frameCount() {return 0; }
  uint16_t *getFrameBuffer() {return NULL;}
  boolean asyncUpdateActive(void)  {return false;}
  uint16_t subFrameCount() { return 0; }
  void setFrameCompleteCB(void (*pcb)(), bool fCallAlsoHalfDone = false) {return;}

  #endif


 protected:
  uint8_t  tabcolor;

  void     spiwrite(uint8_t),
  		     spiwrite16(uint16_t d),
           writecommand(uint8_t c),
           writecommand_last(uint8_t c),
           writedata(uint8_t d),
           writedata_last(uint8_t d),
           writedata16(uint16_t d),
           writedata16_last(uint16_t d),
           commandList(const uint8_t *addr),
           commonInit(const uint8_t *cmdList, uint8_t mode=SPI_MODE0);
  void charBounds(char c, int16_t *x, int16_t *y,
      			int16_t *minx, int16_t *miny, int16_t *maxx, int16_t *maxy);
//uint8_t  spiread(void);

  boolean  hwSPI;
  ////
  	int16_t  cursor_x, cursor_y;
	bool 	 _center_x_text = false; 
	bool 	 _center_y_text = false; 
	int16_t  _clipx1, _clipy1, _clipx2, _clipy2;
	int16_t  _originx, _originy;
	int16_t  _displayclipx1, _displayclipy1, _displayclipx2, _displayclipy2;
	bool _invisible = false; 
	bool _standard = true; // no bounding rectangle or origin set. 

	inline void updateDisplayClip() {
		_displayclipx1 = max(0,min(_clipx1+_originx, width()));
		_displayclipx2 = max(0,min(_clipx2+_originx, width()));

		_displayclipy1 = max(0,min(_clipy1+_originy, height()));
		_displayclipy2 = max(0,min(_clipy2+_originy, height()));
		_invisible = (_displayclipx1 == _displayclipx2 || _displayclipy1 == _displayclipy2);
		_standard =  (_displayclipx1 == 0) && (_displayclipx2 == _width) && (_displayclipy1 == 0) && (_displayclipy2 == _height);
		if (Serial) {
			//Serial.printf("UDC (%d %d)-(%d %d) %d %d\n", _displayclipx1, _displayclipy1, _displayclipx2, _displayclipy2, _invisible, _standard);
		}
	}
	
	int16_t _width, _height;
	int16_t scroll_x, scroll_y, scroll_width, scroll_height;
	boolean scrollEnable,isWritingScrollArea; // If set, 'wrap' text at right edge of display

	uint16_t textcolor, textbgcolor, scrollbgcolor;
	uint32_t textcolorPrexpanded, textbgcolorPrexpanded;
	uint8_t textsize_x, textsize_y, rotation, textdatum;
	boolean wrap; // If set, 'wrap' text at right edge of display
	const BMC_ILI9341_t3_font_t *font;
	// Anti-aliased font support
	uint8_t fontbpp = 1;
	uint8_t fontbppindex = 0;
	uint8_t fontbppmask = 1;
	uint8_t fontppb = 8;
	uint8_t* fontalphalut;
	float fontalphamx = 1;
	
	uint32_t padX;

	// GFX Font support
	const GFXfont *gfxFont = nullptr;
	int8_t _gfxFont_min_yOffset = 0;

	// Opaque font chracter overlap?
	unsigned int _gfx_c_last;
	int16_t   _gfx_last_cursor_x, _gfx_last_cursor_y;
	int16_t	 _gfx_last_char_x_write = 0;
	uint16_t _gfx_last_char_textcolor;
	uint16_t _gfx_last_char_textbgcolor;
	bool gfxFontLastCharPosFG(int16_t x, int16_t y);
	
	void drawFontBits(bool opaque, uint32_t bits, uint32_t numbits, int32_t x, int32_t y, uint32_t repeat);
	void drawFontPixel( uint8_t alpha, uint32_t x, uint32_t y );
	uint32_t fetchpixel(const uint8_t *p, uint32_t index, uint32_t x);


  uint16_t _colstart, _rowstart, _xstart, _ystart, _rot, _screenHeight, _screenWidth;
  
  SPISettings _spiSettings;
#if defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK64FX512__) || defined(__MK66FX1M0__)
  uint8_t  _cs, _rs, _rst, _sid, _sclk;
  uint8_t pcs_data, pcs_command;
  uint32_t ctar;
  volatile uint8_t *datapin, *clkpin, *cspin, *rspin;

  SPIClass *_pspi = nullptr;
  uint8_t   _spi_num;          // Which buss is this spi on? 
  KINETISK_SPI_t *_pkinetisk_spi;
  SPIClass::SPI_Hardware_t *_spi_hardware;
  void waitTransmitComplete(void);
  void waitTransmitComplete(uint32_t mcr);
  uint32_t _fifo_full_test;

  inline void beginSPITransaction() {
    if (_pspi) _pspi->beginTransaction(_spiSettings);
    if (cspin) *cspin = 0;
  }

  inline void endSPITransaction()
  {
    if (cspin) *cspin = 1;
    if (_pspi) _pspi->endTransaction();  
  }


#endif
#if defined(__IMXRT1062__)  // Teensy 4.x
  SPIClass *_pspi = nullptr;
  uint8_t   _spi_num = 0;          // Which buss is this spi on? 
  IMXRT_LPSPI_t *_pimxrt_spi = nullptr;
  SPIClass::SPI_Hardware_t *_spi_hardware;
  uint8_t _pending_rx_count = 0;
  uint32_t _spi_tcr_current = 0; 
  uint32_t _tcr_dc_assert;
  uint32_t _tcr_dc_not_assert;


  void DIRECT_WRITE_LOW(volatile uint32_t * base, uint32_t mask)  __attribute__((always_inline)) {
    *(base+34) = mask;
  }
  void DIRECT_WRITE_HIGH(volatile uint32_t * base, uint32_t mask)  __attribute__((always_inline)) {
    *(base+33) = mask;
  }
  
#ifndef TCR_MASK
#define TCR_MASK  (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK )
#endif  
  void maybeUpdateTCR(uint32_t requested_tcr_state) {
  if ((_spi_tcr_current & TCR_MASK) != requested_tcr_state) {
      bool dc_state_change = (_spi_tcr_current & LPSPI_TCR_PCS(3)) != (requested_tcr_state & LPSPI_TCR_PCS(3));
      _spi_tcr_current = (_spi_tcr_current & ~TCR_MASK) | requested_tcr_state ;
      // only output when Transfer queue is empty.
      if (!dc_state_change || !_dcpinmask) {
        while ((_pimxrt_spi->FSR & 0x1f) )  ;
        _pimxrt_spi->TCR = _spi_tcr_current;  // update the TCR

      } else {
        waitTransmitComplete();
        if (requested_tcr_state & LPSPI_TCR_PCS(3)) DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
        else DIRECT_WRITE_LOW(_dcport, _dcpinmask);
        _pimxrt_spi->TCR = _spi_tcr_current & ~(LPSPI_TCR_PCS(3) | LPSPI_TCR_CONT); // go ahead and update TCR anyway?  

      }
    }
  }

  inline void beginSPITransaction() {
    if (hwSPI) _pspi->beginTransaction(_spiSettings);
    if (!_dcport) _spi_tcr_current = _pimxrt_spi->TCR;  // Only if DC is on hardware CS 
    if (_csport)DIRECT_WRITE_LOW(_csport, _cspinmask);
  }

  inline void endSPITransaction() {
    if (_csport)DIRECT_WRITE_HIGH(_csport, _cspinmask);
    if (hwSPI) _pspi->endTransaction();  
  }

 
  void waitFifoNotFull(void) {
    uint32_t tmp __attribute__((unused));
    do {
        if ((_pimxrt_spi->RSR & LPSPI_RSR_RXEMPTY) == 0)  {
            tmp = _pimxrt_spi->RDR;  // Read any pending RX bytes in
            if (_pending_rx_count) _pending_rx_count--; //decrement count of bytes still levt
        }
    } while ((_pimxrt_spi->SR & LPSPI_SR_TDF) == 0) ;
 }
 void waitTransmitComplete(void)  {
    uint32_t tmp __attribute__((unused));
//    digitalWriteFast(2, HIGH);

    while (_pending_rx_count) {
        if ((_pimxrt_spi->RSR & LPSPI_RSR_RXEMPTY) == 0)  {
            tmp = _pimxrt_spi->RDR;  // Read any pending RX bytes in
            _pending_rx_count--; //decrement count of bytes still levt
        }
    }
    _pimxrt_spi->CR = LPSPI_CR_MEN | LPSPI_CR_RRF;       // Clear RX FIFO
//    digitalWriteFast(2, LOW);
}


  uint8_t  _cs, _rs, _rst, _sid, _sclk;

  uint32_t _cspinmask;
  volatile uint32_t *_csport;
  uint32_t _dcpinmask;
  volatile uint32_t *_dcport;
  uint32_t _mosipinmask;
  volatile uint32_t *_mosiport;
  uint32_t _sckpinmask;
  volatile uint32_t *_sckport;
  

  uint32_t ctar;
#endif
#if defined(__MKL26Z64__)
  KINETISL_SPI_t *_pkinetisl_spi;
  uint8_t _dcpinAsserted;
  uint8_t _data_sent_not_completed;

  volatile uint8_t *dataport, *clkport, *csport, *rsport;
  uint8_t  _cs, _rs, _rst, _sid, _sclk,
           datapinmask, clkpinmask, cspinmask, rspinmask;
  boolean  hwSPI1;
  inline void beginSPITransaction() {
    if (hwSPI) SPI.beginTransaction(_spiSettings);
    else if (hwSPI1) SPI1.beginTransaction(_spiSettings);
    if (csport)*csport &= ~cspinmask;
  }

  inline void endSPITransaction() {
    if (csport) *csport |= cspinmask;
    if (hwSPI) SPI.endTransaction(); 
    else if (hwSPI1)  SPI1.endTransaction();  
  }

  void waitTransmitComplete();

  void setCommandMode() __attribute__((always_inline)) {
    if (_dcpinAsserted != 1) {
      waitTransmitComplete();
      *rsport  &= ~rspinmask;
      _dcpinAsserted = 1;
    }
  }

  void setDataMode() __attribute__((always_inline)) {
    if (_dcpinAsserted != 0) {
      waitTransmitComplete();
      *rsport  |= rspinmask;
      _dcpinAsserted = 0;
    }
  }

#endif 

#ifdef ENABLE_ST77XX_FRAMEBUFFER
    // Add support for optional frame buffer
  uint16_t  *_pfbtft;           // Optional Frame buffer 
  uint8_t   _use_fbtft;         // Are we in frame buffer mode?
  uint16_t  *_we_allocated_buffer;      // We allocated the buffer; 
  uint32_t  _count_pixels;       // How big is the display in total pixels...
  void (*_frame_complete_callback)() = nullptr;
  bool _frame_callback_on_HalfDone = false;

  // Add DMA support. 
  // Note: We have enough memory to have more than one, so could have multiple active devices (one per SPI BUS)
  //     All three devices have 3 SPI buss so hard coded
  static  BMC_ST7735_t3     *_dmaActiveDisplay[3];  // Use pointer to this as a way to get back to object...
  volatile uint8_t      _dma_state;         // DMA status
  volatile uint32_t     _dma_frame_count;   // Can return a frame count...
  volatile uint16_t     _dma_sub_frame_count = 0; // Can return a frame count...

  #if defined(__MK66FX1M0__) 
  // T3.6 use Scatter/gather with chain to do transfer
  static DMASetting   _dmasettings[3][4];
  DMAChannel   _dmatx;
  uint8_t      _cnt_dma_settings;   // how many do we need for this display?

  #elif defined(__IMXRT1062__)  // Teensy 4.x
  uint8_t       _cnt_dma_settings;   // how many do we need for this display?
  static BMC_ST7735DMA_Data _dma_data[3];   // one structure for each SPI buss... 
  // try work around DMA memory cached.  So have a couple of buffers we copy frame buffer into
  // as to move it out of the memory that is cached...
  volatile    uint32_t _dma_pixel_index = 0;
  uint16_t          _dma_buffer_size;   // the actual size we are using <= DMA_BUFFER_SIZE;
  uint16_t          _dma_cnt_sub_frames_per_frame;  
  uint32_t      _spi_fcr_save;    // save away previous FCR register value

  #elif defined(__MK64FX512__)
  // T3.5 - had issues scatter/gather so do just use channels/interrupts
  // and update and continue
  uint8_t _cspinmask;
  volatile uint8_t *_csport = nullptr;
  DMAChannel   _dmatx;
  DMAChannel   _dmarx;
  uint32_t   _dma_count_remaining;
  uint16_t   _dma_write_size_words;
  #elif defined(__MK20DX256__)
  // For first pass maybe emulate T3.5 on SPI...
  uint8_t _cspinmask;
  volatile uint8_t *_csport = nullptr;
  DMAChannel   _dmatx;
  DMAChannel   _dmarx;
  uint16_t   _dma_count_remaining;
  uint16_t   _dma_write_size_words;

  #endif  
  static void dmaInterrupt(void);
  static void dmaInterrupt1(void);
  static void dmaInterrupt2(void);
  void process_dma_interrupt(void);
#endif

	void HLine(int16_t x, int16_t y, int16_t w, uint16_t color)
	  __attribute__((always_inline)) 
	  {
		#ifdef ENABLE_ST77XX_FRAMEBUFFER
	  	if (_use_fbtft) {
	  		drawFastHLine(x, y, w, color);
	  		return;
	  	}
	  	#endif
	    x+=_originx;
	    y+=_originy;

	    // Rectangular clipping
	    if((y < _displayclipy1) || (x >= _displayclipx2) || (y >= _displayclipy2)) return;
	    if(x<_displayclipx1) { w = w - (_displayclipx1 - x); x = _displayclipx1; }
	    if((x+w-1) >= _displayclipx2)  w = _displayclipx2-x;
	    if (w<1) return;

		setAddr(x, y, x+w-1, y);
		writecommand(ST7735_RAMWR);
		do { writedata16(color); } while (--w > 0);
	  }
	  
	void VLine(int16_t x, int16_t y, int16_t h, uint16_t color)
	  __attribute__((always_inline)) 
	  {
		#ifdef ENABLE_ST77XX_FRAMEBUFFER
	  	if (_use_fbtft) {
	  		drawFastVLine(x, y, h, color);
	  		return;
	  	}
	  	#endif
		x+=_originx;
	    y+=_originy;

	    // Rectangular clipping
	    if((x < _displayclipx1) || (x >= _displayclipx2) || (y >= _displayclipy2)) return;
	    if(y < _displayclipy1) { h = h - (_displayclipy1 - y); y = _displayclipy1;}
	    if((y+h-1) >= _displayclipy2) h = _displayclipy2-y;
	    if(h<1) return;

		setAddr(x, y, x, y+h-1);
		writecommand(ST7735_RAMWR);
		do { writedata16(color); } while (--h > 0);
	  }
	  
	/**
	 * Found in a pull request for the Adafruit framebuffer library. Clever!
	 * https://github.com/tricorderproject/arducordermini/pull/1/files#diff-d22a481ade4dbb4e41acc4d7c77f683d
	 * Converts  0000000000000000rrrrrggggggbbbbb
	 *     into  00000gggggg00000rrrrr000000bbbbb
	 * with mask 00000111111000001111100000011111
	 * This is useful because it makes space for a parallel fixed-point multiply
	 * This implements the linear interpolation formula: result = bg * (1.0 - alpha) + fg * alpha
	 * This can be factorized into: result = bg + (fg - bg) * alpha
	 * alpha is in Q1.5 format, so 0.0 is represented by 0, and 1.0 is represented by 32
	 * @param	fg		Color to draw in RGB565 (16bit)
	 * @param	bg		Color to draw over in RGB565 (16bit)
	 * @param	alpha	Alpha in range 0-255
	 **/
	uint16_t alphaBlendRGB565( uint32_t fg, uint32_t bg, uint8_t alpha )
	 __attribute__((always_inline)) {
	 	alpha = ( alpha + 4 ) >> 3; // from 0-255 to 0-31
		bg = (bg | (bg << 16)) & 0b00000111111000001111100000011111;
		fg = (fg | (fg << 16)) & 0b00000111111000001111100000011111;
		uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
		return (uint16_t)((result >> 16) | result); // contract result
	}
	/**
	 * Same as above, but fg and bg are premultiplied, and alpah is already in range 0-31
	 */
	uint16_t alphaBlendRGB565Premultiplied( uint32_t fg, uint32_t bg, uint8_t alpha )
	 __attribute__((always_inline)) {
		uint32_t result = ((((fg - bg) * alpha) >> 5) + bg) & 0b00000111111000001111100000011111;
		return (uint16_t)((result >> 16) | result); // contract result
	}
	  
	void Pixel(int16_t x, int16_t y, uint16_t color)
	  __attribute__((always_inline)) {
	    x+=_originx;
	    y+=_originy;

	  	if((x < _displayclipx1) ||(x >= _displayclipx2) || (y < _displayclipy1) || (y >= _displayclipy2)) return;

		#ifdef ENABLE_ST77XX_FRAMEBUFFER
	  	if (_use_fbtft) {
        int pixel_index = (int)y*(int)_width + x;
	  		_pfbtft[pixel_index] = color;
	  		return;
	  	}
	  	#endif
		setAddr(x, y, x, y);
		writecommand(ST7735_RAMWR);
		writedata16(color);
	}

};

// To avoid conflict when also using Adafruit_GFX or any Adafruit library
// which depends on Adafruit_GFX, #include the Adafruit library *BEFORE*
// you #include ST7735_t3.h.
// Warning the implemention of class needs to be here, else the code
// compiled in the c++ file will cause duplicate defines in the link phase. 



// #ifdef Adafruit_GFX_Button
// #undef Adafruit_GFX_Button
// #endif
// #define Adafruit_GFX_Button ST7735_Button
// class ST7735_Button {
// public:
// 	ST7735_Button(void) { _gfx = NULL; }
// 	void initButton(ST7735_t3 *gfx, int16_t x, int16_t y,
// 		uint8_t w, uint8_t h,
// 		uint16_t outline, uint16_t fill, uint16_t textcolor,
// 		const char *label, uint8_t textsize_x, uint8_t textsize_y) {
// 		_x = x;
// 		_y = y;
// 		_w = w;
// 		_h = h;
// 		_outlinecolor = outline;
// 		_fillcolor = fill;
// 		_textcolor = textcolor;
// 		_textsize_x = textsize_x;
// 		_textsize_y = textsize_y;
// 		_gfx = gfx;
// 		strncpy(_label, label, 9);
// 		_label[9] = 0;

// 	}
// 	void drawButton(bool inverted = false) {
// 		uint16_t fill, outline, text;

// 		if (! inverted) {
// 			fill = _fillcolor;
// 			outline = _outlinecolor;
// 			text = _textcolor;
// 		} else {
// 			fill =  _textcolor;
// 			outline = _outlinecolor;
// 			text = _fillcolor;
// 		}
// 		_gfx->fillRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, fill);
// 		_gfx->drawRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, outline);
// 		_gfx->setCursor(_x - strlen(_label)*3*_textsize_x, _y-4*_textsize_y);
// 		_gfx->setTextColor(text);
// 		_gfx->setTextSize(_textsize_x, _textsize_y);
// 		_gfx->print(_label);
// 	}

// 	bool contains(int16_t x, int16_t y) {
// 		if ((x < (_x - _w/2)) || (x > (_x + _w/2))) return false;
// 		if ((y < (_y - _h/2)) || (y > (_y + _h/2))) return false;
// 		return true;
// 	}

// 	void press(boolean p) {
// 		laststate = currstate;
// 		currstate = p;
// 	}
// 	bool isPressed() { return currstate; }
// 	bool justPressed() { return (currstate && !laststate); }
// 	bool justReleased() { return (!currstate && laststate); }
// private:
// 	ST7735_t3 *_gfx;
// 	int16_t _x, _y;
// 	uint16_t _w, _h;
// 	uint8_t _textsize_x, _textsize_y;
// 	uint16_t _outlinecolor, _fillcolor, _textcolor;
// 	char _label[10];
// 	boolean currstate, laststate;
// };

#endif	 //end cplus

#endif	 