/*
 * lcd128x64.c:
 *	Graphics-based LCD driver.
 *	This is designed to drive the parallel interface LCD drivers
 *	based on the generic 12864H chips
 *
 *	There are many variations on these chips, however they all mostly
 *	seem to be similar.
 *	This implementation has the Pins from the Pi hard-wired into it,
 *	in particular wiringPi pins 0-7 so that we can use
 *	digitalWriteByete() to speed things up somewhat.
 *
 * Copyright (c) 2013 Gordon Henderson.
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://projects.drogon.net/raspberry-pi/wiringpi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with wiringPi.  If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

#include <stdio.h>
#include <stdlib.h>

#include <wiringPi.h>

#include "font.h"
#include "lcd128x64.h"

// Size

#define	LCD_WIDTH	128
#define	LCD_HEIGHT	 64

// Hardware Pins
//	Note pins 0-7 are the 8-bit data port

#define	CS1		10
#define	CS2		11
#define	STROBE		12
#define	RS		13

// Software copy of the framebuffer
//	it's 8-bit deep although the display itself is only 1-bit deep.

static unsigned char frameBuffer [LCD_WIDTH * LCD_HEIGHT] ;

static int maxX,    maxY ;
static int lastX,   lastY ;
static int xOrigin, yOrigin ;
static int lcdOrientation = 0 ;

/*
 * strobe:
 *	Toggle the strobe (Really the "E") pin to the device.
 *	According to the docs, data is latched on the falling edge.
 *********************************************************************************
 */

static void strobe (void)
{
  digitalWrite (STROBE, 1) ; delayMicroseconds (1) ;
  digitalWrite (STROBE, 0) ; delayMicroseconds (5) ;
}


/*
 * sentData:
 *	Send an data or command byte to the display.
 *********************************************************************************
 */

static void sendData (const int data, const int chip)
{
  digitalWrite     (chip, 0) ;
  digitalWriteByte (data) ;
  strobe           () ;
  digitalWrite     (chip, 1) ;
}


/*
 * sendCommand:
 *	Send a command byte to the display
 *********************************************************************************
 */

static void sendCommand (const int command, const int chip)
{
  digitalWrite (RS, 0) ;
  sendData     (command, chip) ;
  digitalWrite (RS, 1) ;
}


/*
 * setCol: SetLine:
 *	Set the column and line addresses
 *********************************************************************************
 */

static void setCol  (int col, const int chip)
  { sendCommand (0x40 | (col  & 0x3F), chip) ; }

static void setLine (int line, const int chip)
  { sendCommand (0xB8 | (line & 0x07), chip) ; }


/*
 * lcd128x64update:
 *	Copy our software version to the real display
 *********************************************************************************
 */

void lcd128x64update (void)
{
  int line, x, y, fbLoc ;
  unsigned char byte ;

// Left side 

  for (line = 0 ; line < 8 ; ++line)
  {
    setCol  (0,    CS1) ;
    setLine (line, CS1) ;

    for (x = 63 ; x >= 0 ; --x)
    {
      byte = 0 ;
      for (y = 0 ; y < 8 ; ++y)
      {
	fbLoc = x + (((7 - line) * 8) + (7 - y)) * LCD_WIDTH ;
	if (frameBuffer [fbLoc] != 0)
	  byte |= (1 << y) ;
      }
      sendData (byte, CS1) ;
    }
  }

// Right side 

  for (line = 0 ; line < 8 ; ++line)
  {
    setCol  (0,    CS2) ;
    setLine (line, CS2) ;

    for (x = 127 ; x >= 64 ; --x)
    {
      byte = 0 ;
      for (y = 0 ; y < 8 ; ++y)
      {
	fbLoc = x + (((7 - line) * 8) + (7 - y)) * LCD_WIDTH ;
	if (frameBuffer [fbLoc] != 0)
	  byte |= (1 << y) ;
      }
      sendData (byte, CS2) ;
    }
  }
}


/*
 * lcd128x64setOrigin:
 *	Set the display offset origin
 *********************************************************************************
 */

void lcd128x64setOrigin (int x, int y)
{
  xOrigin = x ;
  yOrigin = y ;
}


