#include <windows.h>
#include <stdio.h>
#include "pthread.h"
#include "thread.h"
#include "ref.h"
#include "rwlock.h"
#include "spinlock.h"
#include "misc.h"

static spin_t rwl_global = {0,LIFE_SPINLOCK,0};

static __attribute__((noinline)) int rwlock_static_init(pthread_rwlock_t *rw);

static __attribute__ ((noinline)) int rwl_unref(volatile pthread_rwlock_t *rwl, int res)
{
    _spin_lite_lock(&rwl_global);
#ifdef WINPTHREAD_DBG
    assert((((rwlock_t *)*rwl)->valid == LIFE_RWLOCK) && (((rwlock_t *)*rwl)->busy > 0));
#endif
     ((rwlock_t *)*rwl)->busy--;
    _spin_lite_unlock(&rwl_global);
    return res;
}

static __attribute__((noinline)) int rwl_ref(pthread_rwlock_t *rwl, int f )
{
    int r = 0;
    INIT_RWLOCK(rwl);
    _spin_lite_lock(&rwl_global);

    if (!rwl || !*rwl || ((rwlock_t *)*rwl)->valid != LIFE_RWLOCK) r = EINVAL;
    else {
        ((rwlock_t *)*rwl)->busy ++;
    }

    _spin_lite_unlock(&rwl_global);

    return r;
}

static __attribute__((noinline)) int rwl_ref_unlock(pthread_rwlock_t *rwl )
{
    int r = 0;

    _spin_lite_lock(&rwl_global);

    if (!rwl || !*rwl || ((rwlock_t *)*rwl)->valid != LIFE_RWLOCK) r = EINVAL;
    else if (STATIC_RWL_INITIALIZER(*rwl)) r= EPERM;
    else {
        ((rwlock_t *)*rwl)->busy ++;
    }

    _spin_lite_unlock(&rwl_global);

    return r;
}

static __attribute__((noinline)) int rwl_ref_destroy(pthread_rwlock_t *rwl, pthread_rwlock_t *rDestroy )
{
    int r = 0;

    *rDestroy = NULL;
    if (_spin_lite_trylock(&rwl_global)) return EBUSY;
    
    if (!rwl || !*rwl) r = EINVAL;
    else {
        rwlock_t *r_ = (rwlock_t *)*rwl;
        if (STATIC_RWL_INITIALIZER(*rwl)) *rwl = NULL;
        else if (r_->valid != LIFE_RWLOCK) r = EINVAL;
        else if (r_->busy) r = EBUSY;
        else {
            *rDestroy = *rwl;
            *rwl = NULL;
        }
    }

    _spin_lite_unlock(&rwl_global);
    return r;
}

static int rwlock_gain_both_locks(rwlock_t *rwlock)
{
  int ret;
  ret = pthread_mutex_lock(&rwlock->mex);
  if (ret != 0)
    return ret;
  ret = pthread_mutex_lock(&rwlock->mcomplete);
  if (ret != 0)
    pthread_mutex_unlock(&rwlock->mex);
  return ret;
}

static int rwlock_free_both_locks(rwlock_t *rwlock, int last_fail)
{
  int ret, ret2;
  ret = pthread_mutex_unlock(&rwlock->mcomplete);
  ret2 = pthread_mutex_unlock(&rwlock->mex);
  if (last_fail && ret2 != 0)
    ret = ret2;
  else if (!last_fail && !ret)
    ret = ret2;
  return ret;
}

#ifdef WINPTHREAD_DBG
static int print_state = 0;
void rwl_print_set(int state)
{
    print_state = state;
}

void rwl_print(volatile pthread_rwlock_t *rwl, char *txt)
{
    if (!print_state) return;
    rwlock_t *r = (rwlock_t *)*rwl;
    if (r == NULL) {
        printf("RWL%p %d %s\n",*rwl,(int)GetCurrentThreadId(),txt);
    } else {
        printf("RWL%p %d V=%0X B=%d r=%ld w=%ld L=%p %s\n",
            *rwl, 
            (int)GetCurrentThreadId(), 
            (int)r->valid, 
            (int)r->busy,
            0L,0L,NULL,txt);
    }
}
#endif

