#include "pxt.h"
#include <stdarg.h>

PXT_ABI(__aeabi_dadd)
PXT_ABI(__aeabi_dcmplt)
PXT_ABI(__aeabi_dcmpgt)
PXT_ABI(__aeabi_dsub)
PXT_ABI(__aeabi_ddiv)
PXT_ABI(__aeabi_dmul)

extern "C" void target_panic(int error_code) {
#if !MICROBIT_CODAL
    // wait for serial to flush
    sleep_us(300000);
#endif
    microbit_panic(error_code);
}

#if !MICROBIT_CODAL
extern "C" void target_reset() {
    microbit_reset();
}
#endif

uint32_t device_heap_size(uint8_t heap_index); // defined in microbit-dal

namespace pxt {

MicroBit uBit;
MicroBitEvent lastEvent;

void platform_init() {
    microbit_seed_random();    
    int seed = microbit_random(0x7fffffff);
    DMESG("random seed: %d", seed);
    seedRandom(seed);
}

void initMicrobitGC() {
    uBit.init();
    if (device_heap_size(1) > NON_GC_HEAP_RESERVATION + 4)
        gcPreAllocateBlock(device_heap_size(1) - NON_GC_HEAP_RESERVATION);
}

void platform_init();
void usb_init();

struct FreeList {
    FreeList *next;
};

void dispatchForeground(MicroBitEvent e, void *action) {
    lastEvent = e;
    auto value = fromInt(e.value);
    runAction1((Action)action, value);
}

void deleteListener(MicroBitListener *l) {
    if (l->cb_param == (void (*)(MicroBitEvent, void *))dispatchForeground) {
        decr((Action)(l->cb_arg));
        unregisterGCPtr((Action)(l->cb_arg));
    }
}

static void initCodal() {
    // TODO!!!
#ifndef MICROBIT_CODAL
    uBit.messageBus.setListenerDeletionCallback(deleteListener);
#endif

    // repeat error 4 times and restart as needed
    microbit_panic_timeout(4);
}

void dumpDmesg() {}

// ---------------------------------------------------------------------------
// An adapter for the API expected by the run-time.
// ---------------------------------------------------------------------------

void registerWithDal(int id, int event, Action a, int flags) {
    uBit.messageBus.ignore(id, event, dispatchForeground);
    uBit.messageBus.listen(id, event, dispatchForeground, a);
    incr(a);
    registerGCPtr(a);
}

void fiberDone(void *a) {
    decr((Action)a);
    unregisterGCPtr((Action)a);
    release_fiber();
}

void releaseFiber() {
    release_fiber();
}

void sleep_ms(unsigned ms) {
    fiber_sleep(ms);
}

void sleep_us(uint64_t us) {
#if MICROBIT_CODAL
    target_wait_us(us);
#else
    wait_us(us);
#endif
}

void forever_stub(void *a) {
    while (true) {
        runAction0((Action)a);
        fiber_sleep(20);
    }
}

void runForever(Action a) {
    if (a != 0) {
        incr(a);
        registerGCPtr(a);
        create_fiber(forever_stub, (void *)a);
    }
}

void runInParallel(Action a) {
    if (a != 0) {
        incr(a);
        registerGCPtr(a);
        create_fiber((void (*)(void *))runAction0, (void *)a, fiberDone);
    }
}

void waitForEvent(int id, int event) {
    fiber_wait_for_event(id, event);
}

void initRuntime() {
    initCodal();
    platform_init();
}

//%
unsigned afterProgramPage() {
    unsigned ptr = (unsigned)&bytecode[0];
    ptr += programSize();
    ptr = (ptr + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
    return ptr;
}

int current_time_ms() {
    return system_timer_current_time();
}

static void logwriten(const char *msg, int l) {
    uBit.serial.send((uint8_t *)msg, l);
}

static void logwrite(const char *msg) {
    logwriten(msg, strlen(msg));
}

static void writeNum(char *buf, uint32_t n, bool full) {
    int i = 0;
    int sh = 28;
    while (sh >= 0) {
        int d = (n >> sh) & 0xf;
        if (full || d || sh == 0 || i) {
            buf[i++] = d > 9 ? 'A' + d - 10 : '0' + d;
        }
        sh -= 4;
    }
    buf[i] = 0;
}

static void logwritenum(uint32_t n, bool full, bool hex) {
    char buff[20];

    if (hex) {
        writeNum(buff, n, full);
        logwrite("0x");
    } else {
        itoa(n, buff);
    }

    logwrite(buff);
}

void vdebuglog(const char *format, va_list ap) {
    const char *end = format;

    while (*end) {
        if (*end++ == '%') {
            logwriten(format, end - format - 1);
            uint32_t val = va_arg(ap, uint32_t);
            switch (*end++) {
            case 'c':
                logwriten((const char *)&val, 1);
                break;
            case 'd':
                logwritenum(val, false, false);
                break;
            case 'x':
                logwritenum(val, false, true);
                break;
            case 'p':
            case 'X':
                logwritenum(val, true, true);
                break;
            case 's':
                logwrite((char *)(void *)val);
                break;
            case '%':
                logwrite("%");
                break;
            default:
                logwrite("???");
                break;
            }
            format = end;
        }
    }
    logwriten(format, end - format);
    logwrite("\n");
}

void debuglog(const char *format, ...) {
    va_list arg;
    va_start(arg, format);
    vdebuglog(format, arg);
    va_end(arg);
}

void sendSerial(const char *data, int len) {
    logwriten(data, len);
}

ThreadContext *getThreadContext() {
    if (!currentFiber)
        return NULL;
    return (ThreadContext *)currentFiber->user_data;
}

void setThreadContext(ThreadContext *ctx) {
    currentFiber->user_data = ctx;
}

#if !MICROBIT_CODAL
#define tcb_get_stack_base(tcb) (tcb).stack_base
#endif

static void *threadAddressFor(Fiber *fib, void *sp) {
    if (fib == currentFiber)
        return sp;

    return (uint8_t *)sp + ((uint8_t *)fib->stack_top - (uint8_t *)tcb_get_stack_base(fib->tcb));
}

void gcProcessStacks(int flags) {
    // check scheduler is initialized
    if (!currentFiber) {
        // make sure we allocate something to at least initalize the memory allocator
        void *volatile p = xmalloc(1);
        xfree(p);
        return;
    }

#ifdef MICROBIT_GET_FIBER_LIST_SUPPORTED
    for (Fiber *fib = get_fiber_list(); fib; fib = fib->next) {
        auto ctx = (ThreadContext *)fib->user_data;
        if (!ctx)
            continue;
        for (auto seg = &ctx->stack; seg; seg = seg->next) {
            auto ptr = (TValue *)threadAddressFor(fib, seg->top);
            auto end = (TValue *)threadAddressFor(fib, seg->bottom);
            if (flags & 2)
                DMESG("RS%d:%p/%d", cnt++, ptr, end - ptr);
            // VLOG("mark: %p - %p", ptr, end);
            while (ptr < end) {
                gcProcess(*ptr++);
            }
        }
    }
#else
    int numFibers = list_fibers(NULL);
    Fiber **fibers = (Fiber **)xmalloc(sizeof(Fiber *) * numFibers);
    int num2 = list_fibers(fibers);
    if (numFibers != num2)
        oops(12);
    int cnt = 0;

    for (int i = 0; i < numFibers; ++i) {
        auto fib = fibers[i];
        auto ctx = (ThreadContext *)fib->user_data;
        if (!ctx)
            continue;
        for (auto seg = &ctx->stack; seg; seg = seg->next) {
            auto ptr = (TValue *)threadAddressFor(fib, seg->top);
            auto end = (TValue *)threadAddressFor(fib, seg->bottom);
            if (flags & 2) {
                DMESG("RS%d:%p/%d", cnt, ptr, end - ptr);
                cnt++;
            }
            // VLOG("mark: %p - %p", ptr, end);
            while (ptr < end) {
                gcProcess(*ptr++);
            }
        }
    }
    xfree(fibers);
#endif
}

} // namespace pxt