//-----------------------------------------------------------------------------
///
/// \brief  Displays with ILI9341 controller, e.g. from Display Elektronik
///
///         SPI, Chip-Select and Data-Command wires needed.
///
///         There are Command-writes (writeCommand, DC low) and Data-writes
///         (writeData, DC high) which are controlled by D/C gpio line.
///
///         https://www.crystalfontz.com/controllers/Ilitek/ILI9341V/143/
///
/// \date   20181026
/// \author Maximilian Seesslen <mes@seesslen.net>
///
//-----------------------------------------------------------------------------


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


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

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

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

#include <biwak/stm32/gdisplay_ili9341.h>
#include <biwak/output.hpp>
#include <biwak/biwak.h>
#include <biwak/timer.hpp>      // delay
#include <biwak/spi.hpp>
#include <lepto/log.h>
#include <biwak/font.h>
#include <biwak/exception.h>

#include <time.h>
#include <stdlib.h>
#include <arpa/inet.h>        // htons

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

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

#define MAC_HORZONTAL_DIRECTION     4
#define MAC_BGR                     8
#define MAC_MX                      0x40
#define MAC_MV                      0x20

enum class CGraphicDisplayIli9341::IliCommand: uint8_t
{
      NOP = 0x00,
      SoftwareReset = 0x01,         // pg. 92
      ReadDisplayStatus = 0x09,
      NormalDisplayMode = 0x13,     // pg. 106
      DisplayOn = 0x29,             // pg. 111
      SleepOut = 0x11,              // pg. 103
      MemoryAccessControl = 0x36,   // pg. 129
      WriteDisplayBrightness = 0x51,// pg. 143
      ColumnAddressSet = 0x2A,      // pg. 112
      PageAddressSet = 0x2B,        // pg. 114
      Invert = 0x21,
      WriteMemoryContinue = 0x3C,   // pg. 137
      WriteMemory = 0x2C,           // pg. 116
};


/// \brief  Le contructeur
///
CGraphicDisplayIli9341::CGraphicDisplayIli9341(CSpi &spi, EGpioPeripheral eGpioPeripheralCs, EGpioPin eGpioPinCs,
                                 EGpioPeripheral eGpioPeripheralDc, EGpioPin eGpioPinDc )
   :CGraphicDisplay(240, 320)
   ,CSpiSlave(spi, eGpioPeripheralCs, eGpioPinCs)
   ,m_spi(spi)
   ,m_outDc(eGpioPeripheralDc, eGpioPinDc)
{
   spi.setDataSize(8);
   setDataMode( DataMode::Data );
   init();
}

void CGraphicDisplayIli9341::init()
{
   uint retVal=0;
   int timeout=20;
   counter=0;
   uint32_t readBack;

   // Wait till device actually answeres correctly
   do
   {
      chipSelect();
      writeCommand(IliCommand::ReadDisplayStatus);
      retVal=m_spi.readData( (uint8_t*)&readBack, sizeof(readBack) );
      lAssert(!retVal);

      m_outDc.setValue( false );
      chipDeselect();
      timeout--;
   }while( (readBack==0xFFFFFFFFu) && timeout );

   if( !timeout )
   {
      // "Display did not answer"
      exception( );
   }

   writeCommand( IliCommand::SoftwareReset );
   // Having to less sleep here leads to strange effetcs; image missalligned
   usleep(30000);

   // Using writeCommands() increases binary size
   writeCommand( IliCommand::SleepOut );
   writeCommand( IliCommand::NormalDisplayMode );
   writeCommand( IliCommand::DisplayOn );

   writeCommand( IliCommand::MemoryAccessControl, MAC_BGR | MAC_MX );
   writeCommand( IliCommand::WriteDisplayBrightness, 0x0); // DOes nothing

   initSize();
   return;
}

CGraphicDisplayIli9341::~CGraphicDisplayIli9341()
{

}

void CGraphicDisplayIli9341::writeCommand (CGraphicDisplayIli9341::IliCommand command)
{
   chipSelect();
   setDataMode( DataMode::Command );
   m_spi.writeData( (uint32_t)command, 1 );
   setDataMode( DataMode::Data );
   chipDeselect();
   return;
}

