/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/

#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

// ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK.
bool ON_SleepLock::IsLocked() const
{
  // ON_SleepLock::IsLocked() is not reliable. Do not use it. It will be deleted in the next SDK.
#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG)
  const bool b = m_lockedX.test_and_set(std::memory_order_acquire);
  if (false == b)
    m_lockedX.clear(std::memory_order_release);
  return b;
#else
  return 0 != m_locked;
#endif
}

bool ON_SleepLock::GetLockOrReturnFalse()
{
  // returns true if the value of the m_locked changed from 0 to 1.
  // returns false if the value of m_locked was 1 and was not changed.
  // In all cases, the final value of m_locked = 1.

#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG)
  // std::atomic_flag.test_and_set
  // does the following as an atomic operation:
  //    bool b = m_lockedX;
  //    m_lockedX = true;
  //    // in all cases, the final value of m_lockedX = true.
  bool locked = m_lockedX.test_and_set(std::memory_order_acquire);
  return (false == locked);
#else
  // Windows int locked = InterlockedCompareExchange(((LONG*)(&(m_locked))), 1, 0)
  // does the following as an atomic operation:
  //    int locked = m_locked;
  //    if ( 0 == m_locked )
  //      m_locked = 1;
  //    // in all cases, the final value of m_locked = 1.
  int locked = InterlockedCompareExchange(((LONG*)(&(m_locked))), 1, 0);
  return (0 == locked);
#endif
}

bool ON_SleepLock::ReturnLock()
{
  // returns true if the input value of m_locked != 0.
  // returns false of the input value of m_locked = 0.
  // In all cases, the final value of m_locked = 0.

#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG)
  const bool b = m_lockedX.test_and_set(std::memory_order_acquire);
  m_lockedX.clear(std::memory_order_release);
  return b;
#else
  // Windows int locked = InterlockedExchange(((LONG*)(&(m_locked))),0);
  // does the following as an atomic operation:
  //    int locked = m_locked;
  //    m_locked = 0;
  //    // in all cases, the final value of m_locked = 0.
  int locked = InterlockedExchange(((LONG*)(&(m_locked))),0);
  return (0 != locked);
#endif
}

bool ON_SleepLock::GetLock()
{
  return GetLock(ON_SleepLock::DefaultWaitInterval,0);
}

bool ON_SleepLock::GetLock(
  unsigned int interval_wait_msecs,
  unsigned int max_wait_msecs
  )
{
  return GetLock(interval_wait_msecs,max_wait_msecs,false);
}

bool ON_SleepLock::GetLock( 
  unsigned int interval_wait_msecs,
  unsigned int max_wait_msecs,
  bool bStealLockAfterWaiting
  )
{
  // int locked = ON_IntSleepLock_Test(m_locked,0,1)
  // performs the following test and assignment in
  // an atomic operation.
  //
  // int locked = m_locked;
  // if ( 0 == locked )
  //   m_locked = 1;
  if ( GetLockOrReturnFalse() )
    return true; // acquired lock

  if ( interval_wait_msecs <= 0 )
    interval_wait_msecs = ON_SleepLock::DefaultWaitInterval;

  for(;;)
  {
    // Something else currently has this lock.

    // ON_PointerSleepLock_SuspendThisThread() suspends the thread
    // (not the process) for the specified number of milliseconds.
#if defined(ON_SLEEPLOCK_USES_STD_ATOMIC_FLAG)
    std::this_thread::sleep_for(std::chrono::milliseconds(interval_wait_msecs));
#else
    // Windows - sleep the thread (not the process)
    ::Sleep(interval_wait_msecs);
#endif

    // locked = ON_IntSleepLock_Test(m_locked,0,1)
    // performs the following test and assignment in
    // an atomic operation.
    //
    // locked = m_locked;
    // if ( 0 == locked )
    //   m_locked = 1;
    if ( GetLockOrReturnFalse() )
      return true; // acquired lock
    
    if ( max_wait_msecs > 0 )
    {
      if ( interval_wait_msecs >= max_wait_msecs )
      {
        if (bStealLockAfterWaiting)
        {
          // If the caller set max_wait_msecs to a value that is too small, 
          // has a bug in their code, then this is a bad idea, 
          // but it's also their own fault.
          //
          // If the resource is locked because another thread locked it and 
          // then forgot to unlock it or terminated before it could be unlocked,
          // then this is a good thing because it gets this thread unblocked 
          // and back in business.
          //
          ON_WARNING("Stealing a resource lock.");
          GetLockOrReturnFalse();
          return true;
        }

        // Give up and return false.
        break;
      }
      max_wait_msecs -= interval_wait_msecs;
    }
  }

  return false;  
}


ON_SleepLockGuard::ON_SleepLockGuard(class ON_SleepLock& sleep_lock)
  : m_sleep_lock(sleep_lock)
{
  m_bIsManagingLock = m_sleep_lock.GetLock();
}


ON_SleepLockGuard::ON_SleepLockGuard(
  class ON_SleepLock& sleep_lock,
  unsigned int interval_wait_msecs,
  unsigned int max_wait_msecs
  )
  : m_sleep_lock(sleep_lock)
{
  m_bIsManagingLock = sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs);
}

ON_SleepLockGuard::ON_SleepLockGuard(
  class ON_SleepLock& sleep_lock,
  unsigned int interval_wait_msecs,
  unsigned int max_wait_msecs,
  bool bStealLockAfterWaiting
  )
  : m_sleep_lock(sleep_lock)
{
  m_bIsManagingLock = sleep_lock.GetLock(interval_wait_msecs,max_wait_msecs,bStealLockAfterWaiting);
}

ON_SleepLockGuard::~ON_SleepLockGuard()
{
  ReturnLock();
}


void ON_SleepLockGuard::ReturnLock()
{
  if (m_bIsManagingLock)
  {
    m_bIsManagingLock = false;
    m_sleep_lock.ReturnLock();
  }
}

const bool ON_SleepLockGuard::IsManagingLock() const
{
  return m_bIsManagingLock;
}


