/*********************************************************************
This is a library for Monochrome OLEDs based on SSD1308 controller,
derived from adafruit SSD1306 driver.
 See at ------> http://www.adafruit.com/category/63_98
*********************************************************************/

#include <avr/pgmspace.h>
#include <stdlib.h>
#include "Adafruit_GFX.h"
#include "Grove_ssd1308.h"
#include <Wire.h>

// the memory buffer for the LCD
static uint8_t buffer[SSD1308_LCDHEIGHT * SSD1308_LCDWIDTH / 8] = 
 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
   0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x80, 0x80, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xF8, 0xE0, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
   0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0xFF,
   0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
   0x80, 0xFF, 0xFF, 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x8C, 0x8E, 0x84, 0x00, 0x00, 0x80, 0xF8,
   0xF8, 0xF8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xE0, 0xE0, 0xC0, 0x80,
   0x00, 0xE0, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0x01, 0x01,
   0x01, 0x01, 0x83, 0xFF, 0xFF, 0x00, 0x00, 0x7C, 0xFE, 0xC7, 0x01, 0x01, 0x01, 0x01, 0x83, 0xFF,
   0xFF, 0xFF, 0x00, 0x38, 0xFE, 0xC7, 0x83, 0x01, 0x01, 0x01, 0x83, 0xC7, 0xFF, 0xFF, 0x00, 0x00,
   0x01, 0xFF, 0xFF, 0x01, 0x01, 0x00, 0xFF, 0xFF, 0x07, 0x01, 0x01, 0x01, 0x00, 0x00, 0x7F, 0xFF,
   0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF,
   0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x03, 0x0F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC7, 0xC7, 0x8F,
   0x8F, 0x9F, 0xBF, 0xFF, 0xFF, 0xC3, 0xC0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC, 0xFC,
   0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xF8, 0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x00, 0x01, 0x03, 0x03, 0x03,
   0x03, 0x03, 0x01, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01,
   0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, 0x03, 0x03, 0x00, 0x00,
   0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
   0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03,
   0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x1F, 0x0F,
   0x87, 0xC7, 0xF7, 0xFF, 0xFF, 0x1F, 0x1F, 0x3D, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0x7C, 0x7D, 0xFF,
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x0F, 0x07, 0x00, 0x30, 0x30, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC0, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0xC0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x3F, 0x1F,
   0x0F, 0x07, 0x1F, 0x7F, 0xFF, 0xFF, 0xF8, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xF8, 0xE0,
   0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00,
   0x00, 0xFC, 0xFE, 0xFC, 0x0C, 0x06, 0x06, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xF0, 0xF8, 0x1C, 0x0E,
   0x06, 0x06, 0x06, 0x0C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFC,
   0xFE, 0xFC, 0x00, 0x18, 0x3C, 0x7E, 0x66, 0xE6, 0xCE, 0x84, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x06,
   0x06, 0xFC, 0xFE, 0xFC, 0x0C, 0x06, 0x06, 0x06, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00, 0xC0, 0xF8,
   0xFC, 0x4E, 0x46, 0x46, 0x46, 0x4E, 0x7C, 0x78, 0x40, 0x18, 0x3C, 0x76, 0xE6, 0xCE, 0xCC, 0x80,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x0F, 0x1F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x0F, 0x03,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00,
   0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x03, 0x07, 0x0E, 0x0C,
   0x18, 0x18, 0x0C, 0x06, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x01, 0x0F, 0x0E, 0x0C, 0x18, 0x0C, 0x0F,
   0x07, 0x01, 0x00, 0x04, 0x0E, 0x0C, 0x18, 0x0C, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00,
   0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x07,
   0x07, 0x0C, 0x0C, 0x18, 0x1C, 0x0C, 0x06, 0x06, 0x00, 0x04, 0x0E, 0x0C, 0x18, 0x0C, 0x0F, 0x07,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 };

// the most basic function, set a single pixel
void Grove_SSD1308::drawPixel(int16_t x, int16_t y, uint16_t color) 
 { if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
      return;
   // check rotation, move pixel around if necessary
   switch (getRotation()) 
    { case 1:
         swap(x, y);
         x = WIDTH - x - 1;
         break;
      case 2:
         x = WIDTH - x - 1;
         y = HEIGHT - y - 1;
         break;
      case 3:
         swap(x, y);
         y = HEIGHT - y - 1;
         break;
    }  
   // x is which column
   switch (color) 
    { case WHITE:   buffer[x+ (y/8)*SSD1308_LCDWIDTH] |=  (1 << (y&7)); break;
      case BLACK:   buffer[x+ (y/8)*SSD1308_LCDWIDTH] &= ~(1 << (y&7)); break; 
      case INVERSE: buffer[x+ (y/8)*SSD1308_LCDWIDTH] ^=  (1 << (y&7)); break; 
    }
 }