/*
 * lcd128x64setOrientation:
 *	Set the display orientation:
 *	0: Normal, the display is portrait mode, 0,0 is top left
 *	1: Landscape
 *	2: Portrait, flipped
 *	3: Landscape, flipped
 *********************************************************************************
 */

void lcd128x64setOrientation (int orientation)
{
  lcdOrientation = orientation & 3 ;

  lcd128x64setOrigin (0,0) ;

  switch (lcdOrientation)
  {
    case 0:
      maxX = LCD_WIDTH ;
      maxY = LCD_HEIGHT ;
      break ;

    case 1:
      maxX = LCD_HEIGHT ;
      maxY = LCD_WIDTH ;
      break ;

    case 2:
      maxX = LCD_WIDTH ;
      maxY = LCD_HEIGHT ;
      break ;

    case 3:
      maxX = LCD_HEIGHT ;
      maxY = LCD_WIDTH ;
      break ;
  }
}


/*
 * lcd128x64orientCoordinates:
 *	Adjust the coordinates given to the display orientation
 *********************************************************************************
 */

void lcd128x64orientCoordinates (int *x, int *y)
{
  register int tmp ;

  *x += xOrigin ;
  *y += yOrigin ;
  *y  = maxY - *y - 1 ;

  switch (lcdOrientation)
  {
    case 0:
      break;

    case 1:
      tmp = maxY - *y - 1 ;
      *y = *x ;
      *x = tmp ;
      break;

    case 2:
      *x = maxX - *x - 1 ;
      *y = maxY - *y - 1 ;
      break;

    case 3:
      *x = maxX - *x - 1 ;
      tmp = *y ;
      *y = *x ;
      *x = tmp ;
      break ;
  }
}


/*
 * lcd128x64getScreenSize:
 *	Return the max X & Y screen sizes. Needs to be called again, if you 
 *	change screen orientation.
 *********************************************************************************
 */

void lcd128x64getScreenSize (int *x, int *y)
{
  *x = maxX ;
  *y = maxY ;
}


/*
 *********************************************************************************
 * Standard Graphical Functions
 *********************************************************************************
 */


/*
 * lcd128x64point:
 *	Plot a pixel.
 *********************************************************************************
 */

void lcd128x64point (int x, int y, int colour)
{
  lastX = x ;
  lastY = y ;

  lcd128x64orientCoordinates (&x, &y) ;

  if ((x < 0) || (x >= LCD_WIDTH) || (y < 0) || (y >= LCD_HEIGHT))
    return ;

  frameBuffer [x + y * LCD_WIDTH] = colour ;
}


/*
 * lcd128x64line: lcd128x64lineTo:
 *	Classic Bressenham Line code
 *********************************************************************************
 */

void lcd128x64line (int x0, int y0, int x1, int y1, int colour)
{
  int dx, dy ;
  int sx, sy ;
  int err, e2 ;

  lastX = x1 ;
  lastY = y1 ;

  dx = abs (x1 - x0) ;
  dy = abs (y1 - y0) ;

  sx = (x0 < x1) ? 1 : -1 ;
  sy = (y0 < y1) ? 1 : -1 ;

  err = dx - dy ;
 
  for (;;)
  {
    lcd128x64point (x0, y0, colour) ;

    if ((x0 == x1) && (y0 == y1))
      break ;

    e2 = 2 * err ;

    if (e2 > -dy)
    {
      err -= dy ;
      x0  += sx ;
    }

    if (e2 < dx)
    {
      err += dx ;
      y0  += sy ;
    }
  }

}

void lcd128x64lineTo (int x, int y, int colour)
{
  lcd128x64line (lastX, lastY, x, y, colour) ;
}


/*
 * lcd128x64rectangle:
 *	A rectangle is a spoilt days fishing
 *********************************************************************************
 */

void lcd128x64rectangle (int x1, int y1, int x2, int y2, int colour, int filled)
{
  register int x ;

  if (filled)
  {
    /**/ if (x1 == x2)
      lcd128x64line (x1, y1, x2, y2, colour) ;
    else if (x1 < x2)
      for (x = x1 ; x <= x2 ; ++x)
	lcd128x64line (x, y1, x, y2, colour) ;
    else
      for (x = x2 ; x <= x1 ; ++x)
	lcd128x64line (x, y1, x, y2, colour) ;
  }
  else
  {
    lcd128x64line   (x1, y1, x2, y1, colour) ;
    lcd128x64lineTo (x2, y2, colour) ;
    lcd128x64lineTo (x1, y2, colour) ;
    lcd128x64lineTo (x1, y1, colour) ;
  }
}


