//
// 14-Segment Red LED Display LTM-8647AP by LITE-ON
//
//   AAAAAAA    AAAAAAA
//  FK  M  NB  FK  M  NB
//  F K M N B  F K M N B
//  F  KMN  B  F  KMN  B
//   GGG HHH   GGG HHH
//  E  TSR  C  E  TSR  C
//  E T S R C  E T S R C
//  ET  S  RC  ET  S  RC
//   DDDDDDD dp DDDDDDD  dp
//
//   Digit 1    Digit 2
//
// Data sequence:
//    digit:  222222222222221111111111111112
//  segment: 1ABCDEFGHKMNRSTABCDEFGHKMNRSTddxx000
// curDisplay ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  The leading 1 and trailing 00 are not included.
#include "stdio.h"
#include "display_driver.h"

const int LED_CLOCK_PIN  = 3;      // 14-Seg Clock bus
const int LED_DATA_PIN   = 4;      // 14-Seg Data bus
const int buttonPin      = 5;      // Hard to get to push button on buzzer
const int IRPin          = 6;      // Infered detector
const int buzzerPin      = 7;      // Buzzer pin (HIGH = on)
const int LED_ENABLE_PIN = 8;      // Enable a 14-seg chip after address lines are set
const int LED_ADDR0_PIN  = 14;     // Address line 0
const int LED_ADDR1_PIN  = 15;     // Address line 1
const int LED_ADDR2_PIN  = 16;     // Address line 2
const int LED_ADDR3_PIN  = 17;     // Address line 3


//
// Character Font Table.
//
static int  fontTbl[224] = {
  /* */0x0000, /*!*/0x3001, /*"*/0x2020, /*#*/0x07A4,
  /*$*/0x5BA4, /*%*/0x13DA, /*&*/0x4D68, /*'*/0x0010,
  /*(*/0x0018, /*)*/0x0042, /***/0x01FE, /*+*/0x01A4,
  /*,*/0x0002, /*-*/0x0180, /*.*/0x0001, /*/*/0x0012,
  /*0*/0x7E12, /*1*/0x3010, /*2*/0x6D80, /*3*/0x7880,
  /*4*/0x3380, /*5*/0x4B08, /*6*/0x5F80, /*7*/0x7200,
  /*8*/0x7F80, /*9*/0x7380, /*:*/0x0024, /*;*/0x0022,
  /*<*/0x0118, /*=*/0x0980, /*>*/0x00C2, /*?*/0x6085,
  /*@*/0x6E90, /*A*/0x7780, /*B*/0x78A4, /*C*/0x4E00,
  /*D*/0x7824, /*E*/0x4F00, /*F*/0x4700, /*G*/0x5E80,
  /*H*/0x3780, /*I*/0x4824, /*J*/0x3C00, /*K*/0x0718,
  /*L*/0x0E00, /*M*/0x3650, /*N*/0x3648, /*O*/0x7E00,
  /*P*/0x6780, /*Q*/0x7E08, /*R*/0x6788, /*S*/0x5B80,
  /*T*/0x4024, /*U*/0x3E00, /*V*/0x0612, /*W*/0x360A,
  /*X*/0x005A, /*Y*/0x2384, /*Z*/0x4812, /*[*/0x3098,
  /*\*/0x0048, /*]*/0x0742, /*^*/0x0240, /*_*/0x0800,
  /*`*/0x0040, /*a*/0x0D44, /*b*/0x0F04, /*c*/0x0D00,
  /*d*/0x0D24, /*e*/0x0D02, /*f*/0x40A4, /*g*/0x4B24,
  /*h*/0x0704, /*i*/0x0004, /*j*/0x0422, /*k*/0x003C,
  /*l*/0x0024, /*m*/0x1584, /*n*/0x0504, /*o*/0x1D80,
  /*p*/0x4720, /*q*/0x4324, /*r*/0x0500, /*s*/0x4B04,
  /*t*/0x01A4, /*u*/0x0C04, /*v*/0x0402, /*w*/0x140A,
  /*x*/0x005A, /*y*/0x0054, /*z*/0x0902, /*{*/0x4942,
  /*|*/0x0600, /*}*/0x4898, /*~*/0x0050, /*¦*/0x7FFE
  };


