#include "SegmentDisplayConfig.h"
#include "SegmentDisplay.h"


void SegmentDisplay::begin()
{
  pinMode(latchPin, OUTPUT);

  #if !defined(SDUseSPI)
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  digitalWrite(clockPin, LOW);
  digitalWrite(dataPin, LOW);
  #endif

  writeDisplay(0, 0);

  digitalWrite(latchPin, HIGH);
  digitalWrite(latchPin, LOW);
  digitalWrite(latchPin, HIGH);

  #if defined(SDUseSPI)
  SPI.begin();
  #endif

  #if defined(SDSerialDebug)
  Serial.begin(9600);
  Serial.print("SegmentDisplay::begin\n");
  #endif
}


void SegmentDisplay::end()
{
  #if defined(SDUseSPI)
  SPI.end();
  #endif
}


void SegmentDisplay::writeNumber(uint32_t value)
{
  #if defined(SDSerialDebug)
  String textValue = String(value);
  Serial.print("SegmentDisplay::writeNumber: " + textValue + "\n");
  #endif

  byte digitMask = 1;

  for (byte digit = digits; digit > 0; digit--)
  {
    writeDigit((byte)(value % 10), digitMask);

    value /= 10;
    digitMask <<= 1;
  }
}


void SegmentDisplay::writeTextLeft(char* value)
{
  #if defined(SDSerialDebug)
  Serial.print("SegmentDisplay::writeTextLeft: " + value + "\n");
  #endif

  byte digitMask = 1 << (digits - 1);
  byte charIndex = 0;

  while (charIndex < digits && value[charIndex] != '\0')
  {
    writeChar(value[charIndex], digitMask);

    digitMask >>= 1;
    charIndex++;
  }

  while (charIndex < digits)
  {
    writeDisplay(0, digitMask);
    digitMask >>= 1;
    charIndex++;
  }
}


void SegmentDisplay::writeTextCenter(char* value)
{
}


void SegmentDisplay::writeTextRight(char* value)
{
}


inline void SegmentDisplay::writeDigit(byte value, byte digitMask)
{
  writeDisplay(pgm_read_byte_near(SDNumberSegments + value), digitMask);
}


inline void SegmentDisplay::writeChar(char value, byte digitMask)
{
  // Lowercase
  if (value >= ASCIILowercaseA && value <= ASCIILowercaseZ)
    writeDisplay(pgm_read_byte_near(SDCharacterSegments + (value - ASCIILowercaseA)), digitMask);

  // Uppercase
  else if  (value >= ASCIIUppercaseA && value <= ASCIIUppercaseZ)
    writeDisplay(pgm_read_byte_near(SDCharacterSegments + (value - ASCIIUppercaseA)), digitMask);

  // Numbers
  else if (value >= ASCIIZero && value <= ASCIINine)
    writeDisplay(pgm_read_byte_near(SDNumberSegments + (value - ASCIIZero)), digitMask);

  // Space / unknown
  else
    writeDisplay(0, digitMask);
}


inline void SegmentDisplay::writeDisplay(byte segmentMask, byte digitMask)
{
  // If no segments should be lit, clear the digit mask as well to prevent
  // power leaking through (it's probably an issue in my circuit or with the
  // specific displays I used, but it still makes sense to close both sides).
  //
  // Still push out the zeros though, if we don't the display will vary
  // in brightness depending on what's on it.
  if (segmentMask == 0)
    digitMask = 0;

  #if defined(SDSerialDebug)
  String segmentValue = String(segmentMask);
  String digitValue = String(digitMask);

  Serial.print("SegmentDisplay::writeDisplay: " + segmentValue + " " + digitValue + "\n");
  #endif

  #if defined(SDUseSPI)
    SPI.beginTransaction(SPISettings(clockSpeed, MSBFIRST, SPI_MODE0));
    #if defined(SDPushSegmentsFirst)
    SPI.transfer(segmentMask);
    SPI.transfer(digitMask);
    #else
    SPI.transfer(digitMask);
    SPI.transfer(segmentMask);
    #endif
    SPI.endTransaction();
  #else
    #if defined(SDPushSegmentsFirst)
    shiftOut(dataPin, clockPin, MSBFIRST, segmentMask);
    shiftOut(dataPin, clockPin, MSBFIRST, digitMask);
    #else
    shiftOut(dataPin, clockPin, MSBFIRST, digitMask);
    shiftOut(dataPin, clockPin, MSBFIRST, segmentMask);
    #endif

    digitalWrite(latchPin, HIGH);
    digitalWrite(latchPin, LOW);
  #endif

  #if defined(SPHandleLargeDelays)
  if (digitDelayMicroseconds > 15000)
    delay(digitDelayMicroseconds / 1000);
  else
  #endif
    delayMicroseconds(digitDelayMicroseconds);
}