/*
 * lcd128x64circle:
 *      This is the midpoint circle algorithm.
 *********************************************************************************
 */

void lcd128x64circle (int x, int y, int r, int colour, int filled)
{
  int ddF_x = 1 ;
  int ddF_y = -2 * r ;

  int f = 1 - r ;
  int x1 = 0 ;
  int y1 = r ;

  if (filled)
  {
    lcd128x64line (x, y + r, x, y - r, colour) ;
    lcd128x64line (x + r, y, x - r, y, colour) ;
  }
  else
  {
    lcd128x64point (x, y + r, colour) ;
    lcd128x64point (x, y - r, colour) ;
    lcd128x64point (x + r, y, colour) ;
    lcd128x64point (x - r, y, colour) ;
  }

  while (x1 < y1)
  {
    if (f >= 0)
    {
      y1-- ;
      ddF_y += 2 ;
      f += ddF_y ;
    }
    x1++ ;
    ddF_x += 2 ;
    f += ddF_x ;
    if (filled)
    {
      lcd128x64line (x + x1, y + y1, x - x1, y + y1, colour) ;
      lcd128x64line (x + x1, y - y1, x - x1, y - y1, colour) ;
      lcd128x64line (x + y1, y + x1, x - y1, y + x1, colour) ;
      lcd128x64line (x + y1, y - x1, x - y1, y - x1, colour) ;
    }
    else
    {
      lcd128x64point (x + x1, y + y1, colour) ; lcd128x64point (x - x1, y + y1, colour) ;
      lcd128x64point (x + x1, y - y1, colour) ; lcd128x64point (x - x1, y - y1, colour) ;
      lcd128x64point (x + y1, y + x1, colour) ; lcd128x64point (x - y1, y + x1, colour) ;
      lcd128x64point (x + y1, y - x1, colour) ; lcd128x64point (x - y1, y - x1, colour) ;
    }
  }
}


/*
 * lcd128x64ellipse:
 *	Fast ellipse drawing algorithm by 
 *      John Kennedy
 *	Mathematics Department
 *	Santa Monica College
 *	1900 Pico Blvd.
 *	Santa Monica, CA 90405
 *	jrkennedy6@gmail.com
 *	-Confirned in email this algorithm is in the public domain -GH-
 *********************************************************************************
 */

static void plot4ellipsePoints (int cx, int cy, int x, int y, int colour, int filled)
{
  if (filled)
  {
    lcd128x64line (cx + x, cy + y, cx - x, cy + y, colour) ;
    lcd128x64line (cx - x, cy - y, cx + x, cy - y, colour) ;
  }
  else
  {
    lcd128x64point (cx + x, cy + y, colour) ;
    lcd128x64point (cx - x, cy + y, colour) ;
    lcd128x64point (cx - x, cy - y, colour) ;
    lcd128x64point (cx + x, cy - y, colour) ;
  }
}

void lcd128x64ellipse (int cx, int cy, int xRadius, int yRadius, int colour, int filled)
{
  int x, y ;
  int xChange, yChange, ellipseError ;
  int twoAsquare, twoBsquare ;
  int stoppingX, stoppingY ;

  twoAsquare = 2 * xRadius * xRadius ;
  twoBsquare = 2 * yRadius * yRadius ;

  x = xRadius ;
  y = 0 ;

  xChange = yRadius * yRadius * (1 - 2 * xRadius) ;
  yChange = xRadius * xRadius ;

  ellipseError = 0 ;
  stoppingX    = twoBsquare * xRadius ;
  stoppingY    = 0 ;

  while (stoppingX >= stoppingY)	// 1st set of points
  {
    plot4ellipsePoints (cx, cy, x, y, colour, filled) ;
    ++y ;
    stoppingY    += twoAsquare ;
    ellipseError += yChange ;
    yChange      += twoAsquare ;

    if ((2 * ellipseError + xChange) > 0 )
    {
      --x ;
      stoppingX    -= twoBsquare ;
      ellipseError += xChange ;
      xChange      += twoBsquare ;
    }
  }

  x = 0 ;
  y = yRadius ;

  xChange = yRadius * yRadius ;
  yChange = xRadius * xRadius * (1 - 2 * yRadius) ;

  ellipseError = 0 ;
  stoppingX    = 0 ;
  stoppingY    = twoAsquare * yRadius ;

  while (stoppingX <= stoppingY)	//2nd set of points
  {
    plot4ellipsePoints (cx, cy, x, y, colour, filled) ;
    ++x ;
    stoppingX    += twoBsquare ;
    ellipseError += xChange ;
    xChange      += twoBsquare ;

    if ((2 * ellipseError + yChange) > 0 )
    {
      --y ;
      stoppingY -= twoAsquare ;
      ellipseError += yChange ;
      yChange += twoAsquare ;
    }
  }
}