//
// Constructor
//
DisplayDriver::DisplayDriver()
  {
  cursor        = 0;
  wrapMode      = VSCROLL;
  printMode     = PRINT_MODE_OR;
  msg_ptr       = NULL;
  nextMsg_ptr   = NULL;
  scrollSpeed   = DEFAULT_SCROLL_SPEED;
  waitUntilMillis = 0;

  cls();
  }


//
// Member Functions
//

//
// Set horizontal (right to left) scroll speed in milliseconds per shift.
//
void  DisplayDriver::setHScrollSpeed( int speed )
  {
  scrollSpeed = speed;
  }




//
// Turn all segments off.
//
void DisplayDriver::cls()
  {
  cursor = 0;
  for( int i=0; i<NUM_OF_DISPLAY_CHIPS; i++ )
    nextDisplay[i] = 0;
  }




//
// Virtically Scroll one line. Move line 2 to line 1 and clear line 2.
//
void DisplayDriver::vScroll()
  {
  // Loop thru chips in one line.
  for( int i=0; i<CHIPS_PER_LINE; i++ )
    {
    // Move 2 digits at a time up and clear the bottom chip.
    nextDisplay[i] = curDisplay[i+CHIPS_PER_LINE];
    nextDisplay[i+CHIPS_PER_LINE] = 0;
    }
  cursor -= CHAR_PER_LINE;
  }




//
// Horizontally scroll one line (right to left). 
// line = TOP_LINE (0) or BOTTOM_LINE (1)
//
void DisplayDriver::hScoll( int  line )
  {
  // Scroll 9 digits to the left and clear the 10th
  int   firstChip = line * CHIPS_PER_LINE;

  // Loop thru all but the last chips. That will be handled out of the loop.
  for( int i=0; i < CHIPS_PER_LINE-1; i++ )
    {
    // Copy the segments from digit #2 to digit #1 them #1 from next to #2.
    nextDisplay[firstChip+i] = (curDisplay[firstChip+i]   & 0xFFFC0000) >> 14 | 
                     (curDisplay[firstChip+i+1] & 0x0003FFF0) << 14;
    // Copy the decimal points
    nextDisplay[firstChip+i] |= (curDisplay[firstChip+i]   & 0x00000004) << 1 | 
                      (curDisplay[firstChip+i+1] & 0x00000008) >> 1;
    }

  // The last chip just moves digit 2 to 1 and leaves digit 2 blank.
  nextDisplay[firstChip+i] =  (curDisplay[firstChip+i] & 0xFFFC0000) >> 14;
  nextDisplay[firstChip+i] |= (curDisplay[firstChip+i] & 0x00000004) << 1;
  cursor -= 1;
  }




//
// Put a character on the display in one of the following modes:
// printMode ==
//   PRINT_MODE_OR         '\25' or '\O'
//   PRINT_MODE_XOR        '\26' or '\X'
//   PRINT_MODE_REPLACE    '\27' or '\R'
//
void DisplayDriver::printChar( char  c )
  {
  //                             digit 1      digit 2
  static  long  digitMask[] = { 0xFFFC0004, 0x0003FFF8 };
  int cVal = (int)c - 32;
  if( cVal >= 0  &&  cVal <= 95  &&  cursor >= 0  &&  cursor < CHAR_PER_DISPLAY )
    {
    int  segs;
    if( (cursor & 1) == 0 )
      // Digit 1
      segs = fontTbl[cVal] << 3;
    else
      // Digit 2
      segs = ((fontTbl[cVal] & 0x7FFE) << 17) | ((fontTbl[cVal] & 1) << 2);

    if( printMode == PRINT_MODE_OR )
      nextDisplay[cursor/DIGITS_PER_CHIP] |= segs;
    else if( printMode == PRINT_MODE_XOR )
      nextDisplay[cursor/DIGITS_PER_CHIP] ^= segs;
    else // assume PRINT_MODE_REPLACE
      {
      nextDisplay[cursor/DIGITS_PER_CHIP] &= digitMask[cursor & 1];
      nextDisplay[cursor/DIGITS_PER_CHIP] |= segs;
      }

    cursor++;
    }
  }




