#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "osp.h"

void *osp_semm_create(void)
{

    lx_sem_t *sem = (lx_sem_t *)malloc(sizeof(lx_sem_t));
    if (sem != NULL)
    {
        if (pthread_mutex_init(&sem->u.sem_mutex, NULL) == 0)
        {
            sem->sem_type = SEM_TYPE_MUTEX;
        }
        else
        {
            free(sem);
            sem = NULL;
        }
    }
    
    return (void *)sem;
}

void*osp_semsm_create(void *semId)
{
    pthread_mutexattr_t mutexAttr;
    int32_t Ret;
    lx_sem_t *sem;

    sem = (lx_sem_t *)semId;
    Ret = pthread_mutexattr_init(&mutexAttr);
    if (0 != Ret)
    {
        return NULL;
    }

    Ret = pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED);
    if (0 != Ret)
    {
        return NULL;
    }
    
    Ret = pthread_mutexattr_setrobust(&mutexAttr, PTHREAD_MUTEX_ROBUST);
    if (0 != Ret)
    {
        return NULL;
    }
    
    Ret = pthread_mutex_init(&(sem->u.sem_mutex), &mutexAttr);
    if (0 != Ret)
    {
        osp_debug_out(ERR_DEBUG_LEVEL, "pthread_mutex_init err\n");
        return NULL;

    }
    sem->sem_type = SEM_TYPE_MUTEX;

    return (void *)sem;

}

void *osp_semss_create(void *semId)
{
    lx_sem_t *sem = (lx_sem_t *)semId;
    
    if (sem_init(&sem->u.sem_count, 1, 0) == 0)
    {
        sem->sem_type = SEM_TYPE_COUNT;
    }

    return (void *)sem;
}

void *osp_semb_create(SEM_B_STATE initalState)
{
    
    int32_t initCount = (initalState == SEM_FULL) ? 1 : 0;
    lx_sem_t*sem = (lx_sem_t *)malloc(sizeof(lx_sem_t));

    if (sem != NULL)
    {
        if (sem_init(&sem->u.sem_count, 0, initCount) == 0)
        {
            sem->sem_type = SEM_TYPE_BINARY;
        }
        else
        {
            free(sem);
            sem = NULL;
        }
    }

    return (void *)sem;
}



void *osp_semc_create(int32_t initCount)
{

    lx_sem_t *sem = (lx_sem_t *)malloc(sizeof(lx_sem_t));
    if (sem != NULL)
    {
        if (sem_init(&sem->u.sem_count, 1, initCount) == 0)
        {
            sem->sem_type = SEM_TYPE_COUNT;
        }
        else
        {
            free(sem);
            sem = NULL;
        }
    }

    return (void *)sem;
}
int32_t osp_sem_timed_wait_millsecs(lx_sem_t *sem, long msecs)
{
    struct timespec ts;
    int32_t ret = OSP_OK;
    
    clock_gettime(CLOCK_REALTIME, &ts);
    long secs = msecs/1000;
    msecs = msecs%1000;
    
    long add = 0;
    msecs = msecs*1000*1000 + ts.tv_nsec;
    add = msecs / (1000*1000*1000);
    ts.tv_sec += (add + secs);
    ts.tv_nsec = msecs%(1000*1000*1000);

    if (sem->sem_type == SEM_TYPE_MUTEX)
    {
        ret = pthread_mutex_timedlock(&sem->u.sem_mutex, &ts);
    }
    else
    {
        ret = sem_timedwait(&sem->u.sem_count, &ts);
    }
    return ret;
}

int32_t osp_sem_timedwait_microsecond(lx_sem_t *sem, long msecs)
{
    struct timespec ts;
    int32_t ret = OSP_OK;
    
    clock_gettime(CLOCK_REALTIME, &ts);
    long secs = msecs / 1000000;
    msecs = msecs % 1000000;
    
    long add = 0;
    msecs = msecs * 1000 + ts.tv_nsec;
    add = msecs / (1000 * 1000 * 1000);
    ts.tv_sec += (add + secs);
    ts.tv_nsec = msecs % (1000 * 1000 * 1000);

    if (sem->sem_type == SEM_TYPE_MUTEX)
    {
        ret = pthread_mutex_timedlock(&sem->u.sem_mutex, &ts);
    }
    else
    {
        ret = sem_timedwait(&sem->u.sem_count, &ts);
    }    
    return ret;
}