static spin_t cond_locked = {0,LIFE_SPINLOCK,0};

static __attribute__((noinline)) int rwlock_static_init(pthread_rwlock_t *rw)
{
  int r;
  _spin_lite_lock(&cond_locked);
  if (*rw != PTHREAD_RWLOCK_INITIALIZER)
  {
    _spin_lite_unlock(&cond_locked);
    return EINVAL;
  }
  r = pthread_rwlock_init (rw, NULL);
  _spin_lite_unlock(&cond_locked);
  
  return r;
}

int pthread_rwlock_init (pthread_rwlock_t *rwlock_, const pthread_rwlockattr_t *attr)
{
    rwlock_t *rwlock;
    int r;

    if(!rwlock_)
      return EINVAL;
    *rwlock_ = NULL;
    if ((rwlock = (pthread_rwlock_t)calloc(1, sizeof(*rwlock))) == NULL)
      return ENOMEM; 
    rwlock->valid = DEAD_RWLOCK;

    rwlock->nex_count = rwlock->nsh_count = rwlock->ncomplete = 0;
    if ((r = pthread_mutex_init (&rwlock->mex, NULL)) != 0)
    {
        free(rwlock);
        return r;
    }
    if ((r = pthread_mutex_init (&rwlock->mcomplete, NULL)) != 0)
    {
      pthread_mutex_destroy(&rwlock->mex);
      free(rwlock);
      return r;
    }
    if ((r = pthread_cond_init (&rwlock->ccomplete, NULL)) != 0)
    {
      pthread_mutex_destroy(&rwlock->mex);
      pthread_mutex_destroy (&rwlock->mcomplete);
      free(rwlock);
      return r;
    }
    rwlock->valid = LIFE_RWLOCK;
    *rwlock_ = rwlock;
    return r;
} 

int pthread_rwlock_destroy (pthread_rwlock_t *rwlock_)
{
    rwlock_t *rwlock;
    pthread_rwlock_t rDestroy;
    int r, r2;
    
    _spin_lite_lock(&cond_locked);
    r = rwl_ref_destroy(rwlock_,&rDestroy);
    _spin_lite_unlock(&cond_locked);
    
    if(r) return r;
    if(!rDestroy) return 0; /* destroyed a (still) static initialized rwl */

    rwlock = (rwlock_t *)rDestroy;
    r = rwlock_gain_both_locks (rwlock);
    if (r != 0)
    {
      *rwlock_ = rDestroy;
      return r;
    }
    if (rwlock->nsh_count > rwlock->ncomplete || rwlock->nex_count > 0)
    {
      *rwlock_ = rDestroy;
      r = rwlock_free_both_locks(rwlock, 1);
      if (!r)
        r = EBUSY;
      return r;
    }
    rwlock->valid  = DEAD_RWLOCK;
    r = rwlock_free_both_locks(rwlock, 0);
    if (r != 0) { *rwlock_ = rDestroy; return r; }

    r = pthread_cond_destroy(&rwlock->ccomplete);
    r2 = pthread_mutex_destroy(&rwlock->mex);
    if (!r) r = r2;
    r2 = pthread_mutex_destroy(&rwlock->mcomplete);
    if (!r) r = r2;
    rwlock->valid  = DEAD_RWLOCK;
    free(rDestroy);
    return 0;
} 

int pthread_rwlock_rdlock (pthread_rwlock_t *rwlock_)
{
  rwlock_t *rwlock;
  int ret;

  pthread_testcancel();

  ret = rwl_ref(rwlock_,0);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;

  ret = pthread_mutex_lock(&rwlock->mex);
  if (ret != 0) return rwl_unref(rwlock_, ret);
  InterlockedIncrement((long*)&rwlock->nsh_count);
  if (rwlock->nsh_count == INT_MAX)
  {
    ret = pthread_mutex_lock(&rwlock->mcomplete);
    if (ret != 0)
    {
      pthread_mutex_unlock(&rwlock->mex);
      return rwl_unref(rwlock_,ret);
    }
    rwlock->nsh_count -= rwlock->ncomplete;
    rwlock->ncomplete = 0;
    ret = rwlock_free_both_locks(rwlock, 0);
    return rwl_unref(rwlock_, ret);
  }
  ret = pthread_mutex_unlock(&rwlock->mex);
  return rwl_unref(rwlock_, ret);
}