//
// print()
//   Send and ASCII string to the display.  Use the following special characters
// to change the "cursor possition", "scroll mode" and "overlay mode"  .
// Modify Curson Possition:
//   '\x00' or '\H' - HOME               
//   '\x00' to '\x13' - Place the curson at an absolute possition.
//                   +--+--+--+--+--+--+--+--+--+--+
//                   |0 |1 |2 |3 |4 |5 |6 |7 |8 |9 |
//                   +--+--+--+--+--+--+--+--+--+--+
//                   |10|11|12|13|14|15|16|17|17|19|20 <- curson can go here.
//                   +--+--+--+--+--+--+--+--+--+--+
//   '\x1F' or '\B' - BACKSPACE          Move the cursor back one.
// Scroll Modes:
//   '\x14' or '\S' - VSCROLL            Wrap and scroll like a TTY terminal.
//   '\x15' or '\w' - WRAP_1LINE         Wrap around and stay on the same line.
//   '\x16' or '\W' - WRAP_2LINES        Wrap end of line 2 to start of line 1.
//   '\x17' or '\h' - HSCOLL_1LINE       Scroll just the bottom line.
//   '\x18' or '\H' - HSCOLL_2LINES      Ticker-tape mode. Scroll both lines.
// Print Modes:
//   '\x19' or '\O' - PRINT_MODE_OR      Used to overlay multiple characters.
//   '\x1A' or '\X' - PRINT_MODE_XOR     Used to modify a character.
//   '\x1B' or '\R' - PRINT_MODE_REPLACE Clear before placing a character.
//
// Example:
//   "\ODon\b't Panic\b." - But screen in overlay mode and put the "'" over the "n".
// 
// msg      - Null terminated string to send to the display.  
// preement - "true" will stop printing, clear screen, and start printing this new message.
// returns: "ture" if msg address was stored, "false" if there is already a msg queued.
//
bool  DisplayDriver::print( char*  msg, bool  preempt )
  {
  if( preempt )
    {
    msg_ptr = msg;
    nextMsg_ptr = NULL;
    cls();
    }
  else if( msg_ptr == NULL )
    msg_ptr = msg;
  else if( nextMsg_ptr == NULL )
    nextMsg_ptr = msg;
  else
    return false;

  waitUntilMillis = 0;
  return true;
  }




// When the driver is quieten the display does not change and the
// the print queue is 0.  Then, when print() is called, the queue length
// grows to one while it is being sent to the screen.  A second call to
// print(), while the first string is still printing, will cause the 
// the queue length to go 2.  For now, this is as long as it can be.
int  DisplayDriver::printQueueLen()
  {
  return ( msg_ptr == NULL ? 0 : (nextMsg_ptr == NULL ? 1 : 2) );
  }