/*
 * lcd128x64putchar:
 *	Print a single character to the screen
 *********************************************************************************
 */

void lcd128x64putchar (int x, int y, int c, int bgCol, int fgCol)
{
  int y1, y2 ;

  unsigned char line ;
  unsigned char *fontPtr ;

// Can't print if we're offscreen

//if ((x < 0) || (x >= (maxX - fontWidth)) || (y < 0) || (y >= (maxY - fontHeight)))
//  return ;

  fontPtr = font + c * fontHeight ;

  for (y1 = fontHeight - 1 ; y1 >= 0 ; --y1)
  {
    y2 = y + y1 ;
    line = *fontPtr++ ;
    lcd128x64point (x + 0, y2, (line & 0x80) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 1, y2, (line & 0x40) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 2, y2, (line & 0x20) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 3, y2, (line & 0x10) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 4, y2, (line & 0x08) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 5, y2, (line & 0x04) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 6, y2, (line & 0x02) == 0 ? bgCol : fgCol) ;
    lcd128x64point (x + 7, y2, (line & 0x01) == 0 ? bgCol : fgCol) ;
  }
}


/*
 * lcd128x64puts:
 *	Send a string to the display. Obeys \n and \r formatting
 *********************************************************************************
 */

void lcd128x64puts (int x, int y, const char *str, int bgCol, int fgCol)
{
  int c, mx, my ;

  mx = x ; my = y ;

  while (*str)
  {
    c = *str++ ;

    if (c == '\r')
    {
      mx = x ;
      continue ;
    }

    if (c == '\n')
    {
      mx  = x ;
      my -= fontHeight ;
      continue ;
    }

    lcd128x64putchar (mx, my, c, bgCol, fgCol) ;

    mx += fontWidth ;
    if (mx >= (maxX - fontWidth))
    {
      mx  = 0 ;
      my -= fontHeight ;
    }
  }
}


/*
 * lcd128x64clear:
 *	Clear the display to the given colour.
 *********************************************************************************
 */

void lcd128x64clear (int colour)
{
  register int i ;
  register unsigned char *ptr = frameBuffer ;

  for (i = 0 ; i < (maxX * maxY) ; ++i)
    *ptr++ = colour ;
}




/*
 * lcd128x64setup:
 *	Initialise the display and GPIO.
 *********************************************************************************
 */

int lcd128x64setup (void)
{
  int i ;

  for (i = 0 ; i < 8 ; ++i)
    pinMode (i, OUTPUT) ;

  digitalWrite (CS1,    1) ;
  digitalWrite (CS2,    1) ;
  digitalWrite (STROBE, 0) ;
  digitalWrite (RS,     1) ;

  pinMode (CS1,    OUTPUT) ;
  pinMode (CS2,    OUTPUT) ;
  pinMode (STROBE, OUTPUT) ;
  pinMode (RS,     OUTPUT) ;

  sendCommand (0x3F, CS1) ;	// Display ON
  sendCommand (0xC0, CS1) ;	// Set display start line to 0

  sendCommand (0x3F, CS2) ;	// Display ON
  sendCommand (0xC0, CS2) ;	// Set display start line to 0

  lcd128x64clear          (0) ;
  lcd128x64setOrientation (0) ;
  lcd128x64update         () ;

  return 0 ;
}
