/*
 * File      : semaphore.c
 * This file is part of RT-Thread RTOS
 * COPYRIGHT (C) 2006 - 2010, RT-Thread Development Team
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-10-26     Bernard      the first version
 */

#include <rtthread.h>
#include <string.h>
#include "semaphore.h"
#include "pthread_internal.h"

static sem_t *posix_sem_list = RT_NULL;
static struct rt_semaphore posix_sem_lock;
void posix_sem_system_init()
{
    rt_sem_init(&posix_sem_lock, "psem", 1, RT_IPC_FLAG_FIFO);
}

rt_inline void posix_sem_insert(sem_t *psem)
{
    psem->next = posix_sem_list;
    posix_sem_list = psem;
}

static void posix_sem_delete(sem_t *psem)
{
    sem_t *iter;
    if (posix_sem_list == psem)
    {
        posix_sem_list = psem->next;

        rt_sem_delete(psem->sem);
        rt_free(psem);

        return;
    }
    for (iter = posix_sem_list; iter->next != RT_NULL; iter = iter->next)
    {
        if (iter->next == psem)
        {
            /* delete this mq */
            if (psem->next != RT_NULL)
                iter->next = psem->next;
            else
                iter->next = RT_NULL;

            /* delete RT-Thread mqueue */
            rt_sem_delete(psem->sem);
            rt_free(psem);

            return ;
        }
    }
}

static sem_t *posix_sem_find(const char* name)
{
    sem_t *iter;
    rt_object_t object;

    for (iter = posix_sem_list; iter != RT_NULL; iter = iter->next)
    {
        object = (rt_object_t)&(iter->sem);

        if (strncmp(object->name, name, RT_NAME_MAX) == 0)
        {
            return iter;
        }
    }
    
    return RT_NULL;
}

int sem_close(sem_t *sem)
{
    if (sem == RT_NULL)
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    /* lock posix semaphore list */
    rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
    sem->refcount --;
    if (sem->refcount == 0)
    {
        /* delete from posix semaphore list */
        if (sem->unlinked)
            posix_sem_delete(sem);
        sem = RT_NULL;
    }
    rt_sem_release(&posix_sem_lock);

    return 0;
}
RTM_EXPORT(sem_close);

int sem_destroy(sem_t *sem)
{
    rt_err_t result;

    if ((!sem) || !(sem->unamed))
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    /* lock posix semaphore list */
    rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
    result = rt_sem_trytake(sem->sem);
    if (result != RT_EOK)
    {
        rt_sem_release(&posix_sem_lock);
        rt_set_errno(EBUSY);

        return -1;
    }

    /* destroy an unamed posix semaphore */
    posix_sem_delete(sem);
    rt_sem_release(&posix_sem_lock);

    return 0;
}
RTM_EXPORT(sem_destroy);

int sem_unlink(const char *name)
{
    sem_t *psem;

    /* lock posix semaphore list */
    rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
    psem = posix_sem_find(name);
    if (psem != RT_NULL)
    {
        psem->unlinked = 1;
        if (psem->refcount == 0)
        {
            /* remove this semaphore */
            posix_sem_delete(psem);
        }
        rt_sem_release(&posix_sem_lock);

        return 0;
    }
    rt_sem_release(&posix_sem_lock);

    /* no this entry */
    rt_set_errno(ENOENT);

    return -1;
}
RTM_EXPORT(sem_unlink);

int sem_getvalue(sem_t *sem, int *sval)
{
    if (!sem || !sval)
    {
        rt_set_errno(EINVAL);

        return -1;
    }
    *sval = sem->sem->value;

    return 0;
}
RTM_EXPORT(sem_getvalue);

int sem_init(sem_t *sem, int pshared, unsigned int value)
{
    char name[RT_NAME_MAX];
    static rt_uint16_t psem_number = 0;

    if (sem == RT_NULL)
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    rt_snprintf(name, sizeof(name), "psem%02d", psem_number++);
    sem->sem = rt_sem_create(name, value, RT_IPC_FLAG_FIFO);
    if (sem == RT_NULL)
    {
        rt_set_errno(ENOMEM);

        return -1;
    }

    /* initialize posix semaphore */
    sem->refcount = 1;
    sem->unlinked = 0;
    sem->unamed = 1;
    /* lock posix semaphore list */
    rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
    posix_sem_insert(sem);
    rt_sem_release(&posix_sem_lock);

    return 0;
}
RTM_EXPORT(sem_init);

