//-----------------------------------------------------------------------------
///
/// \brief  "Electronic Assembly" Display
///
///         Controller on EA-Display supports 8- and 4-Bit Mode and SPI Mode.
///         We use SPI Mode here. There was also an 4Bit implementation.
///
///                           F1
///         #1      GND
///         #2      3V3
///         #12     SCLK      D3
///         #13     MISO      D5
///         #14     MOSI      D4
///         #15     CS        PC8
///
/// \date   20150306
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


//---Includes------------------------------------------------------------------


//---General--------------------------

#include <string.h>     // memcpy
#include <unistd.h>     // sleep

//---Own------------------------------

#include <biwak/eadisplay.hpp>
#include <biwak/output.hpp>
#include <biwak/biwak.h>
//#include <biwak/platform.h>      // p.
#include <biwak/timer.hpp>      // delay
#include <lepto/tools.h>
#include <lepto/debug.h>         // FREEZE
#include <biwak/exception.h>


//---Defines-------------------------------------------------------------------


#define EA_CS_ACTIVE       false
#define EA_CS_INACTIVE     true
#define EA_RS_INSTRUCTION  false
#define EA_RS_DATA         true
#define EA_8BIT_MODE
// #define EA_10BIT_SPI_MODE  true

#define  EA_CMD_SET_DDRAM_ADDR   (0x80)
#define  EA_CMD_CLEAR            (0x01)

const uint8_t initInstructionsW162Oled5VSpi[]=
   {  0x39, // function set european chararacter set; +4 does not show full screen in GM
      0x08, // display off
      0x06, // entry mode set increment cursor by 1 not shifting display
      0x17, // Character mode and internel power on
      0x01, // clear display
      0x02, // return home
      0x0C, // display on
      0x00  // fin.
   };

const uint8_t initInstructionsDogm163W5VSpi[]=
   {  0x39, // function set european chararacter set; +4 does not show full screen in GM
      0x1d, // display off
      0x53, // entry mode set increment cursor by 1 not shifting display
      0x6c, // Character mode and internel power on
      0x7F, // clear display; +0..3 changes contrast
      0x38, // return home
      0x0f -2 -1, // display on
      0x01, // display on
      0x06, // display on
      0x00  // fin.
   };

const uint8_t initInstructionsDogm163W3VSpi[]=
   {  0x39, // function set european chararacter set; +4 does not show full screen in GM
      0x15, // display off
      0x54, // entry mode set increment cursor by 1 not shifting display
      0x6e, // Character mode and internel power on
      0x7C, // clear display; +0..3 changes contrast
      0x38, // return home
      0x0F, // display on
      0x01, // display on
      0x06, // display on
      0x00  // fin.
   };

const uint8_t *initInstructions []=
{
   [eEaDisplayTypeW162Oled5VSpi]=initInstructionsW162Oled5VSpi,
   [eEaDisplayTypeDogm163W5VSpi]=initInstructionsDogm163W5VSpi,
   [eEaDisplayTypeDogm163W3VSpi]=initInstructionsDogm163W3VSpi,
};

/*
// 3V3
const unsigned char initInstructions[]=
{  0x39, // function set european chararacter set; +4 does not show full screen in GM
   0x15, // display off
   0x55, // entry mode set increment cursor by 1 not shifting display
   0x6E, // Character mode and internel power on
   0x72, // clear display
   0x38, // return home
   0x0f, // display on
   0x01, // display on
   0x06 // display on
};
*/

//---Implementation------------------------------------------------------------


const int offsetPerLineTM[]=
{
   [eEaDisplayTypeW162Oled5VSpi]=0x40,
   [eEaDisplayTypeDogm163W5VSpi]=16,
   [eEaDisplayTypeDogm163W3VSpi]=16,
   [eEaDisplayTypeQt]=1,
};

const int offsetPerLineGM[]=
{
   [eEaDisplayTypeW162Oled5VSpi]=99,
   [eEaDisplayTypeDogm163W5VSpi]=99,
   [eEaDisplayTypeDogm163W3VSpi]=99,
   [eEaDisplayTypeQt]=1,
};

#if 0
const int CEaDisplay::byteDelay=0;
// csEnableDelay=100, csDisableDelay=100     OK, works well
// csEnableDelay=0, csDisableDelay=100       OK, works well
// csEnableDelay=0, csDisableDelay=20        --, buggy
// csEnableDelay=0, csDisableDelay=50        --, not perfect


/* There were problems with instructions when one if these values was 50
 * at least with eEaDisplayTypeDogm163W3VSpi.
 */
const int CEaDisplay::csEnableDelay=150;
const int CEaDisplay::csDisableDelay=150;
const int CEaDisplay::busyDelay=0;
const int CEaDisplay::clearDelay=1000000;
#endif

/// \brief  Le contructeur
///
CEaDisplay::CEaDisplay( EEaDisplayType _eEaDisplayType, CSpi &_spi, EGpioPeripheral eGpioPeripheralCS, EGpioPin eGpioPinCS
                        , EGpioPeripheral eGpioPeripheralRS, EGpioPin eGpioPinRS )
   :eEaDisplayType(_eEaDisplayType)
   ,spi(_spi)
   ,outputCS(eGpioPeripheralCS, eGpioPinCS)
   ,outputRS(eGpioPeripheralRS, eGpioPinRS)
{
#if 0
   busyCounter=0;
#endif
#if defined(EA_10BIT_SPI_MODE)
   spi.setDataSize(10);
#endif
   resetDisplay();
   return;
}


CEaDisplay::~CEaDisplay()
{

}


void CEaDisplay::resetDisplay()
{
   outputCS.setValue(EA_CS_INACTIVE);
   writeInsArray(initInstructions[eEaDisplayType], strlen((char *)initInstructions[eEaDisplayType]));

   return;
}


