/******************************************************************************
 *
 *  Copyright (C) 2014 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#include "common/bt_defs.h"
#include "osi/allocator.h"
#include "osi/fixed_queue.h"
#include "osi/list.h"
#include "osi/osi.h"
#include "common/bt_trace.h"
#include "osi/mutex.h"
#include "osi/semaphore.h"

typedef struct fixed_queue_t {

    list_t *list;
    osi_sem_t enqueue_sem;
    osi_sem_t dequeue_sem;
    osi_mutex_t lock;
    size_t capacity;

    fixed_queue_cb dequeue_ready;
} fixed_queue_t;


fixed_queue_t *fixed_queue_new(size_t capacity)
{
    fixed_queue_t *ret = osi_calloc(sizeof(fixed_queue_t));
    if (!ret) {
        goto error;
    }

    osi_mutex_new(&ret->lock);
    ret->capacity = capacity;

    ret->list = list_new(NULL);
    if (!ret->list) {
        goto error;
    }


    osi_sem_new(&ret->enqueue_sem, capacity, capacity);
    if (!ret->enqueue_sem) {
        goto error;
    }

    osi_sem_new(&ret->dequeue_sem, capacity, 0);
    if (!ret->dequeue_sem) {
        goto error;
    }

    return ret;

error:;
    fixed_queue_free(ret, NULL);
    return NULL;
}

void fixed_queue_free(fixed_queue_t *queue, fixed_queue_free_cb free_cb)
{
    const list_node_t *node;

    if (queue == NULL) {
	    return;
	}

    fixed_queue_unregister_dequeue(queue);

    if (free_cb) {
        for (node = list_begin(queue->list); node != list_end(queue->list); node = list_next(node)) {
            free_cb(list_node(node));
        }
    }

    list_free(queue->list);
    osi_sem_free(&queue->enqueue_sem);
    osi_sem_free(&queue->dequeue_sem);
    osi_mutex_free(&queue->lock);
    osi_free(queue);
}

bool fixed_queue_is_empty(fixed_queue_t *queue)
{
    bool is_empty = false;

    if (queue == NULL) {
        return true;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    is_empty = list_is_empty(queue->list);
    osi_mutex_unlock(&queue->lock);

    return is_empty;
}

size_t fixed_queue_length(fixed_queue_t *queue)
{
    size_t length;

    if (queue == NULL) {
        return 0;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    length = list_length(queue->list);
    osi_mutex_unlock(&queue->lock);

    return length;
}
size_t fixed_queue_capacity(fixed_queue_t *queue)
{
    assert(queue != NULL);

    return queue->capacity;
}

void fixed_queue_enqueue(fixed_queue_t *queue, void *data)
{
    assert(queue != NULL);
    assert(data != NULL);

    osi_sem_take(&queue->enqueue_sem, OSI_SEM_MAX_TIMEOUT);

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);

    list_append(queue->list, data);
    osi_mutex_unlock(&queue->lock);

    osi_sem_give(&queue->dequeue_sem);
}

void *fixed_queue_dequeue(fixed_queue_t *queue)
{
    void *ret = NULL;

    assert(queue != NULL);

    osi_sem_take(&queue->dequeue_sem, OSI_SEM_MAX_TIMEOUT);

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    ret = list_front(queue->list);
    list_remove(queue->list, ret);
    osi_mutex_unlock(&queue->lock);

    osi_sem_give(&queue->enqueue_sem);

    return ret;
}

bool fixed_queue_try_enqueue(fixed_queue_t *queue, void *data)
{
    assert(queue != NULL);
    assert(data != NULL);

    if (osi_sem_take(&queue->enqueue_sem, 0) != 0) {
        return false;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);

    list_append(queue->list, data);
    osi_mutex_unlock(&queue->lock);

    osi_sem_give(&queue->dequeue_sem);

    return true;
}

void *fixed_queue_try_dequeue(fixed_queue_t *queue)
{
    void *ret = NULL;

    if (queue == NULL) {
        return NULL;
    }

    if (osi_sem_take(queue->dequeue_sem, 0) != 0) {
        return NULL;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    ret = list_front(queue->list);
    list_remove(queue->list, ret);
    osi_mutex_unlock(&queue->lock);

    osi_sem_give(&queue->enqueue_sem);

    return ret;
}

void *fixed_queue_try_peek_first(fixed_queue_t *queue)
{
    void *ret = NULL;

    if (queue == NULL) {
        return NULL;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    ret = list_is_empty(queue->list) ? NULL : list_front(queue->list);
    osi_mutex_unlock(&queue->lock);

    return ret;
}

void *fixed_queue_try_peek_last(fixed_queue_t *queue)
{
    void *ret = NULL;

    if (queue == NULL) {
        return NULL;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    ret = list_is_empty(queue->list) ? NULL : list_back(queue->list);
    osi_mutex_unlock(&queue->lock);

    return ret;
}

void *fixed_queue_try_remove_from_queue(fixed_queue_t *queue, void *data)
{
    bool removed = false;

    if (queue == NULL) {
        return NULL;
    }

    osi_mutex_lock(&queue->lock, OSI_MUTEX_MAX_TIMEOUT);
    if (list_contains(queue->list, data) &&
            osi_sem_take(queue->dequeue_sem, 0) == 0) {
        removed = list_remove(queue->list, data);
        assert(removed);
    }
    osi_mutex_unlock(&queue->lock);

    if (removed) {
        osi_sem_give(queue->enqueue_sem);
        return data;
    }

    return NULL;
}

list_t *fixed_queue_get_list(fixed_queue_t *queue)
{
    assert(queue != NULL);

    // NOTE: This function is not thread safe, and there is no point for
    // calling osi_mutex_lock() / osi_mutex_unlock()
    return queue->list;
}

void fixed_queue_register_dequeue(fixed_queue_t *queue, fixed_queue_cb ready_cb)
{
    assert(queue != NULL);
    assert(ready_cb != NULL);

    queue->dequeue_ready = ready_cb;
}

void fixed_queue_unregister_dequeue(fixed_queue_t *queue)
{
    assert(queue != NULL);

    queue->dequeue_ready = NULL;
}

void fixed_queue_process(fixed_queue_t *queue)
{
    assert(queue != NULL);

    if (queue->dequeue_ready) {
        queue->dequeue_ready(queue);
    }
}