// initializer for I2C.
Grove_SSD1308::Grove_SSD1308() 
 { constructor(SSD1308_LCDWIDTH, SSD1308_LCDHEIGHT);
 }

void Grove_SSD1308::begin(uint8_t i2caddr) 
 { _i2caddr = 0x3C;
   
   if (i2caddr == 2)
      _i2caddr = 0x3D;
   
   // I2C Init
   Wire.begin();

   // Recommended Software Initialization for LY190-128064
   writeCommand(SSD1308_DISPLAYOFF);            // 0xAE         - display off
   writeCommand(SSD1308_SEGREMAP | 0x1);        // 0xA0 | 0x01  - segment remap
   writeCommand(SSD1308_SETCOMPINS);            // 0xDA         - common pads hardware  
   writeCommand(0x12);                          // parameter (alternative COM pin configuration)
   writeCommand(SSD1308_COMSCANDEC);            // 0xC8         - common output scan direction: com63-com0
   writeCommand(SSD1308_SETMULTIPLEX);          // 0xA8         - multiplexed ration mode:63
   writeCommand(0x3F);                          // parameter 
   writeCommand(SSD1308_SETDISPLAYCLOCKDIV);    // 0xD5         - display divide ratio/osc freq. mode
   writeCommand(0x80);                          // parameter (the suggested ratio 0x80)
   writeCommand(SSD1308_SETCONTRAST);           // 0x81         - contrast control
   writeCommand(0x50);                          // parameter   
   writeCommand(SSD1308_SETPRECHARGE);          // 0xD9         - set pre-charge period
   writeCommand(0x21);                          // parameter
   writeCommand(SSD1308_MEMORYMODE);            // 0x20         - set Memory Addressing Mode
   writeCommand(0x00);                          // parameter (0x00 for KS0108 compatibility, Adafruit_GFX needs this mode selected)
   writeCommand(SSD1308_SETVCOMDETECT);         // 0xDB         - VCOM deselect level mode
   writeCommand(0x30);                          // parameter
   writeCommand(SSD1308_SETEXTINT);             // 0xAD         - Master configuration
   writeCommand(0x00);                          // parameter       
   writeCommand(SSD1308_DISPLAYALLON_RESUME);   // 0xA4         - out follows RAM content
   writeCommand(SSD1308_NORMALDISPLAY);         // 0xA6         - set normal display
   writeCommand(SSD1308_DISPLAYON);             // 0xAF         - display on
 }

void Grove_SSD1308::invertDisplay(uint8_t i) 
 { if (i) 
      writeCommand(SSD1308_INVERTDISPLAY);
   else
      writeCommand(SSD1308_NORMALDISPLAY);
 }

void Grove_SSD1308::writeCommand(uint8_t c) 
 { // I2C
   uint8_t control = 0x00;   // Co = 0, D/C = 0
   Wire.beginTransmission(_i2caddr);
   WIRE_WRITE(control);
   WIRE_WRITE(c);
   Wire.endTransmission();
 }

// startScrollRight
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F) 
void Grove_SSD1308::startScrollRight(uint8_t start, uint8_t stop)
 { writeCommand(SSD1308_RIGHT_HORIZONTAL_SCROLL);
   writeCommand(0X00);
   writeCommand(start);
   writeCommand(0X00);
   writeCommand(stop);
   writeCommand(0X00);
   writeCommand(0XFF);
   writeCommand(SSD1308_ACTIVATE_SCROLL);
 }

// startScrollLeft
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F) 
void Grove_SSD1308::startScrollLeft(uint8_t start, uint8_t stop)
 { writeCommand(SSD1308_LEFT_HORIZONTAL_SCROLL);
   writeCommand(0X00);
   writeCommand(start);
   writeCommand(0X00);
   writeCommand(stop);
   writeCommand(0X00);
   writeCommand(0XFF);
   writeCommand(SSD1308_ACTIVATE_SCROLL);
 }

