//----------------------------------------------------------------------------
//
// \brief   Phony BL
//
//          Fake it
//          https://www.st.com/resource/en/application_note/an3155-usart-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf
//
// \author  Maximilian Seesslen <mes@seesslen.net>
//
//----------------------------------------------------------------------------


//---Documentation------------------------------------------------------------



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

#undef LEPTO_LOG_DEBUG

#include <phonybl.h>
#include <stdio.h>
#include <biwak/biwak.h>
#include <biwak/timer.hpp>
#include <lepto/log.h>
#include <lepto/lepto.h>   // arraySize


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


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


constexpr char CPhonyBL::ACK;
constexpr char CPhonyBL::NACK;


CPhonyBL::SFunction CPhonyBL::functions[] {
   { 0x00, 0xFF, &CPhonyBL::info, "info" },
   { 0x02, 0xFD, &CPhonyBL::version, "version" },
   { 0x11, 0xEE, &CPhonyBL::readMemory, "read memory" },
   { 0x43, 0xBC, &CPhonyBL::eraseMemory, "erase memory" },
   { 0x31, 0xCE, &CPhonyBL::writeMemory, "write memory" },
   { 0x44, 0xBB, &CPhonyBL::extEraseMemory, "extended erase memory" },
   { 0x50, 0xAF, &CPhonyBL::specialCommand, "special command" },
};


void CPhonyBL::eventLoop()
{
   int c1 = m_io.getch();

   if(c1 == -1)
   {
      return;
   }
   //m_io.putch(c1);
   //return;
   //printf(" [ 0x%X ] '%c' (%d)\n", c1, c1, m_io.receiveBuffer.getEntryCount() );
   //return;

   int c2;
   bool acknowledged=false;
   switch(c1)
   {
      //case 0x00:
      //   break;
      case 0x7F:
         ack();
         break;
      default:
      {
         c2 = getInput();
         bool found=false;
         for(int i1=0; i1 < arraySize(functions); i1++ )
         {
            if(functions[i1].ch1==c1)
            {
               lDebug("Function found: 0x%X,0x%X", c1, c2);
               found=true;
               if(functions[i1].ch2==c2)
               {
                  ack();
                  ( (*this).*(functions[i1].func) )();
                  lDebug("Function '%s' called", functions[i1].name);
                  acknowledged=true;
                  break;
               }
               else
               {
                  lWarning("Protocol Error C2: 0x%X; C1: 0x%X", c2, c1);
                  nack();
                  acknowledged=true;
                  break;
               }
            }
         }
         if(!found)
         {
            lWarning("Function not found: 0x%X", c1);
            nack();
         }
         else if(!acknowledged)
         {
            lWarning("Protocol Error C1: 0x%X", c1);
            nack();
         }
         break;
      }
   }
}

int CPhonyBL::getInput()
{
   int input;
   lrtimer_t timeoutTimer=lrNow();
   do
   {
      m_io.eventLoop();
      biwakEventLoop();
   }while( ( (input=m_io.getch()) == -1 )
           && (lrElapsedMSeconds(timeoutTimer) < ( 250 * 8 ) ) );

   if( input == -1 )
   {
      lWarning("Input timeout: %d", m_io.receiveBuffer.isDataAvailable() );
      lWarning("   Entries: %d", m_io.receiveBuffer.getEntryCount());
   }

   return(input);
}

void CPhonyBL::ack()
{
   usleep(4000);
   m_io.write( &ACK, 1 );
}

void CPhonyBL::nack()
{
   lWarning("NACK");
   m_io.write( &NACK, 1 );
}

bool CPhonyBL::checkXor( char start, const char *buf, int size, char targetXor )
{
   char val=start;
   for(int i1=0; i1<size; i1++)
   {
      val=val^buf[i1];
   }
   if(val==targetXor)
   {
      return(true);
   }
   lWarning("XOR does not match: target: %X; calculated: %X", targetXor, val);
   return(false);
}

