/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#include "itkConditionVariable.h"

namespace itk
{
ConditionVariable::ConditionVariable()
{
  m_ConditionVariable.m_NumberOfWaiters = 0;
  m_ConditionVariable.m_WasBroadcast = 0;
  m_ConditionVariable.m_Semaphore = CreateSemaphore(nullptr,    // no security
                                                    0,          // initial value
                                                    0x7fffffff, // max count
                                                    nullptr);   // unnamed
  InitializeCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
  m_ConditionVariable.m_WaitersAreDone = CreateEvent(nullptr,  // no security
                                                     FALSE,    // auto-reset
                                                     FALSE,    // non-signaled initially
                                                     nullptr); // unnamed
}

ConditionVariable::~ConditionVariable()
{
  CloseHandle(m_ConditionVariable.m_Semaphore);
  CloseHandle(m_ConditionVariable.m_WaitersAreDone);
  DeleteCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
}

void
ConditionVariable::Signal()
{
  EnterCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
  bool haveWaiters = (m_ConditionVariable.m_NumberOfWaiters > 0);
  LeaveCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);

  // if there were not any waiters, then this is a no-op
  if (haveWaiters)
  {
    ReleaseSemaphore(m_ConditionVariable.m_Semaphore, 1, 0);
  }
}

void
ConditionVariable::Broadcast()
{
  // This is needed to ensure that m_NumberOfWaiters and m_WasBroadcast are
  // consistent
  EnterCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
  bool haveWaiters = false;

  if (m_ConditionVariable.m_NumberOfWaiters > 0)
  {
    // We are broadcasting, even if there is just one waiter...
    // Record that we are broadcasting, which helps optimize Wait()
    // for the non-broadcast case
    m_ConditionVariable.m_WasBroadcast = 1;
    haveWaiters = true;
  }

  if (haveWaiters)
  {
    // Wake up all waiters atomically
    ReleaseSemaphore(m_ConditionVariable.m_Semaphore, m_ConditionVariable.m_NumberOfWaiters, 0);

    LeaveCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);

    // Wait for all the awakened threads to acquire the counting
    // semaphore
    WaitForSingleObject(m_ConditionVariable.m_WaitersAreDone, INFINITE);
    // This assignment is ok, even without the m_NumberOfWaitersLock held
    // because no other waiter threads can wake up to access it.
    m_ConditionVariable.m_WasBroadcast = 0;
  }
  else
  {
    LeaveCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
  }
}

void
ConditionVariable::Wait(SimpleMutexLock * mutex)
{
  // Avoid race conditions
  EnterCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);
  m_ConditionVariable.m_NumberOfWaiters++;
  LeaveCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);

  // This call atomically releases the mutex and waits on the
  // semaphore until signaled
  SignalObjectAndWait(mutex->GetMutexLock(), m_ConditionVariable.m_Semaphore, INFINITE, FALSE);

  // Reacquire lock to avoid race conditions
  EnterCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);

  // We're no longer waiting....
  m_ConditionVariable.m_NumberOfWaiters--;

  // Check to see if we're the last waiter after the broadcast
  int lastWaiter = m_ConditionVariable.m_WasBroadcast && m_ConditionVariable.m_NumberOfWaiters == 0;

  LeaveCriticalSection(&m_ConditionVariable.m_NumberOfWaitersLock);

  // If we're the last waiter thread during this particular broadcast
  // then let the other threads proceed
  if (lastWaiter)
  {
    // This call atomically signals the m_WaitersAreDone event and waits
    // until it can acquire the external mutex.  This is required to
    // ensure fairness
    SignalObjectAndWait(m_ConditionVariable.m_WaitersAreDone, mutex->GetMutexLock(), INFINITE, FALSE);
  }
  else
  {
    // Always regain the external mutex since that's the guarantee we
    // give to our callers
    WaitForSingleObject(mutex->GetMutexLock(), INFINITE);
  }
}
} // end of namespace itk
