#pragma once
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <process.h>
#include <tlhelp32.h>
#include <sys/utime.h>
#include <io.h>
#include <direct.h>
#else
#include <assert.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#endif

namespace ydlidar {
namespace core {
namespace base {

class Locker {
 public:
  enum LOCK_STATUS {
    LOCK_OK = 0,
    LOCK_TIMEOUT = -1,
    LOCK_FAILED = -2
  };

  Locker() {
#ifdef _WIN32
    _lock = NULL;
#endif
    init();
  }

  ~Locker() {
    release();
  }

  Locker::LOCK_STATUS lock(unsigned long timeout = 0xFFFFFFFF) {
#ifdef _WIN32

    switch (WaitForSingleObject(_lock,
                                timeout == 0xFFFFFFF ? INFINITE : (DWORD)timeout)) {
      case WAIT_ABANDONED:
        return LOCK_FAILED;

      case WAIT_OBJECT_0:
        return LOCK_OK;

      case WAIT_TIMEOUT:
        return LOCK_TIMEOUT;
    }

#else
#ifdef _MACOS

    if (timeout != 0) {
      if (pthread_mutex_lock(&_lock) == 0) {
        return LOCK_OK;
      }
    }

#else

    if (timeout == 0xFFFFFFFF) {
      if (pthread_mutex_lock(&_lock) == 0) {
        return LOCK_OK;
      }
    }

#endif
    else if (timeout == 0) {
      if (pthread_mutex_trylock(&_lock) == 0) {
        return LOCK_OK;
      }
    }

#ifndef _MACOS
    else {
      timespec wait_time;
      timeval now;
      gettimeofday(&now, NULL);

      wait_time.tv_sec = timeout / 1000 + now.tv_sec;
      wait_time.tv_nsec = (timeout % 1000) * 1000000 + now.tv_usec * 1000;

      if (wait_time.tv_nsec >= 1000000000) {
        ++wait_time.tv_sec;
        wait_time.tv_nsec -= 1000000000;
      }

#if !defined(__ANDROID__)

      switch (pthread_mutex_timedlock(&_lock, &wait_time)) {
        case 0:
          return LOCK_OK;

        case ETIMEDOUT:
          return LOCK_TIMEOUT;
      }

#else
      struct timeval timenow;
      struct timespec sleepytime;
      /* This is just to avoid a completely busy wait */
      sleepytime.tv_sec = 0;
      sleepytime.tv_nsec = 10000000; /* 10ms */

      while (pthread_mutex_trylock(&_lock) == EBUSY) {
        gettimeofday(&timenow, NULL);

        if (timenow.tv_sec >= wait_time.tv_sec &&
            (timenow.tv_usec * 1000) >= wait_time.tv_nsec) {
          return LOCK_TIMEOUT;
        }

        nanosleep(&sleepytime, NULL);
      }

      return LOCK_OK;

#endif

    }

#endif
#endif

    return LOCK_FAILED;
  }


  void unlock() {
#ifdef _WIN32
    ReleaseMutex(_lock);
#else
    pthread_mutex_unlock(&_lock);
#endif
  }

#ifdef _WIN32
  HANDLE getLockHandle() {
    return _lock;
  }
#else
  pthread_mutex_t *getLockHandle() {
    return &_lock;
  }
#endif



 protected:
  void init() {
#ifdef _WIN32
    _lock = CreateMutex(NULL, FALSE, NULL);
#else
    pthread_mutex_init(&_lock, NULL);
#endif
  }

  void release() {
    unlock();
#ifdef _WIN32

    if (_lock) {
      CloseHandle(_lock);
    }

    _lock = NULL;
#else
    pthread_mutex_destroy(&_lock);
#endif
  }

#ifdef _WIN32
  HANDLE  _lock;
#else
  pthread_mutex_t _lock;
#endif
};


class Event {
 public:

  enum {
    EVENT_OK = 1,
    EVENT_TIMEOUT = 2,
    EVENT_FAILED = 0,
  };

