/*
  Copyright (C) 2008 MySQL AB
  Copyright (C) 2008-2010 Alexey Kopytov <akopytov@gmail.com>

  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
*/

/*
 This file contains Windows port of Posix functionality used in sysbench
 (partial implementation of pthreads, gettimeofday() and random()
*/

#define _CRT_RAND_S /* for rand_s */
#include <stdlib.h>
#include <windows.h>
#include <errno.h>
#include "sb_win.h"

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
{
  cond->waiting= 0;
  InitializeCriticalSection(&cond->lock_waiting);

  cond->events[SIGNAL]= CreateEvent(NULL, FALSE, FALSE, NULL);
  cond->events[BROADCAST]= CreateEvent(NULL, TRUE, FALSE, NULL);
  cond->broadcast_block_event= CreateEvent(NULL, TRUE, TRUE, NULL);

  if( cond->events[SIGNAL] == NULL ||
    cond->events[BROADCAST] == NULL ||
    cond->broadcast_block_event == NULL )
    return ENOMEM;
  return 0;
}

int pthread_cond_destroy(pthread_cond_t *cond)
{
  DeleteCriticalSection(&cond->lock_waiting);

  if (CloseHandle(cond->events[SIGNAL]) == 0 ||
    CloseHandle(cond->events[BROADCAST]) == 0 ||
    CloseHandle(cond->broadcast_block_event) == 0)
    return EINVAL;
  return 0;
}


int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
{
  return pthread_cond_timedwait(cond,mutex,NULL);
}


int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,
struct timespec *abstime)
{
  int result;
  long timeout; 
  union {
    FILETIME ft;
    long long i64;
  }now;

  if( abstime != NULL )
  {
    long long stoptime_nanos = (abstime->tv_sec*1000000000 + abstime->tv_nsec);
    long long now_nanos;
    long long timeout_nanos;

    GetSystemTimeAsFileTime(&now.ft);
    now_nanos = now.i64 *100;
    timeout_nanos = stoptime_nanos - now_nanos;
    timeout = (long)(timeout_nanos /1000000);
  }
  else
  {
    /* No time specified; don't expire */
    timeout= INFINITE;
  }

  /* 
  Block access if previous broadcast hasn't finished.
  This is just for safety and should normally not
  affect the total time spent in this function.
  */
  WaitForSingleObject(cond->broadcast_block_event, INFINITE);

  EnterCriticalSection(&cond->lock_waiting);
  cond->waiting++;
  LeaveCriticalSection(&cond->lock_waiting);

  LeaveCriticalSection(mutex);

  result= WaitForMultipleObjects(2, cond->events, FALSE, timeout);

  EnterCriticalSection(&cond->lock_waiting);
  cond->waiting--;

  if (cond->waiting == 0 && result == (WAIT_OBJECT_0+BROADCAST))
  {
    /*
      We're the last waiter to be notified or to stop waiting, so
      reset the manual event.
    */
    /* Close broadcast gate */
    ResetEvent(cond->events[BROADCAST]);
    /* Open block gate */
    SetEvent(cond->broadcast_block_event);
  }
  LeaveCriticalSection(&cond->lock_waiting);

  EnterCriticalSection(mutex);

  return result == WAIT_TIMEOUT ? ETIMEDOUT : 0;
}

int pthread_cond_signal(pthread_cond_t *cond)
{
  EnterCriticalSection(&cond->lock_waiting);

  if(cond->waiting > 0)
    SetEvent(cond->events[SIGNAL]);

  LeaveCriticalSection(&cond->lock_waiting);

  return 0;
}


int pthread_cond_broadcast(pthread_cond_t *cond)
{
  EnterCriticalSection(&cond->lock_waiting);
  /*
    The mutex protect us from broadcasting if
    there isn't any thread waiting to open the
    block gate after this call has closed it.
  */
  if(cond->waiting > 0)
  {
    /* Close block gate */
    ResetEvent(cond->broadcast_block_event); 
    /* Open broadcast gate */
    SetEvent(cond->events[BROADCAST]);
  }

  LeaveCriticalSection(&cond->lock_waiting);  

  return 0;
}

int pthread_attr_init(pthread_attr_t *attr)
{
  attr->stacksize = 0;
  return 0;
}

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
{
  InitializeCriticalSection(mutex);
  return 0;
}
int pthread_mutex_lock(pthread_mutex_t *mutex)
{
  EnterCriticalSection(mutex);
  return 0;
}
int pthread_mutex_unlock(pthread_mutex_t *mutex)
{
  LeaveCriticalSection(mutex);
  return 0;
}
int pthread_mutex_destroy(pthread_mutex_t *mutex)
{
  DeleteCriticalSection(mutex);
  return 0;
}

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void*), void *arg)
{
  DWORD tid;
  *thread = CreateThread(NULL, attr->stacksize,
                         (LPTHREAD_START_ROUTINE) start_routine, arg,
                         STACK_SIZE_PARAM_IS_A_RESERVATION, &tid);
  if (*thread != NULL)
    return 0;
  return -1;
}

int pthread_cancel(pthread_t thread)
{
  return !TerminateThread(thread, 0);
}

/* Minimal size of thread stack on Windows*/
#define PTHREAD_STACK_MIN 65536*2

int pthread_attr_setstacksize( pthread_attr_t *attr, size_t stacksize)
{
  if(stacksize)
    attr->stacksize = max(stacksize, PTHREAD_STACK_MIN);
  return 0;
}

int pthread_join(pthread_t pthread, void **value_ptr)
{
  if (WaitForSingleObject(pthread, INFINITE) != WAIT_OBJECT_0)
    return -1;
  if (value_ptr)
    *value_ptr = 0;
  return 0;

}

pthread_t pthread_self(void)
{
  return GetCurrentThreadId();
}

/*
 One time initialization. For simplicity, we assume initializer thread
 does not exit within init_routine().
*/
int pthread_once(pthread_once_t *once_control, 
    void (*init_routine)(void))
{
  LONG state = InterlockedCompareExchange(once_control, PTHREAD_ONCE_INPROGRESS,
                                          PTHREAD_ONCE_INIT);
  switch(state)
  {
  case PTHREAD_ONCE_INIT:
    /* This is initializer thread */
    (*init_routine)();
    *once_control = PTHREAD_ONCE_DONE;
    break;

  case PTHREAD_ONCE_INPROGRESS:
    /* init_routine in progress. Wait for its completion */
    while(*once_control == PTHREAD_ONCE_INPROGRESS)
    {
      Sleep(1);
    }
    break;
  case PTHREAD_ONCE_DONE:
    /* Nothing to do */
    break;
  }
  return 0;
}

#include <time.h>
int gettimeofday(struct timeval * tp, void * tzp)
{
  static long long qpf = 0, startup_time = 0;
  long long qpc;
  if(qpf == 0)
  {
    QueryPerformanceFrequency((LARGE_INTEGER *)&qpf);
  }
  if(startup_time == 0)
  {
    QueryPerformanceCounter((LARGE_INTEGER *)&qpc);
    startup_time = time(NULL) - (qpc/qpf);
  }
  QueryPerformanceCounter((LARGE_INTEGER *)&qpc);
  tp->tv_sec  = (long)(startup_time + qpc/qpf);
  tp->tv_usec = (long)((qpc%qpf)*1000000/qpf);
  return 0;
}

int random()
{
  int ret;
  rand_s(&ret);
  return ret;
}
