/**
 * Copyright © 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl
 *
 */
#include "sched.h"
#include "malloc.h"
#include <assert.h>
#include <stdio.h>
#include "common/spew.h"

// Fib example

magic_t pfib_magic = "pfib magic";
struct pfib_frame {
    magic_t *magic;
    size_t n;
    size_t sum;
};
static struct pfib_frame *make_pfib_frame(size_t n) {
    struct pfib_frame *MALLOC(result);
    *result = (struct pfib_frame){.magic = &pfib_magic,
                                  .n     = n,
                                  .sum   = 0};
    spew(SPEW_DEBUG, "%p n=%lu", result, result->n);
    return result;
}
static struct pfib_frame *cast_pfib_frame(void *af) {
    assert(af);
    struct pfib_frame *pf = af;
    assert(pf->magic == &pfib_magic);
    return pf;
}
struct frame *pfib(struct frame *frame, void *app_frame);
static struct frame *pfib2(struct frame *frame, void *app_frame);
static struct frame *pfib3(struct frame *frame, void *app_frame);
static struct frame *pfib4(struct frame *frame, void *app_frame);
static void pfib_return_inlet(void *parent_app_frame, size_t result) {
    struct pfib_frame *pf = cast_pfib_frame(parent_app_frame);
    pf->sum += result;
}
struct frame *pfib(struct frame *frame, void *app_frame) {
    struct pfib_frame *f = cast_pfib_frame(app_frame);
    spew(SPEW_DEBUG, "fib(%lu)", f->n);
    if (f->n < 2) {
        //sleep((unsigned int)f->n);
        size_t r = f->n;
        free(f);
        return sched_return(frame, r);
    } else {
        struct pfib_frame *subf = make_pfib_frame(f->n-1);
        return pfib(sched_spawn(frame, pfib2, subf, pfib_return_inlet), subf);
    }
}
static struct frame *pfib2(struct frame *frame, void *app_frame) {
    struct pfib_frame *f = cast_pfib_frame(app_frame);
    struct pfib_frame *subf = make_pfib_frame(f->n-2);
    return pfib(sched_spawn(frame, pfib3, subf, pfib_return_inlet), subf);
}
static struct frame *pfib3(struct frame *frame, void *app_frame __attribute__((unused))) {
    return sched_sync(frame, pfib4);
}
static struct frame *pfib4(struct frame *frame, void *app_frame) {
    struct pfib_frame *f = cast_pfib_frame(app_frame);
    size_t sum = f->sum;
    free(f);
    return sched_return(frame, sum);
}

static size_t fib(size_t n) {
    struct pfib_frame *pf = make_pfib_frame((size_t)n);
    size_t fn = prun(4, pfib, pf);
    return fn;
}

int main(int argc, char *argv[]) {
    if (argc == 1) {
        assert(0 == fib(0));
        assert(1 == fib(1));
        assert(1 == fib(2));
        assert(2 == fib(3));
        assert(3 == fib(4));
        assert(5 == fib(5));
        assert(8 == fib(6));
        assert(6765 == fib(20));
    } else {
        int n = atoi(argv[1]);
        printf("fib(%u)=%lu\n", n, fib((size_t)n));
    }
    return 0;
}