// startScrollDiagRight
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F) 
void Grove_SSD1308::startScrollDiagRight(uint8_t start, uint8_t stop)
 { writeCommand(SSD1308_SET_VERTICAL_SCROLL_AREA);  
   writeCommand(0X00);
   writeCommand(SSD1308_LCDHEIGHT);
   writeCommand(SSD1308_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
   writeCommand(0X00);
   writeCommand(start);
   writeCommand(0X00);
   writeCommand(stop);
   writeCommand(0X01);
   writeCommand(SSD1308_ACTIVATE_SCROLL);
}

// startScrollDiagLeft
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F) 
void Grove_SSD1308::startScrollDiagLeft(uint8_t start, uint8_t stop)
 { writeCommand(SSD1308_SET_VERTICAL_SCROLL_AREA);  
   writeCommand(0X00);
   writeCommand(SSD1308_LCDHEIGHT);
   writeCommand(SSD1308_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
   writeCommand(0X00);
   writeCommand(start);
   writeCommand(0X00);
   writeCommand(stop);
   writeCommand(0X01);
   writeCommand(SSD1308_ACTIVATE_SCROLL);
 }

void Grove_SSD1308::stopScroll(void)
 { writeCommand(SSD1308_DEACTIVATE_SCROLL);
 }

// Dim the display
// dim = true: display is dimmed
// dim = false: display is normal
void Grove_SSD1308::dim(boolean dim) 
 { uint8_t contrast;

   if (dim) 
      contrast = 0; // Dimmed display
   else 
      contrast = 0x50;

   // the range of contrast to too small to be really useful
   // it is useful to dim the display
   writeCommand(SSD1308_SETCONTRAST);
   writeCommand(contrast);
 }

void Grove_SSD1308::writeData(uint8_t c) 
 { // I2C
   uint8_t control = 0x40;   // Co = 0, D/C = 1
   Wire.beginTransmission(_i2caddr);
   WIRE_WRITE(control);
   WIRE_WRITE(c);
   Wire.endTransmission();
 }

void Grove_SSD1308::display(void) 
 { writeCommand(SSD1308_COLUMNADDR);
   writeCommand(0);   // Column start address (0 = reset)
   writeCommand(SSD1308_LCDWIDTH-1); // Column end address (127 = reset)

   writeCommand(SSD1308_PAGEADDR);
   writeCommand(0); // Page start address (0 = reset)
   writeCommand(7); // Page end address

   // I2C
   for (uint16_t i=0; i<(SSD1308_LCDWIDTH*SSD1308_LCDHEIGHT/8); i++) 
    { // send a bunch of data in one xmission
      Wire.beginTransmission(_i2caddr);
      WIRE_WRITE(0x40);
      for (uint8_t x=0; x<16; x++) 
       { WIRE_WRITE(buffer[i]);
         i++;
       }
      i--;
      Wire.endTransmission();
    }
 }

// clear everything
void Grove_SSD1308::clearDisplay(void) 
 { memset(buffer, 0, (SSD1308_LCDWIDTH*SSD1308_LCDHEIGHT/8));
 }

void Grove_SSD1308::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) 
 { boolean bSwap = false;
   switch(rotation) 
    { case 0:
         // 0 degree rotation, do nothing
         break;
      case 1:
         // 90 degree rotation, swap x & y for rotation, then invert x
         bSwap = true;
         swap(x, y);
         x = WIDTH - x - 1;
         break;
      case 2:
         // 180 degree rotation, invert x and y - then shift y around for height.
         x = WIDTH - x - 1;
         y = HEIGHT - y - 1;
         x -= (w-1);
         break;
      case 3:
         // 270 degree rotation, swap x & y for rotation, then invert y  and adjust y for w (not to become h)
         bSwap = true;
         swap(x, y);
         y = HEIGHT - y - 1;
         y -= (w-1);
         break;
    }

   if (bSwap) 
      drawFastVLineInternal(x, y, w, color);
   else 
      drawFastHLineInternal(x, y, w, color);
 }

void Grove_SSD1308::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) 
 { bool bSwap = false;
   switch(rotation) 
    { case 0:
         break;
      case 1:
         // 90 degree rotation, swap x & y for rotation, then invert x and adjust x for h (now to become w)
         bSwap = true;
         swap(x, y);
         x = WIDTH - x - 1;
         x -= (h-1);
         break;
      case 2:
         // 180 degree rotation, invert x and y - then shift y around for height.
         x = WIDTH - x - 1;
         y = HEIGHT - y - 1;
         y -= (h-1);
         break;
      case 3:
         // 270 degree rotation, swap x & y for rotation, then invert y 
         bSwap = true;
         swap(x, y);
         y = HEIGHT - y - 1;
         break;
    }
   if(bSwap)
      drawFastHLineInternal(x, y, h, color);
   else
      drawFastVLineInternal(x, y, h, color);
 }