int pthread_rwlock_timedrdlock (pthread_rwlock_t *rwlock_, const struct timespec *ts)
{
  rwlock_t *rwlock;
  int ret;

  pthread_testcancel();

  ret = rwl_ref(rwlock_,0);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;
  if ((ret = pthread_mutex_timedlock (&rwlock->mex, ts)) != 0)
      return rwl_unref(rwlock_, ret);
  InterlockedIncrement(&rwlock->nsh_count);
  if (rwlock->nsh_count == INT_MAX)
  {
    ret = pthread_mutex_timedlock(&rwlock->mcomplete, ts);
    if (ret != 0)
    {
      if (ret == ETIMEDOUT)
	InterlockedIncrement(&rwlock->ncomplete);
      pthread_mutex_unlock(&rwlock->mex);
      return rwl_unref(rwlock_, ret);
    }
    rwlock->nsh_count -= rwlock->ncomplete;
    rwlock->ncomplete = 0;
    ret = rwlock_free_both_locks(rwlock, 0);
    return rwl_unref(rwlock_, ret);
  }
  ret = pthread_mutex_unlock(&rwlock->mex);
  return rwl_unref(rwlock_, ret);
}

int pthread_rwlock_tryrdlock (pthread_rwlock_t *rwlock_)
{
  rwlock_t *rwlock;
  int ret;

  ret = rwl_ref(rwlock_,RWL_TRY);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;
  ret = pthread_mutex_trylock(&rwlock->mex);
  if (ret != 0)
      return rwl_unref(rwlock_, ret);
  InterlockedIncrement(&rwlock->nsh_count);
  if (rwlock->nsh_count == INT_MAX)
  {
    ret = pthread_mutex_lock(&rwlock->mcomplete);
    if (ret != 0)
    {
      pthread_mutex_unlock(&rwlock->mex);
      return rwl_unref(rwlock_, ret);
    }
    rwlock->nsh_count -= rwlock->ncomplete;
    rwlock->ncomplete = 0;
    ret = rwlock_free_both_locks(rwlock, 0);
    return rwl_unref(rwlock_, ret);
  }
  ret = pthread_mutex_unlock(&rwlock->mex);
  return rwl_unref(rwlock_,ret);
} 

int pthread_rwlock_trywrlock (pthread_rwlock_t *rwlock_)
{
  rwlock_t *rwlock;
  int ret;

  ret = rwl_ref(rwlock_,RWL_TRY);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;
  ret = pthread_mutex_trylock (&rwlock->mex);
  if (ret != 0)
    return rwl_unref(rwlock_, ret);
  ret = pthread_mutex_trylock(&rwlock->mcomplete);
  if (ret != 0)
  {
    int r1 = pthread_mutex_unlock(&rwlock->mex);
    if (r1 != 0)
      ret = r1;
    return rwl_unref(rwlock_, ret);
  }
  if (rwlock->nex_count != 0)
    return rwl_unref(rwlock_, EBUSY);
  if (rwlock->ncomplete > 0)
  {
    rwlock->nsh_count -= rwlock->ncomplete;
    rwlock->ncomplete = 0;
  }
  if (rwlock->nsh_count > 0)
  {
    ret = rwlock_free_both_locks(rwlock, 0);
    if (!ret)
      ret = EBUSY;
    return rwl_unref(rwlock_, ret);
  }
  rwlock->nex_count = 1;
  return rwl_unref(rwlock_, 0);
} 

int pthread_rwlock_unlock (pthread_rwlock_t *rwlock_)
{
  rwlock_t *rwlock;
  int ret;

  ret = rwl_ref_unlock(rwlock_);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;
  if (rwlock->nex_count == 0)
  {
    ret = pthread_mutex_lock(&rwlock->mcomplete);
    if (!ret)
    {
      int r1;
      InterlockedIncrement(&rwlock->ncomplete);
      if (rwlock->ncomplete == 0)
	ret = pthread_cond_signal(&rwlock->ccomplete);
      r1 = pthread_mutex_unlock(&rwlock->mcomplete);
      if (!ret)
	ret = r1;
    }
  }
  else
  {
    InterlockedDecrement(&rwlock->nex_count);
    ret = rwlock_free_both_locks(rwlock, 0);
  }
  return rwl_unref(rwlock_, ret);
} 

