/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

// System dependencies
#include <string.h>
#include <utils/Errors.h>

// Camera dependencies
#include "QCameraQueue.h"

extern "C" {
#include "mm_camera_dbg.h"
}

namespace qcamera {

/*===========================================================================
 * FUNCTION   : QCameraQueue
 *
 * DESCRIPTION: default constructor of QCameraQueue
 *
 * PARAMETERS : None
 *
 * RETURN     : None
 *==========================================================================*/
QCameraQueue::QCameraQueue()
{
    pthread_mutex_init(&m_lock, NULL);
    cam_list_init(&m_head.list);
    m_size = 0;
    m_dataFn = NULL;
    m_userData = NULL;
    m_active = true;
}

/*===========================================================================
 * FUNCTION   : QCameraQueue
 *
 * DESCRIPTION: constructor of QCameraQueue
 *
 * PARAMETERS :
 *   @data_rel_fn : function ptr to release node data internal resource
 *   @user_data   : user data ptr
 *
 * RETURN     : None
 *==========================================================================*/
QCameraQueue::QCameraQueue(release_data_fn data_rel_fn, void *user_data)
{
    pthread_mutex_init(&m_lock, NULL);
    cam_list_init(&m_head.list);
    m_size = 0;
    m_dataFn = data_rel_fn;
    m_userData = user_data;
    m_active = true;
}

/*===========================================================================
 * FUNCTION   : ~QCameraQueue
 *
 * DESCRIPTION: deconstructor of QCameraQueue
 *
 * PARAMETERS : None
 *
 * RETURN     : None
 *==========================================================================*/
QCameraQueue::~QCameraQueue()
{
    flush();
    pthread_mutex_destroy(&m_lock);
}

/*===========================================================================
 * FUNCTION   : init
 *
 * DESCRIPTION: Put the queue to active state (ready to enqueue and dequeue)
 *
 * PARAMETERS : None
 *
 * RETURN     : None
 *==========================================================================*/
void QCameraQueue::init()
{
    pthread_mutex_lock(&m_lock);
    m_active = true;
    pthread_mutex_unlock(&m_lock);
}

/*===========================================================================
 * FUNCTION   : isEmpty
 *
 * DESCRIPTION: return if the queue is empty or not
 *
 * PARAMETERS : None
 *
 * RETURN     : true -- queue is empty; false -- not empty
 *==========================================================================*/
bool QCameraQueue::isEmpty()
{
    bool flag = true;
    pthread_mutex_lock(&m_lock);
    if (m_size > 0) {
        flag = false;
    }
    pthread_mutex_unlock(&m_lock);
    return flag;
}

/*===========================================================================
 * FUNCTION   : enqueue
 *
 * DESCRIPTION: enqueue data into the queue
 *
 * PARAMETERS :
 *   @data    : data to be enqueued
 *
 * RETURN     : true -- success; false -- failed
 *==========================================================================*/
bool QCameraQueue::enqueue(void *data)
{
    bool rc;
    camera_q_node *node =
        (camera_q_node *)malloc(sizeof(camera_q_node));
    if (NULL == node) {
        LOGE("No memory for camera_q_node");
        return false;
    }

    memset(node, 0, sizeof(camera_q_node));
    node->data = data;

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        cam_list_add_tail_node(&node->list, &m_head.list);
        m_size++;
        rc = true;
    } else {
        free(node);
        rc = false;
    }
    pthread_mutex_unlock(&m_lock);
    return rc;
}

/*===========================================================================
 * FUNCTION   : enqueueWithPriority
 *
 * DESCRIPTION: enqueue data into queue with priority, will insert into the
 *              head of the queue
 *
 * PARAMETERS :
 *   @data    : data to be enqueued
 *
 * RETURN     : true -- success; false -- failed
 *==========================================================================*/
bool QCameraQueue::enqueueWithPriority(void *data)
{
    bool rc;
    camera_q_node *node =
        (camera_q_node *)malloc(sizeof(camera_q_node));
    if (NULL == node) {
        LOGE("No memory for camera_q_node");
        return false;
    }

    memset(node, 0, sizeof(camera_q_node));
    node->data = data;

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        struct cam_list *p_next = m_head.list.next;

        m_head.list.next = &node->list;
        p_next->prev = &node->list;
        node->list.next = p_next;
        node->list.prev = &m_head.list;

        m_size++;
        rc = true;
    } else {
        free(node);
        rc = false;
    }
    pthread_mutex_unlock(&m_lock);
    return rc;
}

/*===========================================================================
 * FUNCTION   : peek
 *
 * DESCRIPTION: return the head element without removing it
 *
 * PARAMETERS : None
 *
 * RETURN     : data ptr. NULL if not any data in the queue.
 *==========================================================================*/
