/*
 * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 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 <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <aos/aos.h>
#include <devif/queue_interface.h>
#include <devif/queue_interface_backend.h>
#include <devif/backends/debug.h>
#include "debug.h"


//#define DQ_ENABLE_HIST
#define HIST_SIZE 128
#define MAX_STR_SIZE 128

/*
 * This is a debugging interface for the device queue interface that
 * can be used with any existing queue. It can be stacked on top 
 * to check for non valid buffer enqueues/deqeues that might happen.
 * A not valid enqueue of a buffer is when the endpoint that enqueues
 * the buffer does not own the buffer. 
 *
 * We keep track of the owned buffers as a list of regions which each
 * contains a list of memory chunks.
 * Each chunk specifies a offset within the region and its length.
 *
 * When a region is registered, we add one memory chunk that describes
 * the whole region i.e. offset=0 length= length of region
 *
 * If a a buffer is enqueued, it has to be contained in one of these
 * memory chunks. The memory chunk is then altered according how
 * the buffer is contained in the chunk. If it is at the beginning or
 * end of the chunk, the offset/length of the chunk is changed accordingly
 * If the buffer is in the middle of the chunk, we split the memory chunk
 * into two new memory chunks that do not contain the buffer.
 *
 * If a buffer is dequeued the buffer is added to the existing memory
 * chunks if possible, otherwise a new memory chunk is added to the
 * list of chunks. If a buffer is dequeued that is in between two
 * memory chunks, the memory chunks are merged to one big chunk.
 * We might fail to find the region id in our list of regions. In this
 * case we add the region with the deqeued offset+length as a size.
 * We can be sure that this region exists since the devq library itself
 * does these checks if the region is known to the endpoint. This simply
 * means the debugging queue on top of the other queue does not have a 
 * consistant view of the registered regions (but the queue below does)
 *
 * When a region is deregistered, the list of chunks has to only 
 * contain a single chunk that descirbes the whole region. Otherwise
 * the call will fail since some of the buffers are still in use. 
 * 
 */

struct memory_ele {
    genoffset_t offset;
    genoffset_t length;
    struct memory_ele* next;
    struct memory_ele* prev;
};

struct memory_list {
    regionid_t rid;
    genoffset_t length;
    // is a region that we did not register ourselves
    bool not_consistent;
    struct memory_ele* buffers;
    struct memory_list* next; // next in list of lists
};

struct operation {
    char str[MAX_STR_SIZE];
    genoffset_t offset;
    genoffset_t length;
};

struct debug_q {
    struct devq my_q;
    struct devq* q;
    struct memory_list* regions; // list of lists
    struct slab_allocator alloc;
    struct slab_allocator alloc_list;
    uint16_t hist_head;
    struct operation history[HIST_SIZE];
};

static void dump_list(struct memory_list* region)
{  
    struct memory_ele* ele = region->buffers;
    int index = 0;
    printf("================================================ \n");
    while (ele != NULL) {
        printf("Idx=%d offset=%lu length=%lu", index, ele->offset,
               ele->length);
        if (ele->prev != NULL) {

            printf(" prev->offset=%lu prev->length=%lu", ele->prev->offset,
                   ele->prev->length);
        }
        printf(" \n");
        ele = ele->next;
        index++;
    }   
    printf("================================================ \n");
}

// checks if there are elements in the list that should be merged
/*
static void check_consistency(struct memory_list* region)
{
    struct memory_ele* ele = region->buffers;
    while (ele->next != NULL) {
        if (ele->offset + ele->length == ele->next->offset) {
            printf("offset=%lu length=%lu \n", ele->offset, ele->length);
            dump_list(region);
            USER_PANIC("Found entry that should be merged \n");
        }
        ele = ele->next;
    }   
}
*/
#ifdef DQ_ENABLE_HIST
static void add_to_history(struct debug_q* q, genoffset_t offset, 
                           genoffset_t length, char* s)
{
    q->history[q->hist_head].offset = offset;
    q->history[q->hist_head].length = length;
    strncpy(q->history[q->hist_head].str, s, MAX_STR_SIZE);
    q->hist_head = (q->hist_head + 1) % HIST_SIZE;
}