void CEaDisplay::enableCs()
{
   outputCS.setValue(EA_CS_ACTIVE);
   #if defined(EA_10BIT_SPI_MODE)
      spi.setDataSize(10);
   #endif
   usleep(csEnableDelay);
}


void CEaDisplay::disableCs()
{
   outputCS.setValue(EA_CS_INACTIVE);
   usleep(csDisableDelay);
}


/// \brief  checks if busy flag of display is set
///
///         only clear() causes busy to be set some time (evaluated by tests).
///         This check could be dynamically only be run if clear() (or at least
///         any instruction) was called before.
void CEaDisplay::checkBusy()
{
#if 0
   busyCounter=0;
   if(1)
   {
#endif
      usleep(busyDelay);
#if 0
   }
   else
   {
      enableCs();
      spi.writeReadData( (uint32_t)0x40, 1 );

      while( (spi.writeReadData( (uint32_t)0x0, 1 ) & 0x20)
             && (busyCounter<0x1000) )
         busyCounter++;
   }
#endif
   disableCs();
}


void CEaDisplay::writeIns (const uint32_t data, int delayValue /*=0*/)
{
   checkBusy();
   outputRS.setValue(EA_RS_INSTRUCTION);
   enableCs();

   switch(eEaDisplayType)
   {
      case eEaDisplayTypeW162Oled5VSpi:
         #if defined(EA_10BIT_SPI_MODE)
            spi.writeReadData( (uint32_t)(uint8_t)data , 1 );
         #else
            spi.writeReadData( (uint32_t)data >> 2 , 1 );
            spi.writeReadData( ( (uint32_t)data << (8-2) ) & 0xFF, 1 );
         #endif
         break;
      case eEaDisplayTypeDogm163W5VSpi:
      case eEaDisplayTypeDogm163W3VSpi:
         spi.writeData( data, 1  );
         break;
      default:
         exception();
   }
/*
   #ifdef EA_8BIT_MODE
      #if 1
      #else
      #endif
      //spi.writeReadData( 0, 1 );
   #else
      spi.writeReadData((uint32_t)data, 1 );    // No RS set
   #endif
*/
   if(delayValue)
      usleep(delayValue);

   disableCs();

   return;
}


void CEaDisplay::writeInsArray (const uint8_t *pData, int size)
{
   //CheckBusy();

   // Have to set/reset CS for every single frame in 8Bit mode because slope
   // counter has to be reset
   #ifdef EA_8BIT_MODE

   for(int i1=0; i1<size; i1++)
   {
      writeIns( (unsigned char)pData[i1] );
      #if 0
      checkBusy();
      outputRS.setValue(EA_RS_INSTRUCTION);
      enableCs();
         switch(eEaDisplayType)
         {
            case eEaDisplayTypeW162Oled5VSpi:
               #if defined(EA_10BIT_SPI_MODE)
                  spi.writeReadData( (uint32_t)pData[i1] , 1 );
               #else
                  spi.writeReadData( (uint32_t)pData[i1] >> 2 , 1 );
                  spi.writeReadData( ( (uint32_t)pData[i1] << (8-2)) & 0xFF, 1 );
               #endif
               break;
            case eEaDisplayTypeDogm163W5VSpi:
               spi.writeData( (uint32_t)pData[i1] );
               break;
            default:
               exception("---", 0);
         }
         disableCs();
   #endif
   }

   #else

   outputCS.switchTo(EA_CS_ACTIVE);
   for(int i1=0; i1<size; i1++)
   {
         spi.writeReadData( (uint32_t)pData[i1] , 1 );
   }
   outputCS.switchTo(EA_CS_INACTIVE);

   #endif

   return;
}


void CEaDisplay::writeData (const uint32_t data)
{
   checkBusy();
   outputRS.setValue(EA_RS_DATA);
   enableCs();


   #ifdef EA_8BIT_MODE
      // RS set for first data Byte
   switch(eEaDisplayType)
   {
      case eEaDisplayTypeW162Oled5VSpi:
         #if defined(EA_10BIT_SPI_MODE)
            spi.writeReadData( ((uint32_t)data) | (1<<9) , 1 );
         #else
            spi.writeReadData( ( (uint16_t)data >> 2) | 0x80, 1 );
            spi.writeReadData( ( (uint16_t)data << (8-2)) & 0xFF, 1 );
         #endif
         break;
      case eEaDisplayTypeDogm163W5VSpi:
      case eEaDisplayTypeDogm163W3VSpi:
         spi.writeData( data, 1 );
         break;
      default:
         exception( );
   }
   #else
      spi.writeReadData( ( (uint16_t)data ) | (1l<<9), 1 );
   #endif
   disableCs();

   return;
}


void CEaDisplay::puts(const char *string)
{
   int i1=0;
   while(string[i1])
   {
      writeData(string[i1++]);
   }
}


void CEaDisplay::putch(const char ch)
{
   writeData(ch);
}


void CEaDisplay::gotoxy(int x, int y)
{
#if 0
   if(0)
   {
      writeIns( EA_CMD_SET_DDRAM_ADDR | ( (y*offsetPerLineGM[eEaDisplayType]) + ((y==0)?x:0 ) ) );
      // Uff, a little bit tricky
      if(y)
         for(int i1=0; i1<x+1; i1++)
            writeData( 0 );
   }
   else
   {
#endif
      int opl=offsetPerLineTM[eEaDisplayType];
      int offset=( ( y * opl ) + x );
      writeIns( EA_CMD_SET_DDRAM_ADDR | offset );
//   }
}


void CEaDisplay::clear()
{
   writeIns( EA_CMD_CLEAR );
   usleep(clearDelay);
   return;
}


//---fin-----------------------------------------------------------------------