//
// Returns "true" if there is more to print.
//
bool  DisplayDriver::msgPump( long  millis )
  {
  // Return "false" if there is nothing to print.
  if( msg_ptr == NULL )
    if( nextMsg_ptr == NULL )
      return false;   // <---------------- RETURN if no more characters.
    else
      {
      // Go on to the next message.
      msg_ptr = nextMsg_ptr;
      nextMsg_ptr = NULL;
      }
  
  // Return "true" if we are waiting to scroll.
  if( millis < waitUntilMillis )
    return true;      // <---------------- RETURN if still waiting.
  // waitOver="true" only just after we finish waiting.
  bool  waitOver = ( waitUntilMillis != 0 );
  waitUntilMillis = 0;

  //
  // Loop thru the characters unill we are done or we have to wait to scroll.
  //
  while( msg_ptr != NULL  &&  millis >= waitUntilMillis )
    {
    // If we are at the end of the string, try going to the next one.
    if( *msg_ptr == '\000' )
      {
      msg_ptr = NULL;

      if( nextMsg_ptr == NULL )
        break; // <------ BREAK out of the loop if no more characters.

      msg_ptr = nextMsg_ptr;
      nextMsg_ptr = NULL;
      }

    // Get the next character.
    unsigned char  c = *msg_ptr;
    msg_ptr++;

    if( c < ' ' )
      {
      // It's a control character.
      if( c == 0 )
        return false; // RETURN - No more to do.

      if( c < VSCROLL )
        cursor = c;
      else if( c < PRINT_MODE_OR )
        wrapMode = c;
      else if( c <= PRINT_MODE_REPLACE )
        printMode = c;
      else if( c == BACKSPACE )                //  Backspace
        if( wrapMode == HSCOLL_2LINES  &&  cursor < CHAR_PER_DISPLAY )
          cursor = ( cursor - (CHAR_PER_LINE) + (CHAR_PER_DISPLAY-1)) % (CHAR_PER_DISPLAY-1);
        else
          cursor = ( cursor > 0 ? cursor-1 : 0 );
      }
    else
      {
      // "c" is a printable character.
      switch( wrapMode )
        {
        // Wrap and scroll like a TTY terminal.
        // This will pause before scrolling to allow reading time.
        case VSCROLL:
          if( cursor < CHAR_PER_DISPLAY )
            printChar( c );
          else
            {
            if( waitOver )
              {
              waitOver = false;
              vScroll();
              printChar( c );
              }
            else
              {
              // Undo "msg_ptr++" so that we retry this same char after a waiting period.
              msg_ptr--;
              waitUntilMillis = millis + (scrollSpeed * CHAR_PER_LINE);
              }
            }
          break;

        // Wrap around and stay on the same line. Wait after each character.
        case WRAP_1LINE:
          printChar( c );
          if( (cursor % CHAR_PER_LINE) == 0 )
            cursor -= CHAR_PER_LINE;

          // In wrap mode, print each char slowly to give time to read.
          waitUntilMillis = millis + scrollSpeed;
          break;

        // Wrap end of line 2 to start of line 1.
        case WRAP_2LINES:
          printChar( c );
          if( cursor >= CHAR_PER_DISPLAY )
            cursor = 0;

          // In wrap mode, print each char slowly to give time to read.
          waitUntilMillis = millis + scrollSpeed;
          break;

        // Scroll just the bottom line.
        case HSCOLL_1LINE:
          if( cursor >= CHAR_PER_DISPLAY )
            hScoll( BOTTOM_LINE );

          printChar( c );

          // Print the first line fast and the second line slowly.
          if( cursor > CHAR_PER_LINE )
            waitUntilMillis = millis + scrollSpeed ;
          break;

        // Ticker-tape mode. Cursor moves up to down and then left to right.
        // Curson in to out: 0 -> 10 -> 1  -> 11 -> 2 -> 12 -> 3 ... 
        //    -> 18 -> 9 -> 19 -> 20(scroll & print at 9) -> 19 -> 20(scroll & print at 9) -> 19
        case HSCOLL_2LINES:
          if( cursor >= CHAR_PER_DISPLAY )
            {
            hScoll( TOP_LINE );
            hScoll( BOTTOM_LINE );
            cursor = CHAR_PER_LINE - 1;
            }

          printChar( c );

          if( cursor == CHAR_PER_LINE )
            cursor = CHAR_PER_DISPLAY-1;
          else if( cursor < CHAR_PER_DISPLAY )
            cursor = ( cursor + (CHAR_PER_LINE-1) ) % (CHAR_PER_DISPLAY-1);
          if( cursor >= CHAR_PER_DISPLAY  &&  *msg_ptr != BACKSPACE )
            waitUntilMillis = millis + ( scrollSpeed * 2 );
          break;
        }
      }
    }

  // Write the new characters to the device.
  update();

  return ( msg_ptr != NULL );
  }