// No benefit yet
void CGraphicDisplayIli9341::writeCommands (const CGraphicDisplayIli9341::IliCommand* commands, int count)
{
   chipSelect();
   setDataMode( DataMode::Command );
   m_spi.writeData( (uint8_t*)commands, count );
   setDataMode( DataMode::Data );
   chipDeselect();
   return;
}

void CGraphicDisplayIli9341::writeCommand (CGraphicDisplayIli9341::IliCommand command, const char param)
{
   chipSelect();
   writeCommand( command );
   m_spi.writeData( (uint32_t)param, 1  );
   chipDeselect();
   
   return;
}

void CGraphicDisplayIli9341::writeData ( const uint32_t data, int count )
{
   chipSelect();
   m_spi.writeData( data, count );
   chipDeselect();

   return;
}


void CGraphicDisplayIli9341::clear()
{
   clear(black);
   return;
}

void CGraphicDisplayIli9341::clear(TRgb color)
{
   chipSelect();
   prepareLine(color, m_xSize);
   // Full frame, use WriteMemory to locate cursor at start
   initSize();

   writeCommand( IliCommand::WriteMemory );
   setDataMode( DataMode::Data );

   for(int i1=0; i1<m_ySize; i1++)
   {
       m_spi.writeData((uint8_t *)m_line, 3 * m_xSize );
   }
   chipDeselect();

   return;
}

void CGraphicDisplayIli9341::gotoxyHw() /*virtual*/
{
   chipSelect();

   // - Set the Frame to the target pixel
   // - initiate an write to locate the cursor
   // - reset to full size again.
   writeCommand(IliCommand::PageAddressSet );
   writeData( yPos, 2 );
   writeData( yPos, 2 );
   writeCommand(IliCommand::ColumnAddressSet );
   writeData( xPos, 2 );
   writeData( xPos, 2 );
   writeCommand(IliCommand::WriteMemory );

   writeCommand(IliCommand::PageAddressSet );
   writeData( (uint32_t)0, 2  );
   writeData( m_ySize - 1, 2 );
   writeCommand(IliCommand::ColumnAddressSet );
   writeData( (uint32_t)0, 2  );
   writeData( m_xSize - 1, 2 );

   chipDeselect();
   return;
}


void CGraphicDisplayIli9341::setFrame(int x1, int y1, int x2, int y2) /*virtual*/
{
   chipSelect();

   writeCommand(IliCommand::PageAddressSet );
   writeData( y1, 2 );
   writeData( y2, 2 );

   writeCommand(IliCommand::ColumnAddressSet );
   writeData( x1, 2 );
   writeData( x2, 2 );

   writeCommand(IliCommand::WriteMemory );

   chipDeselect();
   return;
}


void CGraphicDisplayIli9341::initSize()
{
   setFrame(0, 0, m_xSize - 1, m_ySize - 1 );
   return;
}


void CGraphicDisplayIli9341::drawPixel( const TRgb color )
{
   if(pixelStream==EPixelStream::stopped)
   {
       chipSelect();
       writeCommand( IliCommand::WriteMemoryContinue );
   }
   m_spi.writeData((uint8_t *)&color, 3);
   if(pixelStream==EPixelStream::stopped)
   {
       chipDeselect();
   }
   
}


void CGraphicDisplayIli9341::drawPixels( const TRgb *pixels, int count)
{
   if(pixelStream==EPixelStream::stopped)
   {
      chipSelect();
      writeCommand( IliCommand::WriteMemoryContinue );
   }
   m_spi.writeData((uint8_t *)pixels, 3*count);
   if(pixelStream==EPixelStream::stopped)
   {
      chipDeselect();
   }
}

void CGraphicDisplayIli9341::pixelStreamStart()
{
   chipSelect();
   writeCommand( IliCommand::WriteMemoryContinue );
   pixelStream=EPixelStream::started;
}

void CGraphicDisplayIli9341::pixelStreamStop()
{
   chipDeselect();
   pixelStream=EPixelStream::stopped;
}


bool CGraphicDisplayIli9341::setRotation(bool rot) /* virtual override */
{
   writeCommand( IliCommand::MemoryAccessControl, MAC_BGR | MAC_MX | (rot?MAC_MV:0) );
   return(true);
}


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