/**
 * \file
 * \brief
 */

/*
 * Copyright (c) 2007, 2008, 2010, 2011, 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, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group.
 */

#include <stdio.h>
#include <string.h>
#include <errors/errno.h>
#include <aos/types.h>
#include <barrelfish_kpi/syscalls.h>
#include <barrelfish_kpi/distcaps.h>
#include <capabilities.h>
#include <cap_predicates.h>
#include <mdb/mdb.h>
#include <mdb/mdb_tree.h>
#include <mdb/mdb_counters.h>
//#include <mdb/types.h>

static void
mdb_set_cte_relations(struct cte *cte, uint8_t relations, uint8_t mask)
{
    if (mask & RRELS_COPY_BIT) {
        cte->mdbnode.remote_copies = (relations & RRELS_COPY_BIT) != 0;
    }
    if (mask & RRELS_ANCS_BIT) {
        cte->mdbnode.remote_ancs = (relations & RRELS_ANCS_BIT) != 0;
    }
    if (mask & RRELS_DESC_BIT) {
        cte->mdbnode.remote_descs = (relations & RRELS_DESC_BIT) != 0;
    }
}

void mdb_set_relations(struct cte *cte, uint8_t relations, uint8_t mask)
{
    assert(cte != NULL);

    bool old_copies = cte->mdbnode.remote_copies;
    bool old_ancs = cte->mdbnode.remote_ancs;
    bool old_descs = cte->mdbnode.remote_descs;

    mdb_set_cte_relations(cte, relations, mask);

    if (cte->mdbnode.remote_copies == old_copies
        && cte->mdbnode.remote_ancs == old_ancs
        && cte->mdbnode.remote_descs == old_descs)
    {
        // nothing changed
        return;
    }

    // find all copies and set attributes on them
    for (struct cte *next = mdb_successor(cte);
         next && is_copy(&next->cap, &cte->cap);
         next = mdb_successor(next))
    {
#ifndef NDEBUG
        if (!(next->mdbnode.remote_copies == old_copies
              && next->mdbnode.remote_ancs == old_ancs
              && next->mdbnode.remote_descs == old_descs))
        {
            printf("WARNING: rrels out of sync! %p:%d%d%d vs %p:%d%d%d\n",
                   cte, old_copies, old_ancs, old_descs,
                   next, next->mdbnode.remote_copies,
                   next->mdbnode.remote_ancs, next->mdbnode.remote_descs);
        }
#endif
        mdb_set_cte_relations(next, relations, mask);
    }

    for (struct cte *prev = mdb_predecessor(cte);
         prev && is_copy(&prev->cap, &cte->cap);
         prev = mdb_predecessor(prev))
    {
#ifndef NDEBUG
        if (!(prev->mdbnode.remote_copies == old_copies
              && prev->mdbnode.remote_ancs == old_ancs
              && prev->mdbnode.remote_descs == old_descs))
        {
            printf("WARNING: rrels out of sync! %p:%d%d%d vs %p:%d%d%d\n",
                   cte, old_copies, old_ancs, old_descs,
                   prev, prev->mdbnode.remote_copies,
                   prev->mdbnode.remote_ancs, prev->mdbnode.remote_descs);
        }
#endif
        mdb_set_cte_relations(prev, relations, mask);
    }
}

/// Check if #cte has any descendants
uint64_t mdb_has_descendants_count;
bool has_descendants(struct cte *cte)
{
    mdb_has_descendants_count++;
    assert(cte != NULL);

    struct cte *next = mdb_find_greater(&cte->cap, false);
    return next && get_type_root(next->cap.type) == get_type_root(cte->cap.type)
        && get_address(&next->cap) < get_address(&cte->cap) + get_size(&cte->cap);
}

bool has_ancestors(struct cte *cte) {
    return mdb_find_ancestor(cte) != NULL;
}

uint64_t mdb_find_ancestor_count = 0;
struct cte *mdb_find_ancestor(struct cte *cte)
{
    assert(cte != NULL);
    struct cte *result = NULL;

    mdb_find_ancestor_count++;
#if 0
    // XXX: this check should have its own predicate
    if (!get_address(&cte->cap) && !get_size(&cte->cap)) {
        return false;
    }
#endif

    result = mdb_find_less(&cte->cap, false);
    if (result
        && get_type_root(result->cap.type) == get_type_root(cte->cap.type)
        && get_address(&result->cap) + get_size(&result->cap)
           >= get_address(&cte->cap) + get_size(&cte->cap))
    {
        return result;
    }