void CPhonyBL::info()
{
   constexpr uint8_t data[]{
      11,      // N; Number of chars - 1
      0x10,    // BL Version; 1.0
      0x00,    // Get command
      0x01,    // Get Version and Read Protection Status
      0x02,    // Get ID
      0x11,    // Read Memory command
      0x21,    // Go command
      0x31,    // Write Memory command
      // 0x43);   // Erase command or Extended Erase command
      0x44,    // Erase command or Extended Erase command
      0x63,    // Write Protect command
      0x73,    // Write Unprotect comman
      0x82,    // Readout Protect command
      0x92,    // Readout Unprotect command
      ACK
   };
   m_io.write(data, sizeof(data));
}

void CPhonyBL::version()
{
   constexpr char data[]{
         0x01,
         0x04,    // STM32
         0x10,    // 0x68: G443
         ACK
   };
   m_io.write(data, sizeof(data));
}

void CPhonyBL::readMemory()
{
   uint32_t  addr=0;
   uint8_t  _xor=0;
   uint8_t  num=0;
   for(int i1=0; i1<4; i1++)
   {
      addr |= getInput() << ((3 - i1)*8);
   }

   _xor=getInput();
   if ( ! checkXor( 0, (char *)&addr, 4, _xor) )
   {
      nack();
      return;
   }

   ack();
   num=getInput();

   _xor=getInput();
   if ( ! checkXor( 0xFF, (char *)&num, 1, _xor) )
   {
      nack();
      return;
   }

   ack();
   readFlashData( addr, m_data, num+1);
   for(int i1=0; i1<num+1; i1++)
   {
      m_io.write( &m_data[i1],1  );
   }
}

void CPhonyBL::eraseMemory()
{
   int number=getInput();
   static char value[0x100];
   int _xor=0;
   bool massErase=false;

   if(number==0xFF)
   {
      value[0] = getInput();
      massErase = true;
   }
   else
   {
      for(int i1=0; i1<number+1; i1++)
      {
         value[i1] = getInput();
      }
      _xor=getInput();
      if ( ! checkXor( number, (char *)value, number+1, _xor) )
      {
         nack();
         return;
      }
   }
   if(massErase)
   {
      eraseFlash();
   }

   ack();

   writeSignal.emitSignal(0);
}


void CPhonyBL::writeMemory()
{
   uint32_t  addr=0;
   uint8_t  _xor=0;
   uint8_t  num=0;
   int readValue;

   for(int i1=0; i1<4; i1++)
   {
      addr |= getInput() << ((3 - i1)*8);
   }
   _xor=getInput();
   if ( ! checkXor( 0, (char *)&addr, 4, _xor) )
   {
      nack();
      return;
   }

   ack();
   readValue=getInput();
   if(readValue==-1)
   {
      lWarning("Error reading num\n");
      return;
   }
   num=readValue;

   for(int i1=0; i1<num+1; i1++)
   {
      int i2=getInput();
      m_data[i1]=i2;
      if(i2==-1)
      {
         lWarning("Error reading data %d of num=%d\n", i1, num);
         return;
      }
   }
   readValue=getInput();
   _xor=readValue;
   if ( ! checkXor( num, (char *)&m_data, num+1, _xor) )
   {
      nack();
      return;
   }
   writeFlashData( addr, m_data, num+1);
   ack();

   writeSignal.emitSignal(addr);
}


void CPhonyBL::extEraseMemory( void )
{
   //
}

void CPhonyBL::specialCommand( void )
{
   uint32_t  addr=0;
   uint8_t  _xor=0;
   for(int i1=0; i1<2; i1++)
   {
      addr |= getInput() << ((1 - i1)*8);
   }
   _xor=getInput();
   if ( ! checkXor( 0, (char *)&addr, 4, _xor) )
   {
      nack();
      return;
   }

   ack();

   lWarning("Special command: 0x%lX\n", addr);
   return;
}

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