
/*
 * displayclass.cpp
 *
 *  Created on: 31 Oct 2020
 *      Author: mike
 */

#include "libOLED/displayclass.H"

#include <cstring>
#include <cstdlib>
namespace
{
  uint8_t const SSD1306_SETCONTRAST = 0x81;
  uint8_t const SSD1306_DISPLAYALLON_RESUME = 0xA4;
  uint8_t const SSD1306_DISPLAYALLON = 0xA5;
  uint8_t const SSD1306_NORMALDISPLAY = 0xA6;
  uint8_t const SSD1306_INVERTDISPLAY = 0xA7;
  uint8_t const SSD1306_DISPLAYOFF = 0xAE;
  uint8_t const SSD1306_DISPLAYON = 0xAF;

  uint8_t const SSD1306_SETDISPLAYOFFSET = 0xD3;
  uint8_t const SSD1306_SETCOMPINS = 0xDA;

  uint8_t const SSD1306_SETVCOMDETECT = 0xDB;

  uint8_t const SSD1306_SETDISPLAYCLOCKDIV = 0xD5;
  uint8_t const SSD1306_SETPRECHARGE = 0xD9;

  uint8_t const SSD1306_SETMULTIPLEX = 0xA8;

  uint8_t const SSD1306_SETLOWCOLUMN = 0x00;
  uint8_t const SSD1306_SETHIGHCOLUMN = 0x10;

  uint8_t const SSD1306_SETSTARTLINE = 0x40;

  uint8_t const SSD1306_MEMORYMODE = 0x20;
  uint8_t const SSD1306_COLUMNADDR = 0x21;
  uint8_t const SSD1306_PAGEADDR = 0x22;

  uint8_t const SSD1306_COMSCANINC = 0xC0;
  uint8_t const SSD1306_COMSCANDEC = 0xC8;

  uint8_t const SSD1306_SEGREMAP = 0xA0;

  uint8_t const SSD1306_CHARGEPUMP = 0x8D;

  uint8_t const SSD1306_EXTERNALVCC = 0x1;
  uint8_t const SSD1306_SWITCHCAPVCC = 0x2;

// Scrolling #defines
  uint8_t const SSD1306_ACTIVATE_SCROLL = 0x2F;
  uint8_t const SSD1306_DEACTIVATE_SCROLL = 0x2E;
  uint8_t const SSD1306_SET_VERTICAL_SCROLL_AREA = 0xA3;
  uint8_t const SSD1306_RIGHT_HORIZONTAL_SCROLL = 0x26;
  uint8_t const SSD1306_LEFT_HORIZONTAL_SCROLL = 0x27;
  uint8_t const SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29;
  uint8_t const SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A;

  template<class T>
    void
    swap (T &x, T &y)
    {
      T temp = x;
      x = y;
      y = temp;
    }

  template<class T>
    T
    abs (T x)
    {
      return x < 0 ? -x : x;
    }



}
// provided to allow a destructor to destroy something not deleted
// this is only OK because destructors shouldnt be needed
void operator delete(void * data, unsigned int f)
 {
   (void)data;
   (void)f;
 }

// provided to implement an "error handler" on pure virtual
extern "C" void __cxa_pure_virtual()
{
  while(1);
}

display_t::display_t (int const width, int const height, int const ramwidth,
		      uint8_t *const data) :
    m_width (width), m_height (height), m_ramwidth (ramwidth), m_cursor_x (0), m_cursor_y (
	0), m_rotation (0), m_data (data)
{
}

display_t::~display_t()
{

}