sem_t *sem_open(const char *name, int oflag, ...)
{
    sem_t* sem;
    va_list arg;
    mode_t mode;
    unsigned int value;

    sem = RT_NULL;

    /* lock posix semaphore list */
    rt_sem_take(&posix_sem_lock, RT_WAITING_FOREVER);
    if (oflag & O_CREAT)
    {
        va_start(arg, oflag);
        mode = (mode_t) va_arg( arg, unsigned int); mode = mode;
        value = va_arg( arg, unsigned int);
        va_end(arg);

        if (oflag & O_EXCL)
        {
            if (posix_sem_find(name) != RT_NULL)
            {
                rt_set_errno(EEXIST);
                goto __return;
            }
        }
        sem = (sem_t*) rt_malloc (sizeof(struct posix_sem));
        if (sem == RT_NULL)
        {
            rt_set_errno(ENFILE);
            goto __return;
        }

        /* create RT-Thread semaphore */
        sem->sem = rt_sem_create(name, value, RT_IPC_FLAG_FIFO);
        if (sem->sem == RT_NULL) /* create failed */
        {
            rt_set_errno(ENFILE);
            goto __return;
        }
        /* initialize reference count */
        sem->refcount = 1;
        sem->unlinked = 0;
        sem->unamed = 0;

        /* insert semaphore to posix semaphore list */
        posix_sem_insert(sem);
    }
    else
    {
        /* find semaphore */
        sem = posix_sem_find(name);
        if (sem != RT_NULL)
        {
            sem->refcount ++; /* increase reference count */
        }
        else
        {
            rt_set_errno(ENOENT);
            goto __return;
        }
    }
    rt_sem_release(&posix_sem_lock);

    return sem;

__return:
    /* release lock */
    rt_sem_release(&posix_sem_lock);

    /* release allocated memory */
    if (sem != RT_NULL)
    {
        /* delete RT-Thread semaphore */
        if (sem->sem != RT_NULL)
            rt_sem_delete(sem->sem);
        rt_free(sem);
    }

    return RT_NULL;
}
RTM_EXPORT(sem_open);

int sem_post(sem_t *sem)
{
    rt_err_t result;

    if (!sem)
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    result = rt_sem_release(sem->sem);
    if (result == RT_EOK)
        return 0;

    rt_set_errno(EINVAL);

    return -1;
}
RTM_EXPORT(sem_post);

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout)
{
    rt_err_t result;
    rt_int32_t tick;

    if (!sem || !abs_timeout)
        return EINVAL;

    /* calculate os tick */
    tick = clock_time_to_tick(abs_timeout);
    
    result = rt_sem_take(sem->sem, tick);
    if (result == -RT_ETIMEOUT)
    {
        rt_set_errno(ETIMEDOUT);

        return -1;
    }
    if (result == RT_EOK)
        return 0;

    rt_set_errno(EINTR);

    return -1;
}
RTM_EXPORT(sem_timedwait);

int sem_trywait(sem_t *sem)
{
    rt_err_t result;

    if (!sem)
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    result = rt_sem_take(sem->sem, RT_WAITING_FOREVER);
    if (result == -RT_ETIMEOUT)
    {
        rt_set_errno(EAGAIN);

        return -1;
    }
    if (result == RT_EOK)
        return 0;

    rt_set_errno(EINTR);

    return -1;
}
RTM_EXPORT(sem_trywait);

int sem_wait(sem_t *sem)
{
    rt_err_t result;

    if (!sem)
    {
        rt_set_errno(EINVAL);

        return -1;
    }

    result = rt_sem_take(sem->sem, RT_WAITING_FOREVER);
    if (result == RT_EOK)
        return 0;

    rt_set_errno(EINTR);

    return -1;
}
RTM_EXPORT(sem_wait);