/**
 * \file
 * \brief Distops delete step implementation
 * from (usr/monitor/capops/deletestep.c)
 */

/*
 * Copyright (c) 2016, ETH Zurich.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, Universitaetsstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

#include "internal.h"
#include "distops/invocations.h"
#include "distops/caplock.h"
#include "distops/debug.h"
#include <aos/aos.h>
#include <aos/event_queue.h>
#include <aos/slot_alloc.h>

#include "mem_alloc.h" // for aos_ram_free()

#include "deletestep.h"

struct delete_st {
    struct delete_queue_node qn;
    struct event_queue_node lock_qn;
    struct domcapref capref;
    struct capability cap;
    struct capref newcap;
    void *result_handler;
    bool wait;
    void *st;
};

static struct event_queue trigger_queue;
static bool triggered;
static bool enqueued;
static int suspended;
static struct event_queue_node trigger_qn;
static struct event_closure step_closure;
static struct event_queue_node caplock_qn;
static struct delete_st delete_step_st;
static struct capref delcap;
static struct event_queue delete_queue;
static struct delete_queue_node *pending_head, *pending_tail;

static void delete_steps_cont(void *st);
static void delete_steps_clear(void *st);
static void delete_queue_notify(void);

struct waitset*
delete_steps_get_waitset(void)
{
    return delete_queue.waitset;
}

void
delete_steps_init(struct waitset *ws)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    errval_t err;

    struct waitset *myws = delete_steps_get_waitset();
    if (myws != NULL) {
        DEBUG_CAPOPS("delete stepping already initialized with ws=%p, doing nothing\n", myws);
        return;
    }

    event_queue_init(&trigger_queue, ws, EVENT_QUEUE_CONTINUOUS);
    triggered = false;
    enqueued = false;
    suspended = 0;
    step_closure = MKCLOSURE(delete_steps_cont, NULL);

    event_queue_init(&delete_queue, ws, EVENT_QUEUE_CONTINUOUS);
    pending_head = pending_tail = NULL;

    delete_step_st.wait = false;
    delete_step_st.result_handler = NULL;
    err = slot_alloc(&delcap);
    PANIC_IF_ERR(err, "allocating delete_steps slot");
    delete_step_st.capref = get_cap_domref(delcap);
    err = slot_alloc(&delete_step_st.newcap);
    PANIC_IF_ERR(err, "allocating delete_steps new cap slot");
}

void
delete_steps_trigger(void)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    if (!triggered) {
        triggered = true;
        if (!suspended && !enqueued) {
            event_queue_add(&trigger_queue, &trigger_qn, step_closure);
            enqueued = true;
        }
    }
}

void
delete_steps_pause(void)
{
    DEBUG_CAPOPS("%s: called from %p\n", __FUNCTION__,
            __builtin_return_address(0));
    suspended++;
}

void
delete_steps_resume(void)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    assert(suspended > 0);
    suspended--;
    if (!suspended) {
        DEBUG_CAPOPS("%s: !suspended, continuing\n", __FUNCTION__);
        event_queue_add(&trigger_queue, &trigger_qn, step_closure);
        enqueued = true;
    }
}

static void
delete_steps_delete_result(errval_t status, void *st)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    assert(err_is_ok(status));
    delete_steps_resume();
}

static void
delete_steps_cont(void *st)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    errval_t err;
    assert(triggered);
    assert(enqueued);
    enqueued = false;
    if (suspended) {
        DEBUG_CAPOPS("%s: suspended (%d); return\n", __FUNCTION__, suspended);
        return;
    }

    err = monitor_delete_step(delcap);
    if (err_no(err) == SYS_ERR_CAP_LOCKED) {
        // XXX
        DEBUG_CAPOPS("%s: cap locked\n", __FUNCTION__);
        caplock_wait(get_cap_domref(NULL_CAP), &caplock_qn, step_closure);
    }
    if (err_no(err) == SYS_ERR_DELETE_LAST_OWNED) {
        DEBUG_CAPOPS("%s: deleting last owned\n", __FUNCTION__);
        assert(!delete_step_st.result_handler);
        delete_step_st.result_handler = delete_steps_delete_result;
        delete_step_st.st = NULL;
        USER_PANIC("capops_delete_int() NYI");
        //capops_delete_int(&delete_step_st);
    }
    else if (err_no(err) == SYS_ERR_CAP_NOT_FOUND) {
        DEBUG_CAPOPS("%s: cap not found, starting clear step\n", __FUNCTION__);
        delete_steps_clear(st);
    }
    else if (err_is_fail(err)) {
        USER_PANIC_ERR(err, "while performing delete steps");
    }
    else {
        if (err_no(err) == SYS_ERR_RAM_CAP_CREATED) {
            DEBUG_CAPOPS("%s: sending reclaimed RAM to memserv.\n", __FUNCTION__);
            aos_ram_free(delcap);
        }
        if (!enqueued) {
            DEBUG_CAPOPS("%s: !enqueued, adding to queue\n", __FUNCTION__);
            event_queue_add(&trigger_queue, &trigger_qn, step_closure);
            enqueued = true;
        }
    }
    DEBUG_CAPOPS("%s: done\n", __FUNCTION__);
}

static void
delete_steps_clear(void *st)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    errval_t err;
    while (true) {
        err = monitor_clear_step(delcap);
        if (err_no(err) == SYS_ERR_CAP_NOT_FOUND) {
            break;
        }
        else if (err_is_fail(err)) {
            USER_PANIC_ERR(err, "while performing clear steps");
        }
        else if (err_no(err) == SYS_ERR_RAM_CAP_CREATED) {
            DEBUG_CAPOPS("%s: sending reclaimed RAM to memserv.\n", __FUNCTION__);
            aos_ram_free(delcap);
        }
    }
    DEBUG_CAPOPS("%s: finished, calling delete_queue_notify\n", __FUNCTION__);
    triggered = false;
    delete_queue_notify();
}

void
delete_queue_wait(struct delete_queue_node *qn, struct event_closure cont)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    // enqueue the node in the list of pending events
    if (!pending_head) {
        assert(!pending_tail);
        pending_head = pending_tail = qn;
        qn->next = NULL;
    }
    else {
        assert(pending_tail);
        assert(!pending_tail->next);
        pending_tail->next = qn;
        pending_tail = qn;
        qn->next = NULL;
    }
    qn->cont = cont;

    // trigger "stepping" mode of the delete/revoke state machine
    delete_steps_trigger();
}

static void
delete_queue_notify(void)
{
    DEBUG_CAPOPS("%s\n", __FUNCTION__);
    // this should only be triggered when the "stepping" mode of the
    // delete/revoke state machine completes, so a notify without any
    // operations pending would be very strange and is probably a bug
    assert(pending_head);
    assert(pending_tail);

    // extract the contents of the queue of currently pending delete operations
    struct delete_queue_node *curr = pending_head;
    pending_head = pending_tail = NULL;

    // put them all in the event queue so they are executed
    for ( ; curr; curr = curr->next) {
        DEBUG_CAPOPS("%s: adding %p to ev q.\n", __FUNCTION__, curr);
        event_queue_add(&delete_queue, &curr->qn, curr->cont);
    }
}