void
display_t::display_t::init ()
{
  uint8_t const vccstate = SSD1306_EXTERNALVCC;

  oledReset ();

  oledSetCD (0);

  // Init sequence for 128x32 or 128x64  OLED module
  oledWrite (SSD1306_DISPLAYOFF); // 0xAE
  oledWrite (SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
  oledWrite (0x80); // the suggested ratio 0x80
  oledWrite (SSD1306_SETMULTIPLEX); // 0xA8
  oledWrite (m_height - 1);
  oledWrite (SSD1306_SETDISPLAYOFFSET); // 0xD3
  oledWrite (0x0); // no offset
  oledWrite (SSD1306_SETSTARTLINE | 0x0); // line #0
  oledWrite (SSD1306_CHARGEPUMP); // 0x8D
  oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x10 : 0x14);
  oledWrite (SSD1306_MEMORYMODE);                    // 0x20
  oledWrite (0x00);                    // 0x0 act like ks0108
  oledWrite (SSD1306_SEGREMAP | 0x1);
  oledWrite (SSD1306_COMSCANDEC);
  oledWrite (SSD1306_SETCOMPINS);                    // 0xDA
  oledWrite (m_height == 32 ? 0x02 : 0x12);
  oledWrite (SSD1306_SETCONTRAST);                    // 0x81
  oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x9F : 0xCF);
  oledWrite (SSD1306_SETPRECHARGE);                    // 0xd9
  oledWrite (vccstate == SSD1306_EXTERNALVCC ? 0x22 : 0xF1);
  oledWrite (SSD1306_SETVCOMDETECT);                 // 0xDB
  oledWrite (0x40);
  oledWrite (SSD1306_DISPLAYALLON_RESUME);                 // 0xA4
  oledWrite (SSD1306_NORMALDISPLAY);                 // 0xA6

  oledWrite (SSD1306_DISPLAYON);             //--turn on oled panel

  clearDisplay ();

}

uint8_t
display_t::getRotation (void)
{
  return m_rotation;
}

int16_t
display_t::width (void)
{
  switch (m_rotation)
    {
    case 0:
      return m_width;
      break;
    case 1:
      return m_width;
      break;
    case 2:
      return m_height;
      break;
    case 3:
      return -m_width;
      break;
    }
  return 0;
}

int16_t
display_t::height (void)
{
  switch (m_rotation)
    {
    case 0:
      return m_height;
      break;
    case 1:
      return m_height;
      break;
    case 2:
      return m_width;
      break;
    case 3:
      return -m_height;
      break;
    }
  return 0;
}

// the most basic function, set a single pixel
inline void
display_t::drawPixel (int16_t x, int16_t y, colour_t color)
{
  if ((x < 0) || (x >= m_width) || (y < 0) || (y >= m_height))
    return;

  // check rotation, move pixel around if necessary
  switch (m_rotation)
    {
    case 1:
      swap (x, y);
      x = m_width - x - 1;
      break;
    case 2:
      x = m_width - x - 1;
      y = m_height - y - 1;
      break;
    case 3:
      swap (x, y);
      y = m_height - y - 1;
      break;
    }

  // x is which column
  switch (color)
    {
    case BLACK:
      m_data[x + (y / 8) * m_width] &= ~(1 << (y & 7));
      break;

    default:
    case WHITE:
      m_data[x + (y / 8) * m_width] |= (1 << (y & 7));
      break;

    case INVERT:
      m_data[x + (y / 8) * m_width] ^= (1 << (y & 7));
      break;
    }
}

void
display_t::invertDisplay (uint8_t i)
{
  oledSetCD (0);
  oledWrite (i ? SSD1306_INVERTDISPLAY : SSD1306_NORMALDISPLAY);
}