    // cte is preceded in the ordering by a non-ancestor. This imples one of
    // two situations:
    //  1) cte has no ancestors
    //  2) cte has ancestors but also has siblings earlier in the
    //     ordering, thus the ancestor cannot have the same base
    //     address as cte.
    // If we query for the zero-length memory region at cte's start
    // address, we will not get cte itself back as the end of our query
    // is at cte's start address.
    // Similarly, we cannot get a sibling of cte that ends where cte
    // starts, as the beginning of our query is not in that sibling's
    // region.
    // Thus we must get its ancestor if present, or no cap at all.
    int find_result;
    result = NULL;
    mdb_find_range(get_type_root(cte->cap.type),
                   get_address(&cte->cap), 0,
                   MDB_RANGE_FOUND_SURROUNDING,
                   &result, &find_result);
    if (find_result != MDB_RANGE_NOT_FOUND) {
        assert(find_result == MDB_RANGE_FOUND_SURROUNDING);
        assert(result);
        assert(get_address(&result->cap) <= get_address(&cte->cap));
        assert(get_address(&result->cap) + get_size(&result->cap)
               >= get_address(&cte->cap) + get_size(&cte->cap));
        // don't cound find_ancestor range queries for range query counter
        mdb_find_range_count--;
    }
    else {
        assert(!result);
    }

    return result;
}

uint64_t mdb_has_copies_count;
/// Checks if #cte has any copies
bool has_copies(struct cte *cte)
{
    mdb_has_copies_count++;
    assert(cte != NULL);

    struct cte *next = mdb_successor(cte);
    // don't count calls to mdb_successor() from has_copies()
    mdb_successor_count--;
    if (next && is_copy(&next->cap, &cte->cap)) {
        return true;
    }

    struct cte *prev = mdb_predecessor(cte);
    // don't count calls to mdb_predecessor() from has_copies()
    mdb_predecessor_count--;
    if (prev && is_copy(&prev->cap, &cte->cap)) {
        return true;
    }

    return false;
}

/**
 * \brief Returns a copy of the #cap
 */
errval_t mdb_get_copy(struct capability *cap, struct capability **ret)
{
    assert(cap != NULL);
    assert(ret != NULL);

    struct cte *cte = mdb_find_equal(cap);
    if (cte) {
        *ret = &cte->cap;
        return SYS_ERR_OK;
    }
    else {
        return SYS_ERR_NO_LOCAL_COPIES;
    }
}

bool mdb_is_sane(void)
{
    if (mdb_check_invariants() != 0) {
        return false;
    }

    // TODO: check following conditon on each element of mdb
    //if ((lvaddr_t)walk < BASE_PAGE_SIZE || walk->cap.type == ObjType_Null) {
    //    return false;
    //}

    return true;
}

/**
 * Place #dest_start in the mapping database in the appropriate location.
 *
 * Look for its relations: copies and descendants and place in accordance.
 * If no relations found, place at the top and set map_head to point to it.
 */
void set_init_mapping(struct cte *dest_start, size_t num)
{
    for (size_t i = 0; i < num; i++) {
        mdb_insert(&dest_start[i]);
    }
}

/// Remove one cap from the mapping database
void remove_mapping(struct cte *cte)
{
    mdb_remove(cte);
}

void mdb_print_counters(coreid_t core)
{
    // create variables that match naming of microbenchmarks
    uint64_t insert_one_count = mdb_insert_count;
    uint64_t remove_one_count =  mdb_remove_count;
    uint64_t iterate_1_count = mdb_predecessor_count + mdb_successor_count;
    uint64_t has_copies_count = mdb_has_copies_count;
    uint64_t has_ancestors_count = mdb_find_ancestor_count;
    uint64_t has_descendants_count = mdb_has_descendants_count;
    uint64_t query_address_count = mdb_find_range_count;

    static uint64_t uid = 0;

    printf("[core %d][%"PRIu64"] MDB operation counts:\n", core, uid);
#define PRINT_OP(op) printf("[core %d][%"PRIu64"] " #op "=%"PRIu64"\n", core, uid, op##_count);
    PRINT_OP(insert_one);
    PRINT_OP(remove_one);
    //PRINT_OP(mdb_find_equal);
    //PRINT_OP(mdb_find_less);
    //PRINT_OP(mdb_find_greater);
    PRINT_OP(iterate_1);
    PRINT_OP(has_copies);
    PRINT_OP(has_ancestors);
    PRINT_OP(has_descendants);
    PRINT_OP(query_address);
#undef PRINT_OP
    uid++;
}