static void st_cancelwrite (void *arg)
{
    rwlock_t *rwlock = (rwlock_t *)arg;

    rwlock->nsh_count = - rwlock->ncomplete;
    rwlock->ncomplete = 0;
    rwlock_free_both_locks(rwlock, 0);
}

int pthread_rwlock_wrlock (pthread_rwlock_t *rwlock_)
{
  rwlock_t *rwlock;
  int ret;

  pthread_testcancel();
  ret = rwl_ref(rwlock_,0);
  if(ret != 0) return ret;

  rwlock = (rwlock_t *)*rwlock_;
  ret = rwlock_gain_both_locks(rwlock);
  if (ret != 0)
    return rwl_unref(rwlock_,ret);

  if (rwlock->nex_count == 0)
  {
    if (rwlock->ncomplete > 0)
    {
      rwlock->nsh_count -= rwlock->ncomplete;
      rwlock->ncomplete = 0;
    }
    if (rwlock->nsh_count > 0)
    {
      rwlock->ncomplete = -rwlock->nsh_count;
      pthread_cleanup_push(st_cancelwrite, (void *) rwlock);
      do {
	ret = pthread_cond_wait(&rwlock->ccomplete, &rwlock->mcomplete);
      } while (!ret && rwlock->ncomplete < 0);

      pthread_cleanup_pop(!ret ? 0 : 1);
      if (!ret)
	rwlock->nsh_count = 0;
    }
  }
  if(!ret)
    InterlockedIncrement((long*)&rwlock->nex_count);
  return rwl_unref(rwlock_,ret);
}

int pthread_rwlock_timedwrlock (pthread_rwlock_t *rwlock_, const struct timespec *ts)
{
  int ret;
  rwlock_t *rwlock;

  pthread_testcancel();
  if (!rwlock_ || !ts)
    return EINVAL;
  if ((ret = rwl_ref(rwlock_,0)) != 0)
    return ret;
  rwlock = (rwlock_t *)*rwlock_;

  ret = pthread_mutex_timedlock(&rwlock->mex, ts);
  if (ret != 0)
    return rwl_unref(rwlock_,ret);
  ret = pthread_mutex_timedlock (&rwlock->mcomplete, ts);
  if (ret != 0)
  {
    pthread_mutex_unlock(&rwlock->mex);
    return rwl_unref(rwlock_,ret);
  }
  if (rwlock->nex_count == 0)
  {
    if (rwlock->ncomplete > 0)
    {
      rwlock->nsh_count -= rwlock->ncomplete;
      rwlock->ncomplete = 0;
    }
    if (rwlock->nsh_count > 0)
    {
      rwlock->ncomplete = -rwlock->nsh_count;
      pthread_cleanup_push(st_cancelwrite, (void *) rwlock);
      do {
	ret = pthread_cond_timedwait(&rwlock->ccomplete, &rwlock->mcomplete, ts);
      } while (rwlock->ncomplete < 0 && !ret);
      pthread_cleanup_pop(!ret ? 0 : 1);

      if (!ret)
	rwlock->nsh_count = 0;
    }
  }
  if(!ret)
    InterlockedIncrement((long*)&rwlock->nex_count);
  return rwl_unref(rwlock_,ret);
}

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *a)
{
  if (!a)
    return EINVAL;
  return 0;
}

int pthread_rwlockattr_init(pthread_rwlockattr_t *a)
{
  if (!a)
    return EINVAL;
  *a = PTHREAD_PROCESS_PRIVATE;
  return 0;
}

int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *a, int *s)
{
  if (!a || !s)
    return EINVAL;
  *s = *a;
  return 0;
}

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *a, int s)
{
  if (!a || (s != PTHREAD_PROCESS_SHARED && s != PTHREAD_PROCESS_PRIVATE))
    return EINVAL;
  *a = s;
  return 0;
}