static void dump_history(struct debug_q* q)
{
    for (int i = 0; i < HIST_SIZE; i++) {
        printf("offset=%lu length=%lu %s\n", q->history[q->hist_head].offset, 
               q->history[q->hist_head].length, q->history[q->hist_head].str);
       
        q->hist_head = (q->hist_head + 1) % HIST_SIZE;
    }
}
#endif
/*
static bool in_list(struct memory_list* region, genoffset_t offset,
                    genoffset_t length)
{
    struct memory_ele* ele = region->buffers;
    while (ele != NULL) {
        if (offset >= ele->offset && 
            offset + length <= ele->offset+ele->length) {
            return true;
        }
        ele = ele->next;
    }   
    return false;
}
*/



static errval_t debug_register(struct devq* q, struct capref cap,
                               regionid_t rid) 
{
    errval_t err;
    struct debug_q* que = (struct debug_q*) q;
    DEBUG("Register \n");
    struct frame_identity id;
    
    err = frame_identify(cap, &id);
    if (err_is_fail(err)) {
        return err;
    }
        
    // queue of regions is empty
    if (que->regions == NULL) {
        // add the reigon
        err = que->q->f.reg(que->q, cap, rid);
        if (err_is_fail(err)) {
            return err;
        }

        que->regions = slab_alloc(&que->alloc_list);
        assert(que->regions != NULL);

        que->regions->rid = rid;
        que->regions->length = id.bytes;
        que->regions->not_consistent = false;
        que->regions->next = NULL;
        // add the whole regions as a buffer
        que->regions->buffers = slab_alloc(&que->alloc);
        assert(que->regions->buffers != NULL);

        memset(que->regions->buffers, 0, sizeof(que->regions->buffers));
        que->regions->buffers->offset = 0;
        que->regions->buffers->length = id.bytes;
        que->regions->buffers->next = NULL;
        DEBUG("Register rid=%"PRIu32" size=%"PRIu64" \n", rid, 
              id.bytes);
        return SYS_ERR_OK;
    }

    struct memory_list* ele = que->regions;
    while (ele->next != NULL) {
        ele = ele->next;
    }

    err = que->q->f.reg(que->q, cap, rid);
    if (err_is_fail(err)) {
        return err;
    }

    // add the reigon
    ele->next = slab_alloc(&que->alloc_list);
    assert(ele->next != NULL);

    ele = ele->next;
    ele->rid = rid;
    ele->next = NULL;
    ele->length = id.bytes;
    ele->not_consistent = false;
    // add the whole regions as a buffer
    ele->buffers = slab_alloc(&que->alloc);
    assert(ele->buffers != NULL);

    memset(ele->buffers, 0, sizeof(ele->buffers));
    ele->buffers->offset = 0;
    ele->buffers->length = id.bytes;
    ele->buffers->next = NULL;
    DEBUG("Register rid=%"PRIu32" size=%"PRIu64" \n", rid, 
          id.bytes);

    return SYS_ERR_OK;
}

