#include "ArduboyFX.h"
#include <wiring.c>

uint16_t FX::programDataPage; // program read only data location in flash memory
uint16_t FX::programSavePage; // program read and write data location in flash memory
Font     FX::font;
Cursor   FX::cursor = {0,0,0,WIDTH};

FrameControl FX::frameControl;


uint8_t FX::writeByte(uint8_t data)
{
  SPDR = data;
  asm volatile("nop\n");
  uint8_t result;
  do
  {
    result = SPDR; //reading data here saves 1 extra cycle
  }
  while ((SPSR & (1 << SPIF)) == 0);
  return result;
}


uint8_t FX::readByte()
{
  return writeByte(0);
}


void FX::begin()
{
  disableOLED();
  wakeUp();
}


void FX::begin(uint16_t developmentDataPage)
{
  disableOLED();
 #ifdef ARDUINO_ARCH_AVR
  const uint8_t* vector = (const uint8_t*)FX_DATA_VECTOR_KEY_POINTER;
  asm volatile
  (
    "lpm  r18, z+                       \n"
    "lpm  r19, z+                       \n"
    "lpm  r21, z+                       \n"
    "lpm  r20, z+                       \n"
    "subi r18, 0x18                     \n"
    "sbci r19, 0x95                     \n"
    "brne .+2                           \n" // skip if FX_DATA_VECTOR_KEY_POINTER != FX_VECTOR_KEY_VALUE
    "movw %A[devdata], r20              \n"
    "sts  %[datapage]+0, %A[devdata]    \n"
    "sts  %[datapage]+1, %B[devdata]    \n"
    :            "+&z" (vector),
      [devdata]  "+&r" (developmentDataPage)
    : [datapage] "" (&programDataPage)
    : "r18", "r19", "r20", "r21"
   );
 #else
  if (pgm_read_word(FX_DATA_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
  {
    programDataPage = (pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER + 1);
  }
  else
  {
    programDataPage = developmentDataPage;
  }
 #endif
  wakeUp();
}


void FX::begin(uint16_t developmentDataPage, uint16_t developmentSavePage)
{
  disableOLED();
 #ifdef ARDUINO_ARCH_AVR
  const uint8_t* vector = (const uint8_t*)FX_DATA_VECTOR_KEY_POINTER;
  asm volatile
  (
    "lpm  r18, z+                       \n"
    "lpm  r19, z+                       \n"
    "lpm  r21, z+                       \n"
    "lpm  r20, z+                       \n"
    "subi r18, 0x18                     \n"
    "sbci r19, 0x95                     \n"
    "brne .+2                           \n"  // skip if FX_DATA_VECTOR_KEY_POINTER != FX_VECTOR_KEY_VALUE
    "movw %A[devdata], r20              \n"
    "sts  %[datapage]+0, %A[devdata]    \n"
    "sts  %[datapage]+1, %B[devdata]    \n"
    "lpm  r18, z+                       \n"
    "lpm  r19, z+                       \n"
    "lpm  r21, z+                       \n"
    "lpm  r20, z+                       \n"
    "subi r18, 0x18                     \n"
    "sbci r19, 0x95                     \n"
    "brne .+2                           \n" //  skip if FX_SAVE_VECTOR_KEY_POINTER != FX_VECTOR_KEY_VALUE
    "movw %A[devsave], r20              \n"
    "sts  %[savepage]+0, %A[devsave]    \n"
    "sts  %[savepage]+1, %B[devsave]    \n"
    :            "+&z" (vector),
      [devdata]  "+&r" (developmentDataPage),
      [devsave]  "+&r" (developmentSavePage)
    : [datapage] "" (&programDataPage),
      [savepage] "" (&programSavePage)
    : "r18", "r19", "r20", "r21"
   );
 #else
  if (pgm_read_word(FX_DATA_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
  {
    programDataPage = (pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_DATA_VECTOR_PAGE_POINTER + 1);
  }
  else
  {
    programDataPage = developmentDataPage;
  }
  if (pgm_read_word(FX_SAVE_VECTOR_KEY_POINTER) == FX_VECTOR_KEY_VALUE)
  {
    programSavePage = (pgm_read_byte(FX_SAVE_VECTOR_PAGE_POINTER) << 8) | pgm_read_byte(FX_SAVE_VECTOR_PAGE_POINTER + 1);
  }
  else
  {
    programSavePage = developmentSavePage;
  }
 #endif
  wakeUp();
}

void FX::readJedecID(JedecID & id)
{
  enable();
  writeByte(SFC_JEDEC_ID);
  id.manufacturer = readByte();
  id.device = readByte();
  id.size = readByte();
  disable();
}

void FX::readJedecID(JedecID* id)
{
  enable();
  writeByte(SFC_JEDEC_ID);
  id -> manufacturer = readByte();
  id -> device = readByte();
  id -> size = readByte();
  disable();
}

bool FX::detect()
{
  seekCommand(SFC_READ, 0);
  SPDR = 0;
  return readPendingLastUInt16() == 0x4152;
}


void FX::noFXReboot()
  {
    if (!detect())
    {
      do
      {
        if (*(uint8_t *)&timer0_millis & 0x80) bitSet(PORTB, RED_LED_BIT);
        else bitClear(PORTB, RED_LED_BIT);
      }
      while (bitRead(DOWN_BUTTON_PORTIN, DOWN_BUTTON_BIT)); // wait for DOWN button to enter bootloader
      Arduboy2Core::exitToBootloader();
    }
  }


void FX::writeCommand(uint8_t command)
{
  enable();
  writeByte(command);
  disable();
}

void FX::wakeUp()
{
  writeCommand(SFC_RELEASE_POWERDOWN);
}


void FX::sleep()
{
  writeCommand(SFC_POWERDOWN);
}

void FX::writeEnable()
{
  writeCommand(SFC_WRITE_ENABLE);
}


void FX::seekCommand(uint8_t command, uint24_t address)
{
  enable();
 #ifdef ARDUINO_ARCH_AVR
  register uint8_t cmd asm("r24") = command; //assembly optimizer for AVR platform ~saves 12 bytes
  asm volatile
  (
    "call %x2           \n"
    "mov  r24, %C[addr] \n"
    "call %x2           \n"
    "mov  r24, %B[addr] \n"
    "call %x2           \n"
    "mov  r24, %A[addr] \n"
    "jmp  %x2           \n"
    : [cmd]   "+&r" (cmd)
    : [addr]  "r"   (address),
      [write] "i" (writeByte)
    :
  );
 #else
  writeByte(command);
  writeByte(address >> 16);
  writeByte(address >> 8);
  writeByte(address);
 #endif
}


void FX::seekData(uint24_t address)
{
  uint24_t abs_address = address;
 #ifdef ARDUINO_ARCH_AVR
  asm volatile
  (
    "mov  %A[abs], %A[addr]  \n"
    "lds  %B[abs], %[page]+0 \n"
    "add  %B[abs], %B[addr]  \n"
    "lds  %C[abs], %[page]+1 \n"
    "adc  %C[abs], %C[addr]  \n"
    :
     [abs]  "=r" (abs_address)
    :[page] ""   (&programDataPage),
     [addr] "r"  (address)
    :
  );
 #else // C++ version for non AVR platforms
  abs_address = address + (uint24_t)programDataPage << 8;
 #endif
  seekCommand(SFC_READ, abs_address);
  SPDR = 0;
}


void FX::seekDataArray(uint24_t address, uint8_t index, uint8_t offset, uint8_t elementSize)
{
 #ifdef ARDUINO_ARCH_AVR
  register uint24_t addr asm("r22") = address;
  asm volatile
  (
    "   mul     %[index], %[size]   \n"
    "   brne    .+2                 \n" //treat size 0 as size 256
    "   mov     r1, %[index]        \n"
    "   clr     r21                 \n" //use as alternative zero reg
    "   add     r0, %[offset]       \n"
    "   adc     r1, r21             \n"
    "   add     %A[addr], r0        \n"
    "   adc     %B[addr], r1        \n"
    "   adc     %C[addr], r21       \n"
    "   clr     r1                  \n"
    "   jmp     %x4                 \n" //seekData
    : [addr]    "+r"  (addr)
    : [index]   "r"  (index),
      [offset]  "r"  (offset),
      [size]    "r"  (elementSize),
                ""   (seekData)
    : "r21"
  );
 #else
  address += elementSize ? index * elementSize + offset : index * 256 + offset;
  seekData(address);
 #endif
}


void FX::seekSave(uint24_t address)
{
 #ifdef ARDUINO_ARCH_AVR
  uint24_t abs_address = address;
  asm volatile
  (
    "mov  %A[abs], %A[addr]  \n"
    "lds  %B[abs], %[page]+0 \n"
    "add  %B[abs], %B[addr]  \n"
    "lds  %C[abs], %[page]+1 \n"
    "adc  %C[abs], %C[addr]  \n"
    :
     [abs]  "=r" (abs_address)
    :[page] ""    (&programSavePage),
     [addr] "r"   (address)
    :
  );
 #else // C++ version for non AVR platforms
  abs_address = address + (uint24_t)programSavePage << 8;
 #endif
  seekCommand(SFC_READ, abs_address);
  SPDR = 0;
}


uint8_t FX::readPendingUInt8()
{
  wait();
  uint8_t result = SPDR;
  SPDR = 0;
  return result;
}


uint8_t FX::readPendingLastUInt8()
{
  wait();                 // wait for a pending read to complete
  return readUnsafeEnd(); // read last byte and disable flash
}


uint16_t FX::readPendingUInt16()
{
 #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
  uint16_t result asm("r24"); // we want result to be assigned to r24,r25
  asm volatile
  (
    "call %x1           \n"
    "mov  %B[val], r24  \n"
    "jmp  %x1           \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt8)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint16_t)readPendingUInt8() << 8) | (uint16_t)readPendingUInt8();
 #endif
}


uint16_t FX::readPendingLastUInt16()
{
 #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
  uint16_t result asm("r24"); // we want result to be assigned to r24,r25
  asm volatile
  (
    "call %x1           \n"
    "mov  %B[val], r24  \n"
    "jmp  %x2           \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt8),
      "" (readEnd)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint16_t)readPendingUint8() << 8) | (uint16_t)readEnd();
 #endif
}


uint24_t FX::readPendingUInt24()
{
 #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
  uint24_t result asm("r24"); // we want result to be assigned to r24,r25,r26
  asm volatile
  (
    "call %x1           \n"
    "mov  %B[val], r24  \n"
    "call %x2           \n"
    "mov  %A[val], r24  \n"
    "mov  %C[val], r25  \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt16),
      "" (readPendingUInt8)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint24_t)readPendingUInt16() << 8) | readPendingUInt8();
 #endif
}


uint24_t FX::readPendingLastUInt24()
{
 #ifdef ARDUINO_ARCH_AVR // Assembly implementation for AVR platform
  uint24_t result asm("r24"); // we want result to be assigned to r24,r25,r26
  asm volatile
  (
    "call %x1           \n"
    "mov  %B[val], r24  \n"
    "call %x2           \n"
    "mov  %A[val], r24  \n"
    "mov  %C[val], r25  \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt16),
      "" (readEnd)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint24_t)readPendingUInt16() << 8) | readEnd();
 #endif
}


uint32_t FX::readPendingUInt32()
{
 #ifdef ARDUINO_ARCH_AVR //Assembly implementation for AVR platform
  uint32_t result asm("r24"); // we want result to be assigned to r24,r25,r26,r27
  asm volatile
  (
    "call %x1           \n"
    "movw  %C[val], r24 \n"
    "call %x1           \n"
    "movw  %A[val], r24 \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt16)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint32_t)readPendingUInt16() << 16) | readPendingUInt16();
 #endif
}


uint32_t FX::readPendingLastUInt32()
{
 #ifdef ARDUINO_ARCH_AVR //Assembly implementation for AVR platform
  uint32_t result asm("r24"); // we want result to be assigned to r24,r25,r26,r27
  asm volatile
  (
    "call %x1           \n"
    "movw  %C[val], r24 \n"
    "call %x2           \n"
    "movw  %A[val], r24 \n"
    : [val] "=&r" (result)
    : "" (readPendingUInt16),
      "" (readPendingLastUInt16)
    :
  );
  return result;
 #else //C++ implementation for non AVR platforms
  return ((uint32_t)readPendingUInt16() << 16) | readPendingLastUInt16();
 #endif
}


void FX::readBytes(uint8_t* buffer, size_t length)
{
 #ifdef ARDUINO_ARCH_AVR
  asm volatile(
    "1:              \n"
    "call %x2        \n"
    "st z+,r24       \n"
    "subi %A[len], 1 \n"
    "sbci %B[len], 0 \n"
    "brne 1b         \n"
    :      "+&z" (buffer),
     [len] "+&d" (length)
    : ""    (readPendingUInt8)
    : "r24"
  );
 #else
  for (size_t i = 0; i < length; i++)
  {
    buffer[i] = readPendingUInt8();
  }
 #endif
}


void FX::readBytesEnd(uint8_t* buffer, size_t length)
{
#ifdef ARDUINO_ARCH_AVR
  asm volatile(
    "1:                 \n"
    "subi %A[len], 1    \n"
    "sbci %B[len], 0    \n"
    "breq 2f            \n"
    "                   \n"
    "call %x2           \n"
    "st   z+, r24       \n"
    "rjmp 1b            \n"
    "2:                 \n"
    "call %x3           \n"
    "st   z, r24        \n"

    :      "+&z" (buffer),
     [len] "+&d" (length)
    : ""    (readPendingUInt8),
      ""    (readEnd)
    : "r24"
  );
 #else
   for (size_t i = 0; i <= length; i++)
  {
    if ((i+1) != length)
    buffer[i] = readPendingUInt8();
    else
    {
      buffer[i] = readEnd();
      break;
    }
  }
 #endif
}


uint8_t FX::readEnd()
{
  wait();                 // wait for a pending read to complete
  return readUnsafeEnd(); // read last byte and disable flash
}


void FX::readDataBytes(uint24_t address, uint8_t* buffer, size_t length)
{
  seekData(address);
  readBytesEnd(buffer, length);
}


void FX::readSaveBytes(uint24_t address, uint8_t* buffer, size_t length)
{
  seekSave(address);
  readBytesEnd(buffer, length);
}

uint8_t FX::loadGameState(uint8_t* gameState, size_t size) // ~54 bytes
{
 #ifdef ARDUINO_ARCH_AVR
  uint8_t result asm("r24");
  asm volatile(
    "ldi  r22, 0        \n" //seekSave(0)
    "ldi  r23, 0        \n"
    "ldi  r24, 0        \n"
    "call %x3           \n" //seekSave uses r20, r21, r22, r23, r24
    "movw r18, r26      \n" //save size
    "movw r20, r30      \n" //save gameState
    "0:                 \n"
    "ldi  r22,0         \n" //result = 0
    "1:                 \n"
    "call %x4           \n" //readPendingUInt16 uses r24, r25
    "cp   r24, r18      \n"
    "cpc  r25, r19      \n"
    "brne 4f            \n" //if (readPendingUInt16 != size) break
    "                   \n"
    "movw r26, r18      \n" //restore size
    "movw r30, r20      \n" //restore gameState
    "ldi  r22, 0        \n" //result = 0
    "2:                 \n" //do
    "call %x5           \n" //  data = readPendingUint8
    "st   z+, r24       \n" //  addr = data
    "sbiw r26, 1        \n" //  size--
    "brne 2b            \n" //until size == 0
    "ldi  r22, 1        \n" //result = 1
    "rjmp 1b            \n" //next
    "4:                 \n"
    "call %x6           \n" //readEnd
    "mov  r24, r22      \n" //return result
    : [addr] "+&z" (gameState),
      [size] "+&x" (size),
      [val]  "=&r"  (result)
    :        "i"   (seekSave),
             "i"   (readPendingUInt16),
             "i"   (readPendingUInt8),
             "i"   (readEnd)
    : "r18", "r19", "r20", "r21"
  );
 #else
  seekSave(0);
  uint8_t result = 0;
  while (readPendingUInt16() == size) // if gameState size not equal, most recent gameState has been read or there is no gameState
  {
    for (uint16_t i = 0; i < size; i++)
    {
      uint8_t data = readPendingUInt8();
      gameState[i] = data;
    }
    {
      result = 1; // signal gameState loaded
    }
  }
  readEnd();
 #endif
  return result;
}

void FX::saveGameState(const uint8_t* gameState, size_t size) // ~152 bytes locates free space in 4K save block and saves the GamesState.
{                                                       //            if there is not enough free space, the block is erased prior to saving
 register size_t sz asm("r18") = size;
 #ifdef ARDUINO_ARCH_AVR
  asm volatile(
    "ldi  r26, 0                \n" //addr = 0
    "ldi  r27, 0                \n"
    "1:                         \n"
    "movw r22, r26              \n" //uint24_t addr
    "ldi  r24, 0                \n"
    "call %x2                   \n" //seekSave uses r20, r21, r22, r23, r24
    "call %x3                   \n" //readPendingLastUInt16 uses r24, r25
    "movw r22, r26              \n" //save addr
    "adiw r26, 2                \n" //addr += 2 for size word
    "add  r26, r18              \n" //addr += size
    "adc  r27, r19              \n"
    "cp   r24, r18              \n"
    "cpc  r25, r19              \n"
    "breq 1b                    \n" //readPendingLastUInt16 == size
    "                           \n"
    "subi r24, 0xFF             \n" //if result of readPendingLastUInt16 != 0xFFFF
    "sbci r25, 0xFF             \n"
    "brne 2f                    \n" //erase block
    "                           \n"
    "subi r26, lo8(4094+1)      \n"
    "sbci r27, hi8(4094+1)      \n"// addr < 4094+1 (last two bytes in 4K block always 0xFF)
    "movw r26, r22              \n"// addr -= size - 2  point to start of free space
    "brcs 3f                    \n"
    "2:                         \n" //erase 4K save block at addr
    "call %x4                   \n" //writeEnable
    "ldi  r20, 0                \n"
    "lds  r21, %[page]+0        \n"
    "lds  r22, %[page]+1        \n"
    "ldi  r24, %[erase]         \n" //SFC_ERASE
    "call %x5                   \n" //seekCommand
    "sbi  %[fxport], %[fxbit]   \n" //disable
    "call %x6                   \n" //waitWhileBusy
    "ldi  r26, 0                \n" //addr = 0
    "ldi  r27, 0                \n"
    "3:                         \n"
    "ldi  r23, 0xFC             \n" // int8_t shiftstate = -4
    "4:                         \n"
    "call %x4                   \n" //writeEnable
    "mov  r20, r26              \n" //addr
    "lds  r21, %[page]+0        \n"
    "add  r21, r27              \n"
    "lds  r22, %[page]+1        \n"
    "adc  r22, r1               \n"
    "ldi  r24, %[write]         \n" //SFC_WRITE
    "call %x5                   \n" //seekCommand
    "5:                         \n"
    "mov  r24, r19              \n"
    "sbrc r23, 1                \n" //if (shiftstate & 3 == 0) writebyte(size >> 8)
    "mov  r24, r18              \n" //if (shiftstate & 3 == 2) writebyte(size & 0xFF)
    "sbrc r23, 0                \n" //else writeByte(gameState++);
    "ld   r24, z+               \n" //saveState
    "call %x7                   \n" //writeByte
    "asr  r23                   \n" //shiftstate >>= 1
    "brcc .+6                   \n" //if (shiftstate == -1) size--
    "subi r18, 1                \n"
    "sbci r19, 0                \n"
    "breq 6f                    \n" //size == 0
    "                           \n"
    "adiw r26, 1                \n" //addr++
    "and  r26, r26              \n"
    "brne 5b                    \n" //while addr & 0xFF != 0 (not end of page)
    "6:                         \n"
    "sbi  %[fxport], %[fxbit]   \n" //disable
    "call %x6                   \n" //waitWhileBusy
    "cp   r18, r1               \n"
    "cpc  r19, r1               \n"
    "brne 4b                    \n" //while size != 0
    :[state]  "+&z" (gameState),
     [size]   "+&r" (sz)
    :         ""    (seekSave),
              ""    (readPendingLastUInt16),
              ""    (writeEnable),
              ""    (seekCommand),
              ""    (waitWhileBusy),
              ""    (writeByte),
     [fxport] "i"   (_SFR_IO_ADDR(FX_PORT)),
     [fxbit]  "i"   (FX_BIT),
     [erase]  "i"   (SFC_ERASE),
     [write]  "i"   (SFC_WRITE),
     [page]   ""    (&programSavePage)
    : "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27"
  );
 #else
  uint16_t addr = 0;
  for(;;) // locate end of previous gameStates
  {
    seekSave(addr);
    if (readPendingLastUInt16() != size) break; //found end of previous gameStates
    addr += size + 2;
  }
  if ((addr + size) > 4094) //is there enough space left? last two bytes of 4K block must always be unused (0xFF)
  {
    eraseSaveBlock(0); // erase save block
    waitWhileBusy();   // wait for erase to complete
    addr = 0;          // write saveState at the start of block
  }

  while (size)
  {
    writeEnable();
    seekCommand(SFC_WRITE, (uint24_t)(programSavePage << 8) + addr);
    do
    {
      writeByte(*gameState++);
      if (--size == 0) break;
    }
    while ((uint8_t)++addr); // write bytes until end of a page
    disable();               // start writing the (partial) page
    waitWhileBusy();         // wait for page write to complete
  }
 #endif
}

void  FX::eraseSaveBlock(uint16_t page)
{
  writeEnable();
  seekCommand(SFC_ERASE, (uint24_t)(programSavePage + page) << 8);
  disable();
}


void FX::writeSavePage(uint16_t page, uint8_t* buffer)
{
  writeEnable();
  seekCommand(SFC_WRITE, (uint24_t)(programSavePage + page) << 8);
  uint8_t i = 0;
  do
  {
    writeByte(buffer[i]);
  }
  while (i++ < 255);
  disable();
}

void FX::waitWhileBusy()
{
  enable();
  writeByte(SFC_READSTATUS1);
  while(readByte() & 1)
    ; // wait while BUSY status bit is set
  disable();
}

void FX::drawBitmap(int16_t x, int16_t y, uint24_t address, uint8_t frame, uint8_t mode)
{
  // read bitmap dimensions from flash
  seekData(address);
  int16_t width  = readPendingUInt16();
  int16_t height = readPendingLastUInt16();
  // return if the bitmap is completely off screen
  if (x + width <= 0 || x >= WIDTH || y + height <= 0 || y >= HEIGHT) return;

  // determine render width
  int16_t skipleft = 0;
  uint8_t renderwidth;
  if (x<0)
  {
    skipleft = -x;
    if (width - skipleft < WIDTH) renderwidth = width - skipleft;
    else renderwidth = WIDTH;
  }
  else
  {
    if (x + width > WIDTH) renderwidth = WIDTH - x;
    else renderwidth = width;
  }

  //determine render height
  int16_t skiptop;     // pixel to be skipped at the top
  int8_t renderheight; // in pixels
  if (y < 0)
  {
    skiptop = -y & -8; // optimized -y / 8 * 8
    if (height - skiptop <= HEIGHT) renderheight = height - skiptop;
    else renderheight = HEIGHT + (-y & 7);
    skiptop = fastDiv8(skiptop); // pixels to displayrows
  }
  else
  {
    skiptop = 0;
    if (y + height > HEIGHT) renderheight = HEIGHT - y;
    else renderheight = height;
  }
  uint24_t offset = (uint24_t)(frame * (fastDiv8(height+(uint16_t)7)) + skiptop) * width + skipleft;
  if (mode & dbmMasked)
  {
    offset += offset; // double for masked bitmaps
    width += width;
  }
  address += offset + 4; // skip non rendered pixels, width, height
  int8_t displayrow = (y >> 3) + skiptop;
  uint16_t displayoffset = displayrow * WIDTH + x + skipleft;
  if (mode & dbmFlip) displayoffset += renderwidth - 1;
  uint8_t yshift = bitShiftLeftUInt8(y); //shift by multiply
#ifdef ARDUINO_ARCH_AVR
  uint8_t rowmask;
  uint16_t bitmap;
  asm volatile(
    "1: ;render_row:                                \n"
    "   cbi     %[fxport], %[fxbit]                 \n"
    "   ldi     r24, %[cmd]                         \n" // writeByte(SFC_READ);
    "   out     %[spdr], r24                        \n"
    "   lds     r24, %[datapage]+0                  \n" // address + programDataPage;
    "   lds     r25, %[datapage]+1                  \n"
    "   add     r24, %B[address]                    \n"
    "   adc     r25, %C[address]                    \n"
    "   in      r0, %[spsr]                         \n" // wait()
    "   sbrs    r0, %[spif]                         \n"
    "   rjmp    .-6                                 \n"
    "   out     %[spdr], r25                        \n" // writeByte(address >> 16);
    "   in      r0, %[spsr]                         \n" // wait()
    "   sbrs    r0, %[spif]                         \n"
    "   rjmp    .-6                                 \n"
    "   out     %[spdr], r24                        \n" // writeByte(address >> 8);
    "   in      r0, %[spsr]                         \n" // wait()
    "   sbrs    r0, %[spif]                         \n"
    "   rjmp    .-6                                 \n"
    "   out     %[spdr], %A[address]                \n" // writeByte(address);
    "                                               \n"
    "   add     %A[address], %A[width]              \n" // address += width;
    "   adc     %B[address], %B[width]              \n"
    "   adc     %C[address], r1                     \n"
    "   in      r0, %[spsr]                         \n" // wait();
    "   sbrs    r0, %[spif]                         \n"
    "   rjmp    .-6                                 \n"
    "   out     %[spdr], r1                         \n" // SPDR = 0;
    "                                               \n"
    "   lsl     %[mode]                             \n" // 'clear' mode dbfExtraRow by shifting into carry
    "   cpi     %[displayrow], %[lastrow]           \n"
    "   brge    .+4                                 \n" // row >= lastrow, clear carry
    "   sec                                         \n" // row < lastrow set carry
    "   sbrc    %[yshift], 0                        \n" // yshift != 1, don't change carry state
    "   clc                                         \n" // yshift == 1, clear carry
    "   ror     %[mode]                             \n" // carry to mode dbfExtraRow
    "                                               \n"
    "   ldi     %[rowmask], 0x02                    \n" // rowmask = 0xFF >> (8 - (height & 7));
    "   sbrc    %[renderheight], 1                  \n"
    "   ldi     %[rowmask], 0x08                    \n"
    "   sbrc    %[renderheight], 2                  \n"
    "   swap    %[rowmask]                          \n"
    "   sbrs    %[renderheight], 0                  \n"
    "   lsr     %[rowmask]                          \n"
    "   dec     %[rowmask]                          \n"
    "   breq    .+4                                 \n"
    "   cpi     %[renderheight], 8                  \n" // if (renderheight >= 8) rowmask = 0xFF;
    "   brlt    .+2                                 \n"
    "   ldi     %[rowmask], 0xFF                    \n"
    "                                               \n"
    "   mov     r25, %[renderwidth]                 \n" // for (c < renderwidth)
    "2: ;render_column:                             \n"
    "   in      r0, %[spdr]                         \n" // read bitmap data
    "   out     %[spdr], r1                         \n" // start next read
    "                                               \n"
    "   sbrc    %[mode], %[reverseblack]            \n" // test reverse mode
    "   eor     r0, %[rowmask]                      \n" // reverse bitmap data
    "   mov     r24, %[rowmask]                     \n" // temporary move rowmask
    "   sbrc    %[mode], %[whiteblack]              \n" // for black and white modes:
    "   mov     r24, r0                             \n" // rowmask = bitmap
    "   sbrc    %[mode], %[black]                   \n" // for black mode:
    "   clr     r0                                  \n" // bitmap = 0
    "   mul     r0, %[yshift]                       \n"
    "   movw    %[bitmap], r0                       \n" // bitmap *= yshift
    "   bst     %[mode], %[masked]                  \n" // if bitmap has no mask:
    "   brtc    3f ;render_mask                     \n" // skip next part
    "                                               \n"
    "   lpm                                         \n" // above code took 11 cycles, wait 7 cycles more for SPI data ready
    "   lpm                                         \n"
    "   clr     r1                                  \n" // restore zero reg
    "                                               \n"
    "   in      r0, %[spdr]                         \n" // read mask data
    "   out     %[spdr],r1                          \n" // start next read
    "   sbrc    %[mode], %[whiteblack]              \n" //
    "3: ;render_mask:                               \n"
    "   mov     r0, r24                             \n" // get mask in r0
    "   mul     r0, %[yshift]                       \n" // mask *= yshift
    ";render_page0:                                 \n"
    "   cpi     %[displayrow], 0                    \n" // skip if displayrow < 0
    "   brlt    4f ;render_page1                    \n"
    "                                               \n"
    "   ld      r24, %a[buffer]                     \n" // do top row or to row half
    "   sbrs    %[mode],%[invert]                   \n" // skip 1st eor for invert mode
    "   eor     %A[bitmap], r24                     \n"
    "   and     %A[bitmap], r0                      \n" // and with mask LSB
    "   eor     %A[bitmap], r24                     \n"
    "   st      %a[buffer], %A[bitmap]              \n"
    "4: ;render_page1:                              \n"
    "   subi    %A[buffer], lo8(-%[displaywidth])   \n"
    "   sbci    %B[buffer], hi8(-%[displaywidth])   \n"
    "   sbrs    %[mode], %[extrarow]                \n" // test if ExtraRow mode:
    "   rjmp    5f ;render_next                     \n" // else skip
    "                                               \n"
    "   ld      r24, %a[buffer]                     \n" // do shifted 2nd half
    "   sbrs    %[mode], %[invert]                  \n" // skip 1st eor for invert mode
    "   eor     %B[bitmap], r24                     \n"
    "   and     %B[bitmap], r1                      \n"// and with mask MSB
    "   eor     %B[bitmap], r24                     \n"
    "   st      %a[buffer], %B[bitmap]              \n"
    "5: ;render_next:                               \n"
    "   clr     r1                                  \n" // restore zero reg
    "   sbrc    %[mode], 5                          \n" // flip mode:
    "   subi    %A[buffer], lo8(%[displaywidth]+1)  \n" // buffer -= WIDTH + 1
    "   sbrs    %[mode], 5                          \n" // else
    "   subi    %A[buffer], lo8(%[displaywidth]-1)  \n" // buffer -= WIDTH - 1
    "   sbc     %B[buffer], r1                      \n"
    "   dec     r25                                 \n"
    "   brne    2b ;render_column                   \n" // for (c < renderheigt) loop
    "                                               \n"
    "   mov     r24, %[renderwidth]                 \n"
    "   sbrs    %[mode], 5                          \n" // flip:    + renderwidth + WIDTH
    "   neg     r24                                 \n" // no flip: - renderwidth + WIDTH
    "   subi    r24, lo8(-%[displaywidth])          \n"
    "   add     %A[buffer], r24                     \n" // buffer += WIDTH +/- renderwidth
    "   adc     %B[buffer], r1                      \n"
    "   subi    %[renderheight], 8                  \n" // reinderheight -= 8
    "   inc     %[displayrow]                       \n" // displayrow++
    "   in      r0, %[spsr]                         \n" // clear SPI status
    "   sbi     %[fxport], %[fxbit]                 \n" // disable external flash
    "   cp      r1, %[renderheight]                 \n" // while (renderheight > 0)
    "   brge    .+2                                 \n"
    "   rjmp    1b ;render_row                      \n"
   :
    [address]      "+r" (address),
    [mode]         "+r" (mode),
    [rowmask]      "=&d" (rowmask),
    [bitmap]       "=&r" (bitmap),
    [renderheight] "+d" (renderheight),
    [displayrow]   "+d" (displayrow)
   :
    [width]        "r" (width),
    [height]       "r" (height),
    [yshift]       "r" (yshift),
    [renderwidth]  "r" (renderwidth),
    [buffer]       "e" (Arduboy2Base::sBuffer + displayoffset),

    [fxport]       "I" (_SFR_IO_ADDR(FX_PORT)),
    [fxbit]        "I" (FX_BIT),
    [cmd]          "I" (SFC_READ),
    [spdr]         "I" (_SFR_IO_ADDR(SPDR)),
    [datapage]     ""  (&programDataPage),
    [spsr]         "I" (_SFR_IO_ADDR(SPSR)),
    [spif]         "I" (SPIF),
    [lastrow]      "I" (HEIGHT / 8 - 1),
    [displaywidth] ""  (WIDTH),
    [reverseblack] "I" (dbfReverseBlack),
    [whiteblack]   "I" (dbfWhiteBlack),
    [black]        "I" (dbfBlack),
    [masked]       "I" (dbfMasked),
    [invert]       "I" (dbfInvert),
    [extrarow]     "I" (dbfExtraRow)
   :
    "r24", "r25"
   );
#else
  uint8_t lastmask = bitShiftRightMaskUInt8(8 - height); // mask for bottom most pixels
  do
  {
    seekData(address);
    address += width;
    mode &= ~((1 << dbfExtraRow));
    if (yshift != 1 && displayrow < (HEIGHT / 8 - 1)) mode |= (1 << dbfExtraRow);
    uint8_t rowmask = 0xFF;
    if (renderheight < 8) rowmask = lastmask;
    wait();
    for (uint8_t c = 0; c < renderwidth; c++)
    {
      uint8_t bitmapbyte = readUnsafe();
      if (mode & (1 << dbfReverseBlack)) bitmapbyte ^= rowmask;
      uint8_t maskbyte = rowmask;
      if (mode & (1 << dbfWhiteBlack)) maskbyte = bitmapbyte;
      if (mode & (1 << dbfBlack)) bitmapbyte = 0;
      uint16_t bitmap = multiplyUInt8(bitmapbyte, yshift);
      if (mode & (1 << dbfMasked))
      {
        wait();
        uint8_t tmp = readUnsafe();
        if ((mode & dbfWhiteBlack) == 0) maskbyte = tmp;
      }
      uint16_t mask = multiplyUInt8(maskbyte, yshift);
      if (displayrow >= 0)
      {
        uint8_t pixels = bitmap;
        uint8_t display = Arduboy2Base::sBuffer[displayoffset];
        if ((mode & (1 << dbfInvert)) == 0) pixels ^= display;
        pixels &= mask;
        pixels ^= display;
        Arduboy2Base::sBuffer[displayoffset] = pixels;
      }
      if (mode & (1 << dbfExtraRow))
      {
        uint8_t display = Arduboy2Base::sBuffer[displayoffset + WIDTH];
        uint8_t pixels = bitmap >> 8;
        if ((mode & dbfInvert) == 0) pixels ^= display;
        pixels &= mask >> 8;
        pixels ^= display;
        Arduboy2Base::sBuffer[displayoffset + WIDTH] = pixels;
      }
      (mode & (1 << dbfFlip)) ? displayoffset-- : displayoffset++;
    }
    displayoffset += WIDTH;
    (mode & (1 << dbfFlip)) ?  displayoffset += renderwidth : displayoffset -= renderwidth;
    displayrow ++;
    renderheight -= 8;
    readEnd();
  } while (renderheight > 0);
#endif
}

void FX::setFrame(uint24_t frame, uint8_t repeat) //~22 bytes
{
 #ifdef ARDUINO_ARCH_AVR
  FrameControl* ctrl = &frameControl;
  asm volatile(
    "ldi    r30, lo8(%[ctrl]) \n"
    "ldi    r31, hi8(%[ctrl]) \n"
    "st     z,   %A[frame]    \n" // start
    "std    z+1, %B[frame]    \n"
    "std    z+2, %C[frame]    \n"
    "std    z+3, %A[frame]    \n" // current
    "std    z+4, %B[frame]    \n"
    "std    z+5, %C[frame]    \n"
    "std    z+6, %A[repeat]   \n" // repeat
    "std    z+7, %A[repeat]   \n" // count
    :
    : [ctrl]   ""  (ctrl),
      [frame]  "r" (frame),
      [repeat] "r" (repeat)
    : //"r30", "r31"
  );
 #else
  frameControl.start   = frame;
  frameControl.current = frame;
  frameControl.repeat  = repeat;
  frameControl.count   = repeat;
  #endif
}

uint8_t FX::drawFrame() // ~66 bytes
{
  uint24_t frame = drawFrame(frameControl.current);
  uint8_t moreFrames;
 #ifdef ARDUINO_ARCH_AVR
  FrameControl* ctrl = &frameControl;
  asm volatile(
    "ldi    r30, lo8(%[ctrl])   \n"
    "ldi    r31, hi8(%[ctrl])   \n"
    "ldd    r0, z+7             \n" // frameControl.count
    "mov    %[more], r0         \n" // moreFrames = (frame != 0) | frameControl.count;
    "or     %[more], %A[frame]  \n"
    "or     %[more], %B[frame]  \n"
    "or     %[more], %C[frame]  \n"
    "tst    r0                  \n"
    "breq   1f                  \n" // skip frameControl.count == 0
    "                           \n"
    "dec    r0                  \n"
    "std    z+7, r0             \n" // frameControl.count--
    "rjmp   3f                  \n" // return
    "1:                         \n"
    "ldd    r0, z+6             \n" // frameControl.count = frameControl.repeat
    "std    z+7, r0             \n"
    "tst    %[more]             \n" //
    "brne   2f                  \n" // if if moreFrames skip
    "                           \n"
    "ld     %A[frame], z        \n" // frame = frameControl.start
    "ldd    %B[frame], z+1      \n"
    "ldd    %C[frame], z+2      \n"
    "2:                         \n"
    "std    z+3, %A[frame]      \n" // frameControl.current = frame
    "std    z+4, %B[frame]      \n"
    "std    z+5, %C[frame]      \n"
    "3:                         \n"
    : [more]  "=&r" (moreFrames)
    : [ctrl]  ""   (ctrl),
      [frame] "r"  (frame)
    : "r0", "r30", "r31"
    );
 #else
  moreFrames = (frame != 0) | frameControl.count;
  if (frameControl.count > 0)
  {
    frameControl.count--;
  }
  else
  {
    frameControl.count = frameControl.repeat;
    if (!moreFrames) frame = frameControl.start;
    frameControl.current = frame;
  }
 #endif
  return moreFrames;
}

uint24_t FX::drawFrame(uint24_t address) //~94 bytes
{
  FrameData f;
 #ifdef ARDUINO_ARCH_AVR
  asm volatile (
    "push   r6                  \n"
    "push   r7                  \n"
    "push   r8                  \n"
    "push   r14                 \n"
    "push   r16                 \n"
    "0:                         \n"
    "movw   r6, %A[addr]        \n" //save address for calls
    "mov    r8, %C[addr]        \n"
    "call   %x6                 \n"
    "call   %x1                 \n"
    "movw   r30, r24            \n" //temporary save x
    "call   %x1                 \n"
    "movw   r26, r24            \n" //temporary save y
    "call   %x2                 \n"
    "mov    r20, r24            \n" // bmp address
    "movw   r18, r22            \n"
    "call   %x3                 \n"
    "mov    r16, r24            \n" // frame
    "call   %x4                 \n"
    "mov    r14, r24            \n" // mode
    "movw   r24, r30            \n" // x
    "movw   r22, r26            \n" // y
    "call   %x5                 \n" // drawbitmap
    "movw   %A[addr], r6        \n" // restore address
    "mov    %C[addr], r8        \n"
    "subi   %A[addr], -%[size]  \n"
    "sbci   %B[addr], 0xFF      \n"
    "sbci   %C[addr], 0xFF      \n"
    "                           \n"
    "sbrc   r14, 6              \n" // test next frame
    "rjmp   1f                  \n" // skip end of this frame
    "                           \n"
    "sbrs   r14, 7              \n" // test last frame
    "rjmp   0b                  \n" // loop not last frame
    "                           \n"
    "clr    %A[addr]            \n"
    "clr    %B[addr]            \n"
    "clr    %C[addr]            \n"
    "1:                         \n"
    "pop    r16                 \n"
    "pop    r14                 \n"
    "pop    r8                  \n"
    "pop    r7                  \n"
    "pop    r6                  \n"

  : [addr] "+&r" (address)
  : "" (readPendingUInt16),
    "" (readPendingUInt24),
    "" (readPendingUInt8),
    "" (readEnd),
    "" (drawBitmap),
    "" (seekData),
    [size] "" (sizeof(f))
  : "r18","r19", "r20", "r21", "r25", "r26", "r27", "r30", "r31"
  );
  return address;
 #else
  for(;;)
  {
    seekData(address);
    address += sizeof(f);
    f.x = readPendingUInt16();
    f.y = readPendingUInt16();
    f.bmp = readPendingUInt24();
    f.frame = readPendingUInt8();
    f.mode = readEnd();
    drawBitmap(f.x, f.y, f.bmp, f.frame, f.mode);
    if (f.mode & dbmEndFrame) return address;
    if (f.mode & dbmLastFrame) return 0;
  }
 #endif
}

void FX::readDataArray(uint24_t address, uint8_t index, uint8_t offset, uint8_t elementSize, uint8_t* buffer, size_t length)
{
  seekDataArray(address, index, offset, elementSize);
  readBytesEnd(buffer, length);
}


uint8_t FX::readIndexedUInt8(uint24_t address, uint8_t index)
{
  seekDataArray(address, index, 0, sizeof(uint8_t));
  return readEnd();
}


uint16_t FX::readIndexedUInt16(uint24_t address, uint8_t index)
{
  seekDataArray(address, index, 0, sizeof(uint16_t));
  return readPendingLastUInt16();
}


uint24_t FX::readIndexedUInt24(uint24_t address, uint8_t index)
{
  seekDataArray(address, index, 0, sizeof(uint24_t));
  return readPendingLastUInt24();
}


uint32_t FX::readIndexedUInt32(uint24_t address, uint8_t index)
{
  seekDataArray(address, index, 0, sizeof(uint24_t));
  return readPendingLastUInt32();
}

void FX::displayPrefetch(uint24_t address, uint8_t* target, uint16_t len, bool clear)
{
  seekData(address);
  asm volatile
  (
    "   ldi     r30, lo8(%[sbuf])               \n" // uint8_t* ptr = Arduboy2::sBuffer;
    "   ldi     r31, hi8(%[sbuf])               \n"
    "   ldi     r25, hi8(%[end])                \n"
    "   in      r0, %[spsr]                     \n" // wait(); //for 1st target data recieved (can't enable OLED mid transfer)
    "   sbrs	r0, %[spif]                     \n"
    "   rjmp	.-6                             \n"
    "   cbi     %[csport], %[csbit]             \n" // enableOLED();
    "1:                                         \n" // while (true) {
    "   ld      r0, Z                   ;2      \n" // uint8_t displaydata = *ptr;
    "   in      r24, %[spdr]            ;1  /3  \n" // uint8_t targetdata = SPDR;
    "   out     %[spdr], r0             ;1      \n" // SPDR = displaydata;
    "   cpse    %[clear], r1            ;1-2    \n" // if (clear) displaydata = 0;
    "   mov     r0, r1                  ;1      \n"
    "   st      Z+, r0                  ;2      \n" // *ptr++ = displaydata;
    "   subi    %A[len], 1              ;1      \n" // if (--len >= 0) *target++ = targetdata;
    "   sbci    %B[len], 0              ;1      \n"
    "   brmi    3f                      ;1-2    \n" // branch ahead and back to 2 to burn 4 cycles
    "   nop                             ;1      \n"
    "   st      %a[target]+, r24        ;2  /11 \n"
    "2:                                         \n"
    "   cpi     r30, lo8(%[end])        ;1      \n" // if (ptr >= Arduboy2::sBuffer + WIDTH * HEIGHT / 8) break;
    "   cpc     r31, r25                ;1      \n"
    "   brcs    1b                      ;1-2/4  \n" // }
    "3:                                         \n"
    "   brmi    2b                      ;1-2    \n" // branch only when coming from above brmi
    : [target]   "+e" (target),
      [len]      "+d" (len)
    : [sbuf]     ""   (Arduboy2::sBuffer),
      [end]      ""   (Arduboy2::sBuffer + WIDTH * HEIGHT / 8),
      [clear]    "r" (clear),
      [spsr]     "I" (_SFR_IO_ADDR(SPSR)),
      [spif]     "I" (SPIF),
      [spdr]     "I" (_SFR_IO_ADDR(SPDR)),
      [csport]   "I" (_SFR_IO_ADDR(CS_PORT)),
      [csbit]    "I" (CS_BIT)
    : "r24", "r25", "r30", "r31"
  );
  disableOLED();
  disable();
  SPSR;
}

void FX::display()
{
  enableOLED();
  Arduboy2Base::display();
  disableOLED();
}

void FX::display(bool clear)
{
  enableOLED();
  Arduboy2Base::display(clear);
  disableOLED();
}

void FX::setFont(uint24_t address, uint8_t mode)
{
  font.address = address;
  font.mode = mode;
  seekData(address);
  font.width = readPendingUInt16();
  font.height = readPendingLastUInt16();
}

void FX::setFontMode(uint8_t mode)
{
  font.mode = mode;
}

void FX::setCursor(int16_t x, int16_t y)
{
  cursor.x = x;
  cursor.y = y;
}

void FX::setCursorX(int16_t x)
{
  cursor.x = x;
}

void FX::setCursorY(int16_t y)
{
  cursor.y = y;
}

void FX::setCursorRange(int16_t left, int16_t wrap)
{
  cursor.left = left;
  cursor.wrap = wrap;
}

void FX::setCursorLeft(int16_t left)
{
  cursor.left = left;
}

void FX::setCursorWrap(int16_t wrap)
{
  cursor.wrap = wrap;
}

void FX::drawChar(uint8_t c)
{
  if (c == '\r') return;
  uint8_t mode = font.mode;
  int16_t x = cursor.x;
  int16_t y = cursor.y;
  if (c != '\n')
  {
    drawBitmap(x, y, font.address, c, mode);
    if (mode & dcmProportional)
    {
      seekData(font.address - 256 + c);
      x += readEnd();
    }
    else
    {
      x += font.width;
    }
  }
  if ((c == '\n') || (x >= cursor.wrap))
  {
    x = cursor.left;
    y += font.height;
  }
  setCursor(x,y);
}

void FX::drawString(const uint8_t* buffer)
{
  for(;;)
  {
    uint8_t c = *buffer++;
    if (c) drawChar(c);
    else break;
  }
}

void FX::drawString(const char* str)
{
  FX::drawString((const uint8_t*)str);
}

void FX::drawString(uint24_t address)
{
  for(;;)
  {
    seekData(address++);
    uint8_t c = readEnd();
    if (c) drawChar(c);
    else break;
  }
}

void FX::drawNumber(int16_t n, int8_t digits)
{
  drawNumber((int32_t)n, digits);
}

void FX::drawNumber(uint16_t n, int8_t digits)
{
  drawNumber((uint32_t)n, digits);
}

void FX::drawNumber(int32_t n, int8_t digits)
{
  if (n < 0)
  {
    n = -n;
    drawChar('-');
  }
  else if (digits != 0)
  {
    drawChar(' ');
  }
  drawNumber((uint32_t)n, digits);
}

void FX::drawNumber(uint32_t n, int8_t digits) //
{
  uint8_t buf[33]; //max 32 digits + terminator
  uint8_t *str = &buf[sizeof(buf) - 1];
  *str = '\0';
  do {
    char c = n % 10;
    n /= 10;
    *--str = c + '0';
    if ((digits > 0) && (--digits == 0)) break;
    if ((digits < 0) && (++digits == 0)) break;
  } while(n);
    while (digits > 0) {--digits; *--str = '0';}
    while (digits < 0) {++digits; *--str = ' ';}
  drawString(str);
}
