//
// 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: 1ABCDEFGHKMNRSTABCDEFGHKMNRSTddxx00
// curDisplay ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  The leading 1 and trailing 00 are not included.
#include "stdio.h"
#include "display_driver.h"


//
// 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()
  {
  cls();
  wrapMode  = VSCROLL;
  printMode = PRINT_MODE_OR;
  scrollSpeed = DEFAULT_SCROLL_SPEED;
  }


//
// 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 -= DIGITS_PER_LINE;
  }




//
// Horizontally scroll one line (right to left). 
// line = 0 (top line) or 1 (bottom line)
//
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
    nextDisplay[i] = (curDisplay[firstChip+i]   & 0xFFFC0000) >> 14 | 
                     (curDisplay[firstChip+i+1] & 0x0003FFF0) << 14;
    // Copy the decimal points
    nextDisplay[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[i] =  (curDisplay[firstChip+i] & 0xFFFC0000) >> 14;
  nextDisplay[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 )
    {
    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:
//    '\0' to '\19' - 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|
//                   +--+--+--+--+--+--+--+--+--+--+
//   '\31' or '\b'
// Scroll Modes:
//   '\20' or '\S' - VSCROLL            Wrap and scroll like a TTY terminal.
//   '\21' or '\w' - WRAP_1LINE         Wrap around and stay on the same line.
//   '\22' or '\W' - WRAP_2LINES        Wrap end of line 2 to start of line 1.
//   '\23' or '\h' - HSCOLL_1LINE       Scroll just the current line.
//   '\24' or '\H' - HSCOLL_2LINES      Ticker-tape mode. Scroll both lines.
// Print Modes:
//   '\25' or '\O' - PRINT_MODE_OR      Used to overlay multiple characters.
//   '\26' or '\X' - PRINT_MODE_XOR     Used to modify a character.
//   '\27' 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 call, the queue length
// goes 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 a long as it can be.
int  printQueueLen()
  {
  return ( msg == 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;
    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;

  while( msg_ptr != NULL )
    {
    unsigned char  c = *msg_ptr;

    // If we are at the end of the string, try going to the next one, just return.
    if( c == '\000' )
      {
      msg_ptr = NULL;

      if( nextMsg_ptr == NULL )
        return false;
        {
        msg_ptr = nextMsg_ptr;
        nextMsg_ptr = NULL;
        }
      }


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

      if( c < 20 )
        cursor = c;
      else if( c < 25 )
        wrapMode = c - 20;
      else if( c < 28 )
        printMode = c - 25;
      else if( c == 30 )                //  Home: put the cursor in the top, left corner.
        cursor = ( cursor > 0 ? cursor-1 : 0 );
      else if( c == 31 )                //  Backspace
        cursor = ( cursor > 0 ? cursor-1 : 0 );
      }
    else
      {
      // "c" is a printable character.
      switch( wrapMode )
        {
        case VSCROLL:
          if( cursor >= DIGITS_PER_LINE * NUM_OF_LINES )
            vScoll();
          printChar( c );
          break;

        case WRAP_1LINE:
          printChar( c );
          if( (cursor % DIGITS_PER_LINE) == 0 )
            cursor -= DIGITS_PER_LINE;
          break;

        case WRAP_2LINES:
          printChar( c );
          if( cursor >= DIGITS_PER_LINE*NUM_OF_LINES )
            cursor = 0;
          break;

        case HSCOLL_1LINE:
          if( cursor == DIGITS_PER_LINE  ||  cursor == DIGITS_PER_LINE*NUM_OF_LINES )
            hScoll( (cursor / DIGITS_PER_LINE) - 1 );
          printChar( c );
          break;

        case HSCOLL_2LINES:
          if( cursor == DIGITS_PER_LINE*NUM_OF_LINES )
            {
            hScoll( 0 );
            hScoll( 1 );
            cursor = DIGITS_PER_LINE - 1;
            }
          printChar( c );
          if( cursor == DIGITS_PER_LINE )
            cursor = DIGITS_PER_LINE*NUM_OF_LINES - 1;
          break;
        }
      }
    }

  update();
  }


/////////////////////////////////////////////////////
//
// 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 sendToChip( int  i, long  data )
  {
  // Init Clock, Data, Enable pins

  // Load address line with i.

  // Enable the chip

  // Toggle the clock 35 time sending: 1dddddddddddddddddddddddddddddddd00.
  }




void  DisplayDriver::update()
  {
  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];
      }

  dumpDisplay();
  }