static errval_t debug_deregister(struct devq* q, regionid_t rid) 
{
    DEBUG("Deregister \n");
    struct debug_q* que = (struct debug_q*) q;
    errval_t err;

    struct memory_list* ele = que->regions;
    if (ele == NULL) {
        return DEVQ_ERR_INVALID_REGION_ID;
    }    

    // remove head
    if (ele->rid == rid) {
        // there should only be a single element in the list
        // i.e. the whole region
        if (ele->buffers->offset == 0 &&
            ele->buffers->length == ele->length &&
            ele->buffers->next == NULL) {

            err = que->q->f.dereg(que->q, rid);
            if (err_is_fail(err)) {
                return err;
            }
            que->regions = ele->next;
        
            DEBUG("removed region rid=%"PRIu32" size=%"PRIu64" \n", rid, 
                  ele->length);

            slab_free(&que->alloc, ele->buffers);
            slab_free(&que->alloc_list, ele);

            return SYS_ERR_OK;
        } else {

            DEBUG("Destroy error rid=%d offset=%"PRIu64" length=%"PRIu64" "
                       "should be offset=0 length=%"PRIu64"\n",
                       ele->rid, ele->buffers->offset, 
                       ele->buffers->length, ele->length);
            dump_list(ele);
            return DEVQ_ERR_REGION_DESTROY;
        }   
    }
    
    while (ele->next != NULL) {
        if (ele->next->rid == rid) {
            if (ele->next->buffers->offset == 0 &&
                ele->next->buffers->length == ele->next->length &&
                ele->next->buffers->next == NULL) {
            
                err = que->q->f.dereg(que->q, rid);
                if (err_is_fail(err)) {
                    return err;
                }
                // remove from queue
                struct memory_list* next = ele->next;
                ele->next = ele->next->next;    

                DEBUG("removed region rid=%"PRIu32" size=%"PRIu64" \n", rid, 
                      next->length);
            
                slab_free(&que->alloc, next->buffers);
                slab_free(&que->alloc_list, next);

                return SYS_ERR_OK;
            } else {
                DEBUG("Destroy error rid=%d offset=%"PRIu64" length=%"PRIu64" "
                       "should be offset=0 length=%"PRIu64"\n",
                       ele->next->rid, ele->next->buffers->offset, 
                       ele->next->buffers->length, ele->next->length);
    
                dump_list(ele);
                return DEVQ_ERR_REGION_DESTROY;
            }   
        } else {
            ele = ele->next;
        }
    }
    

    return DEVQ_ERR_INVALID_REGION_ID;
}


static errval_t debug_control(struct devq* q, uint64_t cmd, uint64_t value,
                              uint64_t* result)
{
    DEBUG("control \n");
    struct debug_q* que = (struct debug_q*) q;
    return que->q->f.ctrl(que->q, cmd, value, result);
}


static errval_t debug_notify(struct devq* q)
{
    DEBUG("notify \n");
    struct debug_q* que = (struct debug_q*) q;
    return que->q->f.notify(que->q);
}


// is b1 in bounds of b2?
static bool buffer_in_bounds(genoffset_t offset_b1, genoffset_t len_b1,
                             genoffset_t offset_b2, genoffset_t len_b2)
{
    return (offset_b1 >= offset_b2) && (len_b1 <= len_b2) &&
           ((offset_b1 + len_b1) <= offset_b2 + len_b2);
}