OSP_STATUS osp_sem_take(void *semId, int32_t  timeout)
{
    int32_t ret = -1;
    lx_sem_t *sem = (lx_sem_t *)semId;
    int32_t err = EINTR;
    /*errno ==EINTR means this is broken by singnal */
    while ((-1 == ret) && (err == EINTR))
    {
        if (timeout > 0)
        {
//            ret = osp_sem_timed_wait_millsecs(sem, timeout);
            ret = osp_sem_timedwait_microsecond(sem, timeout);
        }
        else
        {
            if (sem->sem_type == SEM_TYPE_MUTEX)
                ret = pthread_mutex_lock(&sem->u.sem_mutex);
            else
                ret = sem_wait(&sem->u.sem_count);
        }
        if (0 != ret)
            err = errno;
    }

    return (OSP_OK);
}

OSP_STATUS osp_sem_taketry(void *semId)
{

    int32_t ret;
    lx_sem_t *sem = (lx_sem_t *)semId;

    if (sem->sem_type == SEM_TYPE_MUTEX)
        ret = pthread_mutex_trylock(&sem->u.sem_mutex);
    else
        ret = sem_trywait(&sem->u.sem_count);
    if (ret != 0)
        return (OSP_ERROR);

    return (OSP_OK);
}



OSP_STATUS osp_sem_give(void *semId)
{

  
    int32_t ret;
    lx_sem_t *sem = (lx_sem_t *)semId;

    if (sem->sem_type == SEM_TYPE_MUTEX)
        ret = pthread_mutex_unlock(&sem->u.sem_mutex);
    else if (sem->sem_type == SEM_TYPE_COUNT)
        ret = sem_post(&sem->u.sem_count);
    else
    {
        sem_getvalue(&sem->u.sem_count, &ret);
        if (ret == 0)
            ret = sem_post(&sem->u.sem_count);
    }

    if (ret != 0)
        return (OSP_ERROR);

    return (OSP_OK);
}


OSP_STATUS osp_sem_delete(void *semId)
{
    int32_t ret;
    lx_sem_t *sem = (lx_sem_t *)semId;

    if (sem->sem_type == SEM_TYPE_MUTEX)
    {
        ret = pthread_mutex_destroy(&sem->u.sem_mutex);
    }
    else
    {
        ret = sem_destroy(&sem->u.sem_count);
    }
    free(sem);

    return ret;
}



void *osp_semrwc_reate(int32_t readerNum)
{
    pthread_rwlock_t *sem = (pthread_rwlock_t *)malloc(sizeof(pthread_rwlock_t));
    if (sem != NULL)
    {
        if (0 != pthread_rwlock_init(sem, NULL))
        {
            free(sem);
            sem = NULL;
        }
    }

    return (void *)sem;
}



OSP_STATUS osp_semr_take(void *semId, int32_t timeout)
{

    int32_t ret;
    struct timespec timeout1;
    pthread_rwlock_t *sem = (pthread_rwlock_t *)semId;

    if (timeout > 0)
    {
        //timeout1.tv_sec = timeout/1000;
        //timeout1.tv_nsec = (timeout%1000)*1000000;
        clock_gettime(CLOCK_REALTIME, &timeout1);
        timeout1.tv_sec += timeout/1000;
        timeout1.tv_nsec += (timeout%1000)*1000000;

        ret = pthread_rwlock_timedrdlock(sem, &timeout1);
    }
    else
        ret = pthread_rwlock_rdlock(sem);

    if (ret != 0)
    {
        return (OSP_ERROR);
    }
  
    return (OSP_OK);
}



OSP_STATUS osp_sem_wtake(void *semId, int32_t timeout)
{

    int32_t ret;
    struct timespec timeout1;
    pthread_rwlock_t *sem = (pthread_rwlock_t *)semId;

    if (timeout > 0)
    {
        //timeout1.tv_sec = timeout/1000;
        //timeout1.tv_nsec = (timeout%1000)*1000000;
        clock_gettime(CLOCK_REALTIME, &timeout1);
        timeout1.tv_sec += timeout/1000;
        timeout1.tv_nsec += (timeout%1000)*1000000;

        ret = pthread_rwlock_timedwrlock(sem, &timeout1);
    }
    else
        ret = pthread_rwlock_wrlock(sem);

    if (ret != 0)
    {
        return (OSP_ERROR);
    }

    return (OSP_OK);
}

OSP_STATUS osp_semrw_give(void *semId)
{

    int32_t ret;
    pthread_rwlock_t *sem = (pthread_rwlock_t *)semId;

    ret = pthread_rwlock_unlock(sem);
    if (ret != 0)
    {
        return (OSP_ERROR);
    }
    
    return (OSP_OK);
}