// Private functions:
void Grove_SSD1308::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) 
 { // do nothing if we're off the left or right side of the screen
   if(x < 0 || x >= WIDTH)
      return;

   // make sure we don't try to draw below 0
   if(__y < 0) 
    { // __y is negative, this will subtract enough from __h to account for __y being 0
      __h += __y;
      __y = 0;
    } 

   // make sure we don't go past the height of the display
   if( (__y + __h) > HEIGHT)
      __h = (HEIGHT - __y);

   // if our height is now negative, punt 
   if(__h <= 0)
      return;

   // this display doesn't need ints for coordinates, use local byte registers for faster juggling
   register uint8_t y = __y;
   register uint8_t h = __h;


   // set up the pointer for fast movement through the buffer
   register uint8_t *pBuf = buffer;
   // adjust the buffer pointer for the current row
   pBuf += ((y/8) * SSD1308_LCDWIDTH);
   // and offset x columns in
   pBuf += x;

   // do the first partial byte, if necessary - this requires some masking
   register uint8_t mod = (y&7);
   if(mod) 
    { // mask off the high n bits we want to set 
      mod = 8-mod;

      // note - lookup table results in a nearly 10% performance improvement in fill* functions
      // register uint8_t mask = ~(0xFF >> (mod));
      static uint8_t premask[8] = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };
      register uint8_t mask = premask[mod];

      // adjust the mask if we're not going to reach the end of this byte
      if (h < mod) 
         mask &= (0XFF >> (mod-h));
      switch (color) 
       { case WHITE:   *pBuf |=  mask;  break;
         case BLACK:   *pBuf &= ~mask;  break;
         case INVERSE: *pBuf ^=  mask;  break;
       }
     
      // fast exit if we're done here!
      if(h<mod)
         return;

      h -= mod;
      pBuf += SSD1308_LCDWIDTH;
    }
   // write solid bytes while we can - effectively doing 8 rows at a time
   if(h >= 8) 
    { if (color == INVERSE)  
       { // separate copy of the code so we don't impact performance of the black/white write version with an extra comparison per loop
         do  
          { *pBuf=~(*pBuf);
            // adjust the buffer forward 8 rows worth of data
            pBuf += SSD1308_LCDWIDTH;

            // adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
            h -= 8;
          } while(h >= 8);
       }
      else 
       { // store a local value to work with 
         register uint8_t val = (color == WHITE) ? 255 : 0;
         do  
          { // write our value in
            *pBuf = val;
            // adjust the buffer forward 8 rows worth of data
            pBuf += SSD1308_LCDWIDTH;

            // adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
            h -= 8;
          } while(h >= 8);
       }
    }

   // now do the final partial byte, if necessary
   if(h) 
    { mod = h & 7;
      // this time we want to mask the low bits of the byte, vs the high bits we did above
      // register uint8_t mask = (1 << mod) - 1;
      // note - lookup table results in a nearly 10% performance improvement in fill* functions
      static uint8_t postmask[8] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F };
      register uint8_t mask = postmask[mod];
      switch (color) 
       { case WHITE:   *pBuf |=  mask;  break;
         case BLACK:   *pBuf &= ~mask;  break;
         case INVERSE: *pBuf ^=  mask;  break;
       }
    }
 }

void Grove_SSD1308::drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) 
 { // Do bounds/limit checks
   if(y < 0 || y >= HEIGHT) 
      return;
   
   // make sure we don't try to draw below 0
   if(x < 0)
    { w += x;
      x = 0;
    }

   // make sure we don't go off the edge of the display
   if( (x + w) > WIDTH) 
    { w = (WIDTH - x);
    }

   // if our width is now negative, punt
   if(w <= 0) 
      return;

   // set up the pointer for  movement through the buffer
   register uint8_t *pBuf = buffer;
   // adjust the buffer pointer for the current row
   pBuf += ((y/8) * SSD1308_LCDWIDTH);
   // and offset x columns in
   pBuf += x;

   register uint8_t mask = 1 << (y&7);

   switch (color) 
    { case WHITE:                while(w--) { *pBuf++ |= mask; }; break;
      case BLACK: mask = ~mask;  while(w--) { *pBuf++ &= mask; }; break;
      case INVERSE:              while(w--) { *pBuf++ ^= mask; }; break;
    }
 }
