/*******************************************************************************
    PocketScreen Load App From SD Example
    written by Florian Keller
    last changed 11 Nov 20

    ! Upload with Build Type 'Binary Loader / Menu' !
    
    ! Put the 'apps' folder found in the PocketScreen library folder
    ! into the root directory of your SD card.
    
    Displays a small menu on the screen.
    Select an entry with the buttons on the right side, then load the selected
    app by pressing the button on the left.
    To export a sketch as app, build it with Build Type 'Binary for SD card'
    and move the exported binary file (found in the sketch folder) to the SD.
    Then, add the app to the app list below.

    The menu code is located in the lower 1/4 of flash memory, while the app
    is loaded into the remaining space, starting at address 0x10000.
    While in the app, restarting the device causes it to go back into the menu.

    Depending on the screen position, the 'up' and 'down' buttons might be
    reversed.
*******************************************************************************/

// how many apps do we have?
#define TOTAL_APP_COUNT 3

// paths to apps
char *appPaths[TOTAL_APP_COUNT] = {
    "apps/lines_red.bin",
    "apps/lines_green.bin",
    "apps/lines_blue.bin",
};

// app names to display
char *appNames[TOTAL_APP_COUNT] = {
    "Red Lines",
    "Green Lines",
    "Blue Lines",
};

#include <PocketScreen.h>
PocketScreen pocketscreen = PocketScreen();

// include SdFat
#include <SdFat.h>
SdFat SD;

enum SDError {NO_ERROR, NO_SD_FOUND, INIT_SD_ERROR, FILE_NOT_FOUND};
char *sdErrorStrings[] = {"", "No SD Found", "SD init failed", "File not found"};

// which menu entry is selected?
int appSelected = 0;

// remembers the time when a button was pressed
long lastButtonTime;

// error id
SDError sdError = NO_ERROR;

void setup() {
    pocketscreen.begin();
    pocketscreen.setBitDepth(BitDepth16);
    pocketscreen.setFont(pocketScreen7pt);
}

void loop() {
    // block off any input for 250ms after a button was pressed
    if (millis() > lastButtonTime + 250) {

        // check if button is pressed
        if (!digitalRead(PIN_BUTTON_0) && appSelected > 0) {
            // go up and remember time
            appSelected -= 1;
            lastButtonTime = millis();
        }
        else if (!digitalRead(PIN_BUTTON_2) && appSelected < TOTAL_APP_COUNT - 1) {
            // go down and remember time
            appSelected += 1;
            lastButtonTime = millis();
        }
        else if (!digitalRead(PIN_BUTTON_1)) {
            // load and start app!
            loadApp();
        }
    }

    // display menu
    for (int i = 0; i < TOTAL_APP_COUNT; i++) {
        // selected entry is orange, others are white
        if (i == appSelected)
            pocketscreen.setFontColor(ORANGE_16b, BLACK_16b);
        else
            pocketscreen.setFontColor(WHITE_16b, BLACK_16b);
        pocketscreen.setCursor(0, i * 10);
        pocketscreen.print(appNames[i]);
    }
    
    // print error message
    pocketscreen.setFontColor(RED_16b, BLACK_16b);
    pocketscreen.setCursor(0, 52);
    pocketscreen.print(sdErrorStrings[sdError]);

    // print spaces to override previous text
    pocketscreen.print("          ");
}

// function to load a binary app and start it
void loadApp() {
    // is an sd present?
    if (!pocketscreen.isSDInserted()) {
        sdError = NO_SD_FOUND;
        return;
    }
    // try to init
    if (!SD.begin()) {
        sdError = INIT_SD_ERROR;
        return;
    }
    // try to open file
    File file = SD.open(appPaths[appSelected], FILE_READ);
    if (!file) {
        sdError = FILE_NOT_FOUND;
        return;
    }
    
    // now we can load the app!

    uint32_t address = 64*1024;

    uint32_t pageSizes[] = {8, 16, 32, 64, 128, 256, 512, 1024};
    uint32_t PAGE_SIZE = pageSizes[NVMCTRL->PARAM.bit.PSZ];
    uint32_t PAGES = NVMCTRL->PARAM.bit.NVMP;
    uint32_t MAX_FLASH = PAGE_SIZE * PAGES;
    uint8_t buffer[PAGE_SIZE];
    uint32_t erase_dst_addr = address; // starting address

    // first, erase whatever rests in flash
    while (erase_dst_addr < MAX_FLASH) {
        // Execute "ER" Erase Row
        NVMCTRL->ADDR.reg = erase_dst_addr / 2;
        NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_ER;
        
        while (NVMCTRL->INTFLAG.bit.READY == 0)
            ;
            
        erase_dst_addr += PAGE_SIZE * 4; // Skip a ROW
    }

    // get code size
    uint32_t size = file.fileSize();
    uint32_t *dst_addr = (uint32_t *)(64 * 1024);
    uint8_t *ptr_data = buffer;
    
    // Set automatic page write
    NVMCTRL->CTRLB.bit.MANW = 0;
    // Set first address
    NVMCTRL->ADDR.reg = address / 2;
    
    // write to flash, in pages
    while (size) {
        int amtBytes = file.read(buffer, PAGE_SIZE);
        
        if (size < 64) {
            for (int j = size; j < PAGE_SIZE; j++)
                buffer[j] = 0xFF;
        }
        // Execute "PBC" Page Buffer Clear
        NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_PBC;
        while (NVMCTRL->INTFLAG.bit.READY == 0)
            ;

        // Fill page buffer
        uint32_t i;
        for (i = 0; i < (PAGE_SIZE / 4) && i < size; i++) {
            dst_addr[i] = ((uint32_t *)buffer)[i];
        }

        // Execute "WP" Write Page
        NVMCTRL->CTRLA.reg = NVMCTRL_CTRLA_CMDEX_KEY | NVMCTRL_CTRLA_CMD_WP;

        while (NVMCTRL->INTFLAG.bit.READY == 0)
            ;

        // Advance to next page
        dst_addr += i;
        if (size > PAGE_SIZE)
            size -= PAGE_SIZE;
        else
            size = 0;
    }

    // close SPI since it can interfere when jumping to the app code
    SPI.end();
    SPI1.end();

    // jump to the app code
    __set_MSP(*(uint32_t *) 0x10000);
    SCB->VTOR = ((uint32_t) 0x10000 & SCB_VTOR_TBLOFF_Msk);
    void (*appStart)(void);
    appStart = (void (*)(void))(unsigned *)(*(unsigned *)(0x10000 + 4));
    appStart();
}