void* QCameraQueue::peek()
{
    camera_q_node* node = NULL;
    void* data = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        pos = head->next;
        if (pos != head) {
            node = member_of(pos, camera_q_node, list);
        }
    }
    pthread_mutex_unlock(&m_lock);

    if (NULL != node) {
        data = node->data;
    }

    return data;
}

/*===========================================================================
 * FUNCTION   : dequeue
 *
 * DESCRIPTION: dequeue data from the queue
 *
 * PARAMETERS :
 *   @bFromHead : if true, dequeue from the head
 *                if false, dequeue from the tail
 *
 * RETURN     : data ptr. NULL if not any data in the queue.
 *==========================================================================*/
void* QCameraQueue::dequeue(bool bFromHead)
{
    camera_q_node* node = NULL;
    void* data = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        if (bFromHead) {
            pos = head->next;
        } else {
            pos = head->prev;
        }
        if (pos != head) {
            node = member_of(pos, camera_q_node, list);
            cam_list_del_node(&node->list);
            m_size--;
        }
    }
    pthread_mutex_unlock(&m_lock);

    if (NULL != node) {
        data = node->data;
        free(node);
    }

    return data;
}

/*===========================================================================
 * FUNCTION   : dequeue
 *
 * DESCRIPTION: dequeue data from the queue
 *
 * PARAMETERS :
 *   @match : matching function callback
 *   @match_data : the actual data to be matched
 *
 * RETURN     : data ptr. NULL if not any data in the queue.
 *==========================================================================*/
void* QCameraQueue::dequeue(match_fn_data match, void *match_data){
    camera_q_node* node = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;
    void* data = NULL;

    if ( NULL == match || NULL == match_data ) {
        return NULL;
    }

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        pos = head->next;

        while(pos != head) {
            node = member_of(pos, camera_q_node, list);
            pos = pos->next;
            if (NULL != node) {
                if ( match(node->data, m_userData, match_data) ) {
                    cam_list_del_node(&node->list);
                    m_size--;
                    data = node->data;
                    free(node);
                    pthread_mutex_unlock(&m_lock);
                    return data;
                }
            }
        }
    }
    pthread_mutex_unlock(&m_lock);
    return NULL;
}

/*===========================================================================
 * FUNCTION   : flush
 *
 * DESCRIPTION: flush all nodes from the queue, queue will be empty after this
 *              operation.
 *
 * PARAMETERS : None
 *
 * RETURN     : None
 *==========================================================================*/
void QCameraQueue::flush(){
    camera_q_node* node = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        pos = head->next;

        while(pos != head) {
            node = member_of(pos, camera_q_node, list);
            pos = pos->next;
            cam_list_del_node(&node->list);
            m_size--;

            if (NULL != node->data) {
                if (m_dataFn) {
                    m_dataFn(node->data, m_userData);
                }
                free(node->data);
            }
            free(node);

        }
        m_size = 0;
        m_active = false;
    }
    pthread_mutex_unlock(&m_lock);
}

/*===========================================================================
 * FUNCTION   : flushNodes
 *
 * DESCRIPTION: flush only specific nodes, depending on
 *              the given matching function.
 *
 * PARAMETERS :
 *   @match   : matching function
 *
 * RETURN     : None
 *==========================================================================*/
void QCameraQueue::flushNodes(match_fn match){
    camera_q_node* node = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;

    if ( NULL == match ) {
        return;
    }

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        pos = head->next;

        while(pos != head) {
            node = member_of(pos, camera_q_node, list);
            pos = pos->next;
            if ( match(node->data, m_userData) ) {
                cam_list_del_node(&node->list);
                m_size--;

                if (NULL != node->data) {
                    if (m_dataFn) {
                        m_dataFn(node->data, m_userData);
                    }
                    free(node->data);
                }
                free(node);
            }
        }
    }
    pthread_mutex_unlock(&m_lock);
}

/*===========================================================================
 * FUNCTION   : flushNodes
 *
 * DESCRIPTION: flush only specific nodes, depending on
 *              the given matching function.
 *
 * PARAMETERS :
 *   @match   : matching function
 *
 * RETURN     : None
 *==========================================================================*/
void QCameraQueue::flushNodes(match_fn_data match, void *match_data){
    camera_q_node* node = NULL;
    struct cam_list *head = NULL;
    struct cam_list *pos = NULL;

    if ( NULL == match ) {
        return;
    }

    pthread_mutex_lock(&m_lock);
    if (m_active) {
        head = &m_head.list;
        pos = head->next;

        while(pos != head) {
            node = member_of(pos, camera_q_node, list);
            pos = pos->next;
            if ( match(node->data, m_userData, match_data) ) {
                cam_list_del_node(&node->list);
                m_size--;

                if (NULL != node->data) {
                    if (m_dataFn) {
                        m_dataFn(node->data, m_userData);
                    }
                    free(node->data);
                }
                free(node);
            }
        }
    }
    pthread_mutex_unlock(&m_lock);
}

}; // namespace qcamera