// assumes that the buffer described by offset and length is contained
// in the buffer that is given as a struct
static void remove_split_buffer(struct debug_q* que,
                                struct memory_list* region,
                                struct memory_ele* buffer,
                                genoffset_t offset,
                                genoffset_t length) 
{
    // split the buffer 
    // insert half before the buffer

    DEBUG("enqueue offset=%"PRIu64" length=%"PRIu64" buf->offset=%lu "
          "buf->length %lu \n",
          offset, length, buffer->offset, buffer->length);

    // check if buffer at beginning of region
    if (buffer->offset == offset) {
        buffer->offset += length;
        buffer->length -= length;

        if (buffer->length == 0) {
#ifdef DQ_ENABLE_HIST
            add_to_history(que, offset, length, "enq cut of beginning remove");
#endif
            DEBUG("enqueue remove buffer from list\n");
            // remove
            if (buffer->prev != NULL) {
                buffer->prev->next = buffer->next;
            } else {
                region->buffers = buffer->next;
            }
        
            if (buffer->next != NULL) {
                buffer->next->prev = buffer->prev;
            }
            slab_free(&que->alloc, buffer);
        } else {
#ifdef DQ_ENABLE_HIST
            add_to_history(que, offset, length, "enq cut of beginning");
#endif
        }

        DEBUG("enqueue first cut off begining results in offset=%"PRIu64" "
              "length=%"PRIu64"\n",
              buffer->offset, buffer->length);
        return;
    }

    // check if buffer at end of region
    if ((buffer->offset+buffer->length) == (offset+length)) {
        buffer->length -= length;

        if (buffer->length == 0) {
#ifdef DQ_ENABLE_HIST
            add_to_history(que, offset, length, "enq cut of end remove");
#endif
            // remove
            if (buffer->prev != NULL) {
                buffer->prev = buffer->next;
            }
        
            if (buffer->next != NULL) {
                buffer->next->prev = buffer->prev;
            }
            slab_free(&que->alloc, buffer);
        } else {
#ifdef DQ_ENABLE_HIST
            add_to_history(que, offset, length, "enq cut of end");
#endif
        }
    
        DEBUG("enqueue first cut off end results in offset=%"PRIu64" "
              "length=%"PRIu64"\n",
              buffer->offset, buffer->length);
        return;
    }

    // now if this did not work need to split buffer that contains the
    // enqueued buffer into two buffers (might also result only in one)

    // inset half before buffer
    genoffset_t old_len = buffer->length;

    buffer->length = offset - buffer->offset;  

    struct memory_ele* after = NULL;
    after = slab_alloc(&que->alloc);
    assert(after != NULL);

    memset(after, 0, sizeof(after));
    after->offset = buffer->offset + buffer->length + length;
    after->length = old_len - buffer->length - length;

    // insert after buffer
    after->prev = buffer;
    after->next = buffer->next;

    if (buffer->next != NULL) {
        buffer->next->prev = after;
    }

    buffer->next = after;

#ifdef DQ_ENABLE_HIST
    add_to_history(que, offset, length, "enq split buffer");
#endif

    DEBUG("Split buffer length=%lu to "
          "offset=%"PRIu64" length=%"PRIu64" and "
          "offset=%lu length=%lu \n",
          old_len, 
          buffer->offset, buffer->length,
          after->offset, after->length);

}

/*
 * Inserts a buffer either before or after an existing buffer into the queue
 * Checks for merges of prev/next buffer in the queue
 */
