#include <rtthread.h>
#include "pthread.h"

#define  MUTEXATTR_SHARED_MASK 0x0010
#define  MUTEXATTR_TYPE_MASK   0x000f

const pthread_mutexattr_t pthread_default_mutexattr = PTHREAD_PROCESS_PRIVATE;

int pthread_mutexattr_init(pthread_mutexattr_t *attr)
{
	if (attr)
	{
		*attr = pthread_default_mutexattr;
		return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_init);

int pthread_mutexattr_destroy(pthread_mutexattr_t *attr)
{
	if (attr)
	{
	 	*attr = -1;
		return 0;
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_destroy);

int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type)
{
	if (attr && type)
	{
        int  atype = (*attr & MUTEXATTR_TYPE_MASK);

		if (atype >= PTHREAD_MUTEX_NORMAL && atype <= PTHREAD_MUTEX_ERRORCHECK)
		{
			*type = atype;
			return 0;
		}
	}

	return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_gettype);

int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type)
{
    if (attr && type >= PTHREAD_MUTEX_NORMAL &&
                type <= PTHREAD_MUTEX_ERRORCHECK )
	{
        *attr = (*attr & ~MUTEXATTR_TYPE_MASK) | type;
        return 0;
    }

    return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_settype);

int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int  pshared)
{
    if (!attr) return EINVAL;

    switch (pshared)
	{
    case PTHREAD_PROCESS_PRIVATE:
        *attr &= ~MUTEXATTR_SHARED_MASK;
        return 0;

    case PTHREAD_PROCESS_SHARED:
        *attr |= MUTEXATTR_SHARED_MASK;
        return 0;
    }

    return EINVAL;
}
RTM_EXPORT(pthread_mutexattr_setpshared);

int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr, int *pshared)
{
    if (!attr || !pshared) return EINVAL;

    *pshared = (*attr & MUTEXATTR_SHARED_MASK) ? PTHREAD_PROCESS_SHARED
                                               : PTHREAD_PROCESS_PRIVATE;
    return 0;
}
RTM_EXPORT(pthread_mutexattr_getpshared);

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
{
	rt_err_t result;
	char name[RT_NAME_MAX];
	static rt_uint16_t pthread_mutex_number = 0;

	if (!mutex) return EINVAL;

	/* build mutex name */
	rt_snprintf(name, sizeof(name), "pmtx%02d", pthread_mutex_number ++);
	if (attr == RT_NULL) mutex->attr = pthread_default_mutexattr;
	else mutex->attr = *attr;

	/* init mutex lock */
	result = rt_mutex_init(&(mutex->lock), name, RT_IPC_FLAG_FIFO);
	if (result != RT_EOK) return EINVAL;

	/* detach the object from system object container */
	rt_object_detach(&(mutex->lock.parent.parent));

	return 0;
}
RTM_EXPORT(pthread_mutex_init);

int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
	if (!mutex || mutex->attr == -1) return EINVAL;

	/* it's busy */
	if (mutex->lock.owner != RT_NULL) return EBUSY;

	rt_memset(mutex, 0, sizeof(pthread_mutex_t));
	mutex->attr = -1;

	return 0;
}
RTM_EXPORT(pthread_mutex_destroy);

int pthread_mutex_lock(pthread_mutex_t *mutex)
{
	int mtype;
	rt_err_t result;

	if (!mutex) return EINVAL;

	if (mutex->attr == -1)
	{
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	mtype = mutex->attr & MUTEXATTR_TYPE_MASK;
	rt_enter_critical();
	if (mutex->lock.owner == rt_thread_self() && mtype != PTHREAD_MUTEX_RECURSIVE)
	{
		rt_exit_critical();
		return EDEADLK;
	}
	rt_exit_critical();

	result = rt_mutex_take(&(mutex->lock), RT_WAITING_FOREVER);
	if (result == RT_EOK) return 0;

	return EINVAL;
}
RTM_EXPORT(pthread_mutex_lock);

int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
	rt_err_t result;

	if (!mutex) return EINVAL;
	if (mutex->attr == -1)
	{
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	if (mutex->lock.owner != rt_thread_self())
	{
		int mtype;
		mtype = mutex->attr & MUTEXATTR_TYPE_MASK;

		/* error check, return EPERM */
		if (mtype == PTHREAD_MUTEX_ERRORCHECK) return EPERM;

		/* no thread waiting on this mutex */
		if (mutex->lock.owner == RT_NULL) return 0;
	}

	result = rt_mutex_release(&(mutex->lock));
	if (result == RT_EOK) return 0;
	
	return EINVAL;
}
RTM_EXPORT(pthread_mutex_unlock);

int pthread_mutex_trylock(pthread_mutex_t *mutex)
{
	rt_err_t result;

	if (!mutex) return EINVAL;
	if (mutex->attr == -1)
	{
		/* init mutex */
		pthread_mutex_init(mutex, RT_NULL);
	}

	result = rt_mutex_take(&(mutex->lock), 0);
	if (result == RT_EOK) return 0;

	return EBUSY;
}
RTM_EXPORT(pthread_mutex_trylock);