/////////////////////////////////////////////////////
//
// TEMP FOR DEBUGING
//  
void DisplayDriver::dumpDisplay()
  {
  static char  segChar[] = { 
    '-', '-', '-', '-', '-',
    '|', '\\','|', '/', '|',
    '-', '-', ' ', '-', '-',
    '|', '/', '|', '\\','|',
    '-', '-', '-', '-', '-' };

  static char  segIndex[] = { 
   17, 17, 17, 17, 17, 
   12,  9,  8,  7, 16, 
   11, 11,  0, 10, 10, 
   13,  4,  5,  6, 15, 
   14, 14, 14, 14, 14, };

  printf( "\n\n\n\n" );
  for( int q=0; q<5; q++ )
    printf( " 0x%8.8X   ", curDisplay[q] );
  printf( "\n" );

  // This for debugging use only.
  for( int line=0; line<NUM_OF_LINES; line++ )
    {
    for( int row=0; row<5; row++ )
      {
      for( int chip=0; chip<5; chip++ )
        for( int digit=0; digit<DIGITS_PER_CHIP; digit++ )
          {
          int   segs = curDisplay[(line*CHIPS_PER_LINE) + chip];
          bool  dp = ( (segs << (digit&1)) & 0x08 ) == 0x08;
          
          for( int i=0; i<5; i++ )
            if( (segs>>(segIndex[i+(row*5)]+(digit*14)) & 1) == 1 )
              printf( "%c", segChar[i+(row*5)] );
            else
              printf( " " );

          if( dp  &&  row==4 )
            printf( ". " );
          else
            printf( "  " );
          }
      printf( "\n" ); // end of each row
      }
    printf( "\n" ); // blank line between lines of digits
    }

  }
//
/////////////////////////////////////////////////////




static void delay300ns()
  {
  int i=0; 
  while( i < 100 )
    i++;
  }

void sendToChip( int  chip, long  data )
  {
  // Init Clock, Data, Enable pins
  digitalWrite( LED_CLOCK_PIN,  LOW );
  digitalWrite( LED_DATA_PIN,   LOW );
  digitalWrite( LED_ENABLE_PIN, HIGH ); // Inactive

  // Load address line with i.
  digitalWrite( LED_ADDR0_PIN, (chip & 0x01) );
  digitalWrite( LED_ADDR1_PIN, (chip & 0x02) );
  digitalWrite( LED_ADDR2_PIN, (chip & 0x04) );
  digitalWrite( LED_ADDR3_PIN, (chip & 0x08) );
 
  // Enable the chip
  digitalWrite( LED_ENABLE_PIN, LOW );  // Low active
  delay300ns();

  // Toggle the clock 36 time sending: 1dddddddddddddddddddddddddddddddd000.
  digitalWrite( LED_DATA_PIN, 1 );     // Output a "1"
  delay300ns();
  digitalWrite( LED_CLOCK_PIN,  HIGH );// Read the data on the rising edge.
  delay300ns();

  for( int i=0; i<32; i++ )
    {
    digitalWrite( LED_CLOCK_PIN,  LOW );// Reset the clock.
    
    // Set the data pin to the high order bit of data.
    delay300ns();
    digitalWrite( LED_DATA_PIN, (data<0) ? 1 : 0 );
    // Shift in the next bit.
    data <<= 1;

    delay300ns();
    digitalWrite( LED_CLOCK_PIN,  HIGH );// Read the data on the rising edge.
    delay300ns();
    }

  // Write the last 3 bits. Doesn't matter what they are.
  for( int i=0; i<3; i++ )
    {
    digitalWrite( LED_CLOCK_PIN,  LOW ); // Reset the clock.
    delay300ns();
    digitalWrite( LED_DATA_PIN, 0 );     // Output a "0"
    delay300ns();
    digitalWrite( LED_CLOCK_PIN,  HIGH );// Read the data on the rising edge.
    delay300ns();
    }
    
  digitalWrite( LED_ENABLE_PIN, HIGH ); // Inactive
  }




void  DisplayDriver::update()
  {
  dumpDisplay();  // TEMP TEMP TEMP

  for( int i=0; i<NUM_OF_DISPLAY_CHIPS; i++ )
    if( curDisplay[i] != nextDisplay[i] )
      {
      // Update display chip #i.

      sendToChip( i, nextDisplay[i] );
      curDisplay[i] = nextDisplay[i];
      }
  }
