/*
 * @file
 * @brief - eForth1 main program
 */
///
///> display eForth system information
///
#include "eforth_core.h"
#include "eForth1.h"

void _stat(U8 *ram, int sz, Stream *io) {
    LOG_H("\nROM_SZ=x",  sz);
    LOG_H(", RAM_SZ=x",  FORTH_RAM_SZ);
    LOG_V(", Addr=",     (U16)sizeof(IU)*8);
    LOG_V("-bit, CELL=", CELLSZ);
    LOG("-byte\nMemory MAP:");
    LOG_H("\n  ROM  :x0000+", FORTH_ROM_SZ);
    LOG_H("\n  VAR  :x", FORTH_UVAR_ADDR);  LOG_H("+", FORTH_UVAR_SZ);  LOG("  <=> EEPROM");
    LOG_H("\n  DIC  :x", FORTH_DIC_ADDR);   LOG_H("+", FORTH_DIC_SZ);   LOG(" <=> EEPROM");
    LOG_H("\n  STACK:x", FORTH_STACK_ADDR); LOG_H("+", FORTH_STACK_SZ);
    LOG_H("\n  TIB  :x", FORTH_TIB_ADDR);   LOG_H("+", FORTH_TIB_SZ);
    LOG_H("\n  ROOF :x", FORTH_MAX_ADDR);
#if ARDUINO
    extern U8 *__flp;                         ///< freelist
    U16 h = (uintptr_t)&__flp;                ///< start of heap
    U16 s = (uintptr_t)&h + sizeof(U16);      ///< end of heap
    LOG_H(" [heap=x", h);
    LOG_V("--> ", s - h);
    LOG_H(" bytes <--auto=x", s); LOG("]");
#endif // ARDUINO
}
///
///> EEPROM interface
///
#if ARDUINO
#include <EEPROM.h>
#else  // !ARDUINO
class MockPROM                                ///< mock EEPROM access class
{
    U8 _prom[FORTH_UVAR_SZ + FORTH_DIC_SZ];   ///< mock EEPROM storage
public:
    U8   read(U16 idx)         { return _prom[idx]; }
    void update(U16 idx, U8 v) { _prom[idx] = v; }
};
MockPROM EEPROM;                              ///> fake Arduino EEPROM unit
#endif // ARDUINO
///
///> EEPROM Save/Load
///
int ef_save(U8 *ram)
{
    int pidx = sizeof(DU) * 2;        ///< pointer to vCP (i.e. HERE)
    IU  here = ((IU)ram[pidx] << 8) | ram[pidx+1];
    IU  sz   = here - FORTH_RAM_ADDR; ///< uvar + colon words
    for (IU i=0; i < sz; i++) {
        EEPROM.update(i, ram[i]);     /// * store dictionary byte-by-byte
    }
    return sz;
}
int ef_load(U8 *ram)
{
    int pidx = sizeof(DU) * 2;        ///< CP addr in EEPROM (aka HERE)
    IU  vCP  = ((IU)EEPROM.read(pidx)<<8) | EEPROM.read(pidx+1);
    IU  sz   = vCP - FORTH_RAM_ADDR;
    if (!vCP || sz > FORTH_DIC_SZ) return 0; /// * prevent garbage in EEPROM

    for (IU i=0; i < sz; i++) {
        ram[i] = EEPROM.read(i);      /// * retrieve dictionary byte-by-byte
    }
    return sz;
}
///
///> Forth ROM image (eforth_rom.c, generated by eforth_asm.c)
///
extern U32    forth_rom[];
extern U32    forth_rom_sz;                ///< actual size of ROM
///
///> Forth managed memory block
/// @note: dynamic allocation is usually a bad idea for MCU
///    1) uses 4 more bytes of RAM (freelist),
///    2) uses ~700 bytes of Flash (allocator),
///    3) does prevent IDE complaints about low memory in compile time
///
static U8     forth_ram[FORTH_RAM_SZ];     ///< RAM pointer (malloc)

#if ARDUINO
static Stream *io;                         ///< IO stream (Serial Monito///
///> setup (called by Arduino setup)
///
void ef_setup(const char *code, Stream &io_stream)
{
    io = &io_stream;

    vm_init((PGM_P)forth_rom, forth_ram, io, code);
    _stat(forth_ram, forth_rom_sz, io);
}
///
///> VM outer interpreter proxy
///
void ef_run()
{
    vm_outer();
}
///
///> VM RAM space
///
char *ef_ram(int i) {
    return (char*)&forth_ram[i - FORTH_RAM_ADDR];
}

#else  // !ARDUINO
const char code[] =
    "words\n"
    "123 456\n"
    "+\n"
    "0 CALL\n"
    "123 456\n"
    "1 CALL\n"
    "=\n";
///
///> main to support C development debugging
///
void my_dot() {
    printf(" => my_dot()\n");
}

void my_add() {
    int b = vm_pop();
    int a = vm_pop();
    printf(" => my_add(%d, %d)\n", a, b);
    vm_push(a + b);
}

int main(int ac, char* av[]) {
    setvbuf(stdout, NULL, _IONBF, 0);    /// * autoflush (turn STDOUT buffering off)

    _stat(forth_ram, forth_rom_sz, NULL);
    
    vm_cfunc(0, my_dot);                 ///< register C API[0]
    vm_cfunc(1, my_add);                 ///< register C API[1]
    
    vm_init((char*)forth_rom, forth_ram, NULL, code);
    vm_outer();

    return 0;
}
#endif // ARDUINO