  explicit Event(bool isAutoReset = true, bool isSignal = false)
#ifdef _WIN32
    : _event(NULL)
#else
    : _is_signalled(isSignal)
    , _isAutoReset(isAutoReset)
#endif
  {
#ifdef _WIN32
    _event = CreateEvent(NULL, isAutoReset ? FALSE : TRUE, isSignal ? TRUE : FALSE,
                         NULL);
#else
    int ret = pthread_condattr_init(&_cond_cattr);

    if (ret != 0) {
      fprintf(stderr, "Failed to init condattr...\n");
      fflush(stderr);
    }

    ret = pthread_condattr_setclock(&_cond_cattr, CLOCK_MONOTONIC);
    pthread_mutex_init(&_cond_locker, NULL);
    ret =  pthread_cond_init(&_cond_var, &_cond_cattr);
#endif
  }

  ~ Event() {
    release();
  }

  void set(bool isSignal = true) 
  {
    if (isSignal) {
#ifdef _WIN32
      SetEvent(_event);
#else
      pthread_mutex_lock(&_cond_locker);
      if (!_is_signalled) 
      {
        _is_signalled = true;
        pthread_cond_signal(&_cond_var);
      }
      pthread_mutex_unlock(&_cond_locker);
#endif
    } else {
#ifdef _WIN32
      ResetEvent(_event);
#else
      pthread_mutex_lock(&_cond_locker);
      _is_signalled = false;
      pthread_mutex_unlock(&_cond_locker);
#endif
    }
  }

  unsigned long wait(unsigned long timeout = 0xFFFFFFFF) {
#ifdef _WIN32

    switch (WaitForSingleObject(_event,
                                timeout == 0xFFFFFFF ? INFINITE : (DWORD)timeout)) {
      case WAIT_FAILED:
        return EVENT_FAILED;

      case WAIT_OBJECT_0:
        return EVENT_OK;

      case WAIT_TIMEOUT:
        return EVENT_TIMEOUT;
    }

    return EVENT_OK;
#else
    unsigned long ans = EVENT_OK;
    pthread_mutex_lock(&_cond_locker);

    if (!_is_signalled) 
    {
      if (timeout == 0xFFFFFFFF) {
        pthread_cond_wait(&_cond_var, &_cond_locker);
      } else {
        struct timespec wait_time;
        clock_gettime(CLOCK_MONOTONIC, &wait_time);

        wait_time.tv_sec += timeout / 1000;
        wait_time.tv_nsec += (timeout % 1000) * 1000000ULL;
        if (wait_time.tv_nsec >= 1000000000) {
          ++wait_time.tv_sec;
          wait_time.tv_nsec -= 1000000000;
        }

        switch (pthread_cond_timedwait(&_cond_var, &_cond_locker, &wait_time)) 
        {
          case 0:
            // signalled
            break;

          case ETIMEDOUT:
            // time up
            ans = EVENT_TIMEOUT;
            goto _final;
            break;

          default:
            ans = EVENT_FAILED;
            goto _final;
        }
      }
    }

    assert(_is_signalled);

    if (_isAutoReset) {
      _is_signalled = false;
    }

_final:
    pthread_mutex_unlock(&_cond_locker);

    return ans;
#endif

  }
 protected:

  void release() {
#ifdef _WIN32
    CloseHandle(_event);
#else
    pthread_condattr_destroy(&_cond_cattr);
    pthread_mutex_destroy(&_cond_locker);
    pthread_cond_destroy(&_cond_var);
#endif
  }

#ifdef _WIN32
  HANDLE _event;
#else
  pthread_condattr_t     _cond_cattr;
  pthread_cond_t         _cond_var;
  pthread_mutex_t        _cond_locker;
  bool                   _is_signalled;
  bool                   _isAutoReset;
#endif
};

class ScopedLocker {
 public :
  explicit ScopedLocker(Locker &l): _binded(l) {
    _binded.lock();
  }

  void forceUnlock() {
    _binded.unlock();
  }
  ~ScopedLocker() {
    _binded.unlock();
  }
  Locker &_binded;
};

}//base
}//core
}//ydlidar