// 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
display_t::startscrollright (uint8_t start, uint8_t stop)
{
  oledSetCD (0);
  oledWrite (SSD1306_RIGHT_HORIZONTAL_SCROLL);
  oledWrite (0X00);
  oledWrite (start);
  oledWrite (0X00);
  oledWrite (stop);
  oledWrite (0X00);
  oledWrite (0XFF);
  oledWrite (SSD1306_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
display_t::startscrollleft (uint8_t start, uint8_t stop)
{
  oledSetCD (0);
  oledWrite (SSD1306_LEFT_HORIZONTAL_SCROLL);
  oledWrite (0X00);
  oledWrite (start);
  oledWrite (0X00);
  oledWrite (stop);
  oledWrite (0X00);
  oledWrite (0XFF);
  oledWrite (SSD1306_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
display_t::startscrolldiagright (uint8_t start, uint8_t stop)
{
  oledSetCD (0);
  oledWrite (SSD1306_SET_VERTICAL_SCROLL_AREA);
  oledWrite (0X00);
  oledWrite (m_height);
  oledWrite (SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
  oledWrite (0X00);
  oledWrite (start);
  oledWrite (0X00);
  oledWrite (stop);
  oledWrite (0X01);
  oledWrite (SSD1306_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
display_t::startscrolldiagleft (uint8_t start, uint8_t stop)
{
  oledSetCD (0);
  oledWrite (SSD1306_SET_VERTICAL_SCROLL_AREA);
  oledWrite (0X00);
  oledWrite (m_height);
  oledWrite (SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
  oledWrite (0X00);
  oledWrite (start);
  oledWrite (0X00);
  oledWrite (stop);
  oledWrite (0X01);
  oledWrite (SSD1306_ACTIVATE_SCROLL);
}

void
display_t::stopscroll (void)
{
  oledSetCD (0);
  oledWrite (SSD1306_DEACTIVATE_SCROLL);
}

// Dim the display
// dim = true: display is dimmed
// dim = false: display is normal
void
display_t::dim (uint8_t contrast)
{

  // the range of contrast to too small to be really useful
  // it is useful to dim the display

  oledSetCD (0);
  oledWrite (SSD1306_SETCONTRAST);
  oledWrite (contrast);
}

void
display_t::display (void)
{
  oledSetCD (0);
  // select entire display as window to write into
  oledWrite (SSD1306_COLUMNADDR);
  oledWrite (0);   // Column start address (0 = reset)
  oledWrite (m_ramwidth - 1); // Column end address (127 = reset)

  oledWrite (SSD1306_PAGEADDR);
  oledWrite (0); // Page start address (0 = reset)
  oledWrite ((m_height == 64) ? 7 : 3); // Page end address

  int row;

  int col = m_ramwidth == 132 ? 2 : 0;
  for (row = 0; row < m_height / 8; row++)
    {
      oledSetCD (0);
      // set the cursor to
      oledWrite (0xB0 + row); //set page address
      oledWrite (col & 0xf); //set lower column address
      oledWrite (0x10 | (col >> 4)); //set higher column address

      oledSetCD (1);
      oledWrite (m_data + row * m_width, m_width);

    }

}

// clear everything
void
display_t::clearDisplay (void)
{
  memset (m_data, 0, dataSize (m_width, m_height));
}

/* using Bresenham draw algorithm */
void
display_t::drawLine (int16_t x1, int16_t y1, int16_t x2, int16_t y2,
		     colour_t color)
{
  int16_t x, y, dx, dy,    //deltas
      dx2, dy2,	//scaled deltas
      ix, iy,	//increase rate on the x and y axis
      err;	//the error term
  uint16_t i;		//looping variable

  // identify the first pixel
  x = x1;
  y = y1;

  // difference between starting and ending points
  dx = x2 - x1;
  dy = y2 - y1;

  // calculate direction of the vector and store in ix and iy
  if (dx >= 0)
    ix = 1;

  if (dx < 0)
    {
      ix = -1;
      dx = abs (dx);
    }

  if (dy >= 0)
    iy = 1;

  if (dy < 0)
    {
      iy = -1;
      dy = abs (dy);
    }

  // scale deltas and store in dx2 and dy2
  dx2 = dx * 2;
  dy2 = dy * 2;

// all  variables are set and it's time to enter the main loop.

  if (dx > dy)	// dx is the major axis
    {
      // initialize the error term
      err = dy2 - dx;

      for (i = 0; i <= dx; i++)
	{
	  drawPixel (x, y, color);
	  if (err >= 0)
	    {
	      err -= dx2;
	      y += iy;
	    }
	  err += dy2;
	  x += ix;
	}
    }

  else 		// dy is the major axis
    {
      // initialize the error term
      err = dx2 - dy;

      for (i = 0; i <= dy; i++)
	{
	  drawPixel (x, y, color);
	  if (err >= 0)
	    {
	      err -= dy2;
	      x += ix;
	    }
	  err += dx2;
	  y += iy;
	}
    }
}

void
printString (font_t &font, char *string, int length, colour_t colour)
{
  (void) font;
  (void) string;
  (void) length;
  (void) colour;
}

void
printChar (font_t &font, char c, colour_t colour)
{
  (void) font;
  (void) c;
  (void) colour;
}