static void insert_merge_buffer(struct debug_q* que,
                                struct memory_list* region,
                                struct memory_ele* buffer,
                                genoffset_t offset,
                                genoffset_t length) 
{
    assert(buffer != NULL);
    assert(region != NULL);

    if (offset >= buffer->offset+buffer->length) {// insert after
        // buffer is on lower boundary
        //
        if (buffer->offset+length == offset) {
            buffer->length += length;
            DEBUG("dequeue merge after "
                  "offset=%"PRIu64" length=%"PRIu64" to offset=%"PRIu64" "
                  "length=%"PRIu64"\n",
                  offset, length, buffer->offset, buffer->length);
            // check other boundary for merge
            if (buffer->next != NULL &&
                (buffer->offset + buffer->length == buffer->next->offset)) {
                buffer->length += buffer->next->length;
                struct memory_ele* next = buffer->next;
                if (buffer->next->next != NULL) {
                    buffer->next = buffer->next->next;
                    buffer->next->next->prev = buffer;
                }

                DEBUG("dequeue merge after more offset=%"PRIu64" "
                      "length=%"PRIu64" to offset=%"PRIu64" length=%"PRIu64" \n ",
                      next->offset, next->length,
                      buffer->offset, buffer->length);
#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert after"
                               " on lower boundary and merge");
#endif
                slab_free(&que->alloc, next);
            } else {
#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert after on lower boundary");
#endif
            }
        } else { 
            // check higher boundary
            if (buffer->next != NULL && 
                buffer->next->offset == offset+length) {

                buffer->next->offset = offset;
                buffer->next->length += length;

                DEBUG("dequeue merge after more offset=%"PRIu64" "
                      "length=%"PRIu64" to offset=%"PRIu64" length=%"PRIu64" \n ",
                      offset, length,
                      buffer->next->offset, buffer->next->length);

#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert after"
                               " on higer boundary");
#endif           
            } else { 
                // buffer->next can be null and the newly inserted buffer
                // is the inserted at the end -> check boundary
                if (buffer->next == NULL && 
                    buffer->offset + buffer->length == offset) {

                    buffer->length += length;

#ifdef DQ_ENABLE_HIST
                    add_to_history(que, offset, length, "deq insert after"
                                   " on higer boundary end");
#endif
                    DEBUG("dequeue insert after merged offset=%"PRIu64" "
                          "length=%"PRIu64" "
                          "to offset=%"PRIu64" length=%"PRIu64" \n",
                          offset, length, buffer->offset, buffer->length);
                } else {
                    // insert in between
                    struct memory_ele* ele = slab_alloc(&que->alloc);
                    assert(ele != NULL);
                    
                    ele->offset = offset;
                    ele->length = length;
                    ele->next = buffer->next;
                    ele->prev = buffer;            

                    if (buffer->next != NULL) {
                        buffer->next->prev = ele;
                    }

                    buffer->next = ele;

#ifdef DQ_ENABLE_HIST
                    add_to_history(que, offset, length, "deq insert after"
                                   " in between");
#endif
                    DEBUG("dequeue insert after offset=%"PRIu64" length=%"PRIu64" "
                          "after offset=%"PRIu64" length=%"PRIu64" \n",
                          offset, length, buffer->offset, buffer->length);
                }
            }
        }
    } else { // insert before buffer
        // buffer is on lower boundary
        if (buffer->offset == offset+length) {
            buffer->length += length;
            buffer->offset = offset;

            // check other boundary
            if (buffer->prev != NULL &&
                (buffer->prev->offset+ buffer->prev->length == 
                buffer->offset)) {
                struct memory_ele* prev = buffer->prev;
                prev->length += buffer->length;
                prev->next = buffer->next;

                if (buffer->next != NULL) {
                    buffer->next->prev = prev;
                }

                slab_free(&que->alloc, buffer);

#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert buffer"
                               " before lower boundary merge");
#endif
                DEBUG("dequeue merge before more offset=%"PRIu64" "
                      "length=%"PRIu64" to offset=%"PRIu64" length=%"PRIu64" \n ",
                      offset, length, prev->offset, prev->length);
            } else {
#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert buffer"
                               " before lower boundary");
#endif
            }
        } else {
            // check lower boundary
            if (buffer->prev != NULL &&
                (buffer->prev->offset+ buffer->prev->length == 
                offset)) {
                if (length == 0) {
                    printf("Length is 0 \n");
                    buffer->prev->length += 2048;
                }

                buffer->prev->length += length;

#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert buffer"
                               " before prev lower boundary merge");
#endif
                DEBUG("dequeue merge before more offset=%"PRIu64" "
                      "length=%"PRIu64" to offset=%"PRIu64" length=%"PRIu64" \n ",
                      offset, length, buffer->prev->offset, buffer->prev->length);
            } else { // insert in between

                // insert in between
                struct memory_ele* ele = slab_alloc(&que->alloc);
                assert(ele != NULL);

                memset(ele, 0, sizeof(ele));               
                ele->offset = offset;
                ele->length = length;
                ele->next = buffer;
                ele->prev = buffer->prev;            

                if (buffer->prev != NULL) {
                    buffer->prev->next = ele;
                } else {
                    region->buffers = ele;
                }

                buffer->prev = ele;

#ifdef DQ_ENABLE_HIST
                add_to_history(que, offset, length, "deq insert buffer"
                               " before in between");
#endif
                DEBUG("dequeue insert before offset=%"PRIu64" length=%"PRIu64" "
                      "next is offset=%"PRIu64" length=%"PRIu64" \n",
                      offset, length,
                      buffer->offset, buffer->length);
            }
        }
    }
}

static errval_t find_region(struct debug_q* que, struct memory_list** list,
                            regionid_t rid)
{
    // find region
    struct memory_list* region = que->regions;
   
    while (region != NULL) {
        if (region->rid == rid) {
            break;
        }        
        region = region->next;
    }
    
    // check if we found the region
    if (region == NULL) {
        return DEVQ_ERR_INVALID_REGION_ID;
    }

    *list = region;
    return SYS_ERR_OK;
}

static errval_t debug_enqueue(struct devq* q, regionid_t rid, 
                              genoffset_t offset, genoffset_t length,
                              genoffset_t valid_data, genoffset_t valid_length,
                              uint64_t flags)
{
    assert(length > 0);
    DEBUG("enqueue offset %"PRIu64" \n", offset);
    errval_t err;
    struct debug_q* que = (struct debug_q*) q;

    // find region
    struct memory_list* region = NULL;

    err = find_region(que, &region, rid);
    if (err_is_fail(err)){
        return err;
    }
    
    //check_consistency(region);

    // find the buffer 
    struct memory_ele* buffer = region->buffers;
    
    if (region->buffers == NULL) {
        return DEVQ_ERR_BUFFER_ALREADY_IN_USE;
    }    

    // the only buffer
    if (buffer->next == NULL) {
        if (buffer_in_bounds(offset, length,
                             buffer->offset, buffer->length)) {
            err = que->q->f.enq(que->q, rid, offset, length, valid_data,
                                valid_length, flags);
            if (err_is_fail(err)) {
                return err;
            }   

            remove_split_buffer(que, region, buffer, offset, length);
            return SYS_ERR_OK;          
        } else {
            printf("Bounds check failed only buffer offset=%lu length=%lu " 
                  " buf->offset=%lu buf->len=%lu\n", offset, length,
                  buffer->offset, buffer->length);
#ifdef DQ_ENABLE_HIST
            dump_history(que);
#endif
            dump_list(region);
            return DEVQ_ERR_INVALID_BUFFER_ARGS;
        }
    }


    // more than one buffer
    while (buffer != NULL) {
        if (buffer_in_bounds(offset, length, 
                             buffer->offset, buffer->length)){
            err = que->q->f.enq(que->q, rid, offset, length, valid_data,
                                valid_length, flags);
            if (err_is_fail(err)) {
                return err;
            }   

            remove_split_buffer(que, region, buffer, offset, length);
            return SYS_ERR_OK;          
        }
        buffer = buffer->next;
    }  
    
    printf("Did not find region offset=%ld length=%ld \n", offset, length);
#ifdef DQ_ENABLE_HIST
    dump_history(que);
#endif
    dump_list(region);

    return DEVQ_ERR_INVALID_BUFFER_ARGS;
}

static errval_t debug_dequeue(struct devq* q, regionid_t* rid, genoffset_t* offset,
                              genoffset_t* length, genoffset_t* valid_data,
                              genoffset_t* valid_length, uint64_t* flags)
{
    errval_t err;
    struct debug_q* que = (struct debug_q*) q;
    assert(que->q->f.deq != NULL);
    err = que->q->f.deq(que->q, rid, offset, length, valid_data,
                        valid_length, flags);
    if (err_is_fail(err)) {
        return err;
    }
    DEBUG("dequeued offset=%lu \n", *offset);

    struct memory_list* region = NULL;

    err = find_region(que, &region, *rid);
    if (err_is_fail(err)){
        // region ids are checked bythe devq library, if we do not find
        // the region id when dequeueing here we do not have a consistant
        // view of two endpoints
        //
        // Add region
        if (que->regions == NULL) {
            printf("Adding region frirst %lu len \n", *offset + *length);

            que->regions = slab_alloc(&que->alloc_list);
            assert(que->regions != NULL);

            que->regions->rid = *rid;
            que->regions->not_consistent = true;
            // region is at least offset + length
            que->regions->length = *offset + *length;
            que->regions->next = NULL;
            // add the whole regions as a buffer
            que->regions->buffers = slab_alloc(&que->alloc);
            assert(que->regions->buffers != NULL);

            memset(que->regions->buffers, 0, sizeof(que->regions->buffers));
            que->regions->buffers->offset = 0;
            que->regions->buffers->length = *offset + *length;
            que->regions->buffers->next = NULL;
            return SYS_ERR_OK;
        }

        struct memory_list* ele = que->regions;
        while (ele->next != NULL) {
            ele = ele->next;
        }

        printf("Adding region second %lu len \n", *offset + *length);
        // add the reigon
        ele->next = slab_alloc(&que->alloc_list);
        assert(ele->next != NULL);

        memset(que->regions->buffers, 0, sizeof(ele->next));
        ele = ele->next;

        ele->rid = *rid;
        ele->next = NULL;
        ele->not_consistent = true;
        ele->length = *offset + *length;
        // add the whole regions as a buffer
        ele->buffers = slab_alloc(&que->alloc);
        assert(ele->buffers != NULL);

        memset(ele->buffers, 0, sizeof(ele->buffers));
        ele->buffers->offset = 0;
        ele->buffers->length = *offset + *length;
        ele->buffers->next = NULL;
        return SYS_ERR_OK;
    }

    if (region->not_consistent) {
        if ((*offset + *length) > region->length) {
            region->length = *offset + *length;
        }
    }

    //check_consistency(region);

    // find the buffer 
    struct memory_ele* buffer = region->buffers;
    if (buffer == NULL) {
        region->buffers = slab_alloc(&que->alloc);
        assert(region->buffers != NULL);

        region->buffers->offset = *offset;
        region->buffers->length = *length;
        region->buffers->next = NULL;
        region->buffers->prev = NULL;
        return SYS_ERR_OK;
    }

    if (buffer->next == NULL) {
        if (!buffer_in_bounds(*offset, *length, buffer->offset,
                              buffer->length)) {
            insert_merge_buffer(que, region, buffer, *offset, *length);
            return SYS_ERR_OK;
        } else {
            return DEVQ_ERR_BUFFER_NOT_IN_USE;
        }
    }


    while (buffer->next != NULL) {
        if (*offset >= buffer->offset) {
            buffer = buffer->next;
        } else {
            if (!buffer_in_bounds(*offset, *length, buffer->offset, 
                buffer->length)) {
                insert_merge_buffer(que, region, buffer, *offset, *length);
                return SYS_ERR_OK;
            } else {
                return DEVQ_ERR_BUFFER_NOT_IN_USE;
            }
        }
    }

    // insert after the last buffer
    if (!buffer_in_bounds(*offset, *length, buffer->offset, 
        buffer->length)) {
        insert_merge_buffer(que, region, buffer, *offset, *length);
        return SYS_ERR_OK;
    }

    return DEVQ_ERR_BUFFER_NOT_IN_USE;
}

static errval_t debug_destroy(struct devq* devq)
{
    // TODO cleanup
    return SYS_ERR_OK;
}

/**
 * Public functions
 *
 */

errval_t debug_create(struct debug_q** q, struct devq* other_q)
{
    errval_t err;
    struct debug_q* que;
    que = calloc(1, sizeof(struct debug_q));
    assert(que);

    slab_init(&que->alloc, sizeof(struct memory_ele),
              slab_default_refill);
   
    slab_init(&que->alloc_list, sizeof(struct memory_list),
              slab_default_refill);

    que->q = other_q;
    err = devq_init(&que->my_q, false);
    if (err_is_fail(err)) {
        return err;
    }   

    que->my_q.f.reg = debug_register;
    que->my_q.f.dereg = debug_deregister;
    que->my_q.f.ctrl = debug_control;
    que->my_q.f.notify = debug_notify;
    que->my_q.f.enq = debug_enqueue;
    que->my_q.f.deq = debug_dequeue;
    que->my_q.f.destroy = debug_destroy;
    *q = que;
    return SYS_ERR_OK;
}

errval_t debug_dump_region(struct debug_q* que, regionid_t rid) 
{
    errval_t err;
    // find region
    struct memory_list* region = NULL;

    err = find_region(que, &region, rid);
    if (err_is_fail(err)){
        return err;
    }

    dump_list(region);
    return SYS_ERR_OK;
}


void debug_dump_history(struct debug_q* q)
{
#ifdef DQ_ENABLE_HIST
    dump_history(q);
#endif
}

errval_t debug_add_region(struct debug_q* q, struct capref cap,
                         regionid_t rid)
{
    return devq_add_region((struct devq*) q, cap, rid);
}

errval_t debug_remove_region(struct debug_q* q, regionid_t rid)
{
    return devq_remove_region((struct devq*) q, rid);
}
