/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"
#include "prinrval.h"
#include "prtypes.h"

#if defined(WIN95)
/*
** Some local variables report warnings on Win95 because the code paths
** using them are conditioned on HAVE_CUSTOME_USER_THREADS.
** The pragma suppresses the warning.
**
*/
#  pragma warning(disable : 4101)
#endif

/*
** Notify one thread that it has finished waiting on a condition variable
** Caller must hold the _PR_CVAR_LOCK(cv)
*/
PRBool _PR_NotifyThread(PRThread* thread, PRThread* me) {
  PRBool rv;

  PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);

  _PR_THREAD_LOCK(thread);
  PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
  if (!_PR_IS_NATIVE_THREAD(thread)) {
    if (thread->wait.cvar != NULL) {
      thread->wait.cvar = NULL;

      _PR_SLEEPQ_LOCK(thread->cpu);
      /* The notify and timeout can collide; in which case both may
       * attempt to delete from the sleepQ; only let one do it.
       */
      if (thread->flags & (_PR_ON_SLEEPQ | _PR_ON_PAUSEQ)) {
        _PR_DEL_SLEEPQ(thread, PR_TRUE);
      }
      _PR_SLEEPQ_UNLOCK(thread->cpu);

      if (thread->flags & _PR_SUSPENDING) {
        /*
         * set thread state to SUSPENDED; a Resume operation
         * on the thread will move it to the runQ
         */
        thread->state = _PR_SUSPENDED;
        _PR_MISCQ_LOCK(thread->cpu);
        _PR_ADD_SUSPENDQ(thread, thread->cpu);
        _PR_MISCQ_UNLOCK(thread->cpu);
        _PR_THREAD_UNLOCK(thread);
      } else {
        /* Make thread runnable */
        thread->state = _PR_RUNNABLE;
        _PR_THREAD_UNLOCK(thread);

        _PR_AddThreadToRunQ(me, thread);
        _PR_MD_WAKEUP_WAITER(thread);
      }

      rv = PR_TRUE;
    } else {
      /* Thread has already been notified */
      _PR_THREAD_UNLOCK(thread);
      rv = PR_FALSE;
    }
  } else { /* If the thread is a native thread */
    if (thread->wait.cvar) {
      thread->wait.cvar = NULL;

      if (thread->flags & _PR_SUSPENDING) {
        /*
         * set thread state to SUSPENDED; a Resume operation
         * on the thread will enable the thread to run
         */
        thread->state = _PR_SUSPENDED;
      } else {
        thread->state = _PR_RUNNING;
      }
      _PR_THREAD_UNLOCK(thread);
      _PR_MD_WAKEUP_WAITER(thread);
      rv = PR_TRUE;
    } else {
      _PR_THREAD_UNLOCK(thread);
      rv = PR_FALSE;
    }
  }

  return rv;
}

/*
 * Notify thread waiting on cvar; called when thread is interrupted
 *  The thread lock is held on entry and released before return
 */
void _PR_NotifyLockedThread(PRThread* thread) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRCondVar* cvar;
  PRThreadPriority pri;

  if (!_PR_IS_NATIVE_THREAD(me)) {
    PR_ASSERT(_PR_MD_GET_INTSOFF() != 0);
  }

  cvar = thread->wait.cvar;
  thread->wait.cvar = NULL;
  _PR_THREAD_UNLOCK(thread);

  _PR_CVAR_LOCK(cvar);
  _PR_THREAD_LOCK(thread);

  if (!_PR_IS_NATIVE_THREAD(thread)) {
    _PR_SLEEPQ_LOCK(thread->cpu);
    /* The notify and timeout can collide; in which case both may
     * attempt to delete from the sleepQ; only let one do it.
     */
    if (thread->flags & (_PR_ON_SLEEPQ | _PR_ON_PAUSEQ)) {
      _PR_DEL_SLEEPQ(thread, PR_TRUE);
    }
    _PR_SLEEPQ_UNLOCK(thread->cpu);

    /* Make thread runnable */
    pri = thread->priority;
    thread->state = _PR_RUNNABLE;

    PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));

    _PR_AddThreadToRunQ(me, thread);
    _PR_THREAD_UNLOCK(thread);

    _PR_MD_WAKEUP_WAITER(thread);
  } else {
    if (thread->flags & _PR_SUSPENDING) {
      /*
       * set thread state to SUSPENDED; a Resume operation
       * on the thread will enable the thread to run
       */
      thread->state = _PR_SUSPENDED;
    } else {
      thread->state = _PR_RUNNING;
    }
    _PR_THREAD_UNLOCK(thread);
    _PR_MD_WAKEUP_WAITER(thread);
  }

  _PR_CVAR_UNLOCK(cvar);
  return;
}

/*
** Make the given thread wait for the given condition variable
*/
PRStatus _PR_WaitCondVar(PRThread* thread, PRCondVar* cvar, PRLock* lock,
                         PRIntervalTime timeout) {
  PRIntn is;
  PRStatus rv = PR_SUCCESS;

  PR_ASSERT(thread == _PR_MD_CURRENT_THREAD());
  PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));

#ifdef _PR_GLOBAL_THREADS_ONLY
  if (_PR_PENDING_INTERRUPT(thread)) {
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    thread->flags &= ~_PR_INTERRUPT;
    return PR_FAILURE;
  }

  thread->wait.cvar = cvar;
  lock->owner = NULL;
  _PR_MD_WAIT_CV(&cvar->md, &lock->ilock, timeout);
  thread->wait.cvar = NULL;
  lock->owner = thread;
  if (_PR_PENDING_INTERRUPT(thread)) {
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    thread->flags &= ~_PR_INTERRUPT;
    return PR_FAILURE;
  }

  return PR_SUCCESS;
#else  /* _PR_GLOBAL_THREADS_ONLY */

  if (!_PR_IS_NATIVE_THREAD(thread)) {
    _PR_INTSOFF(is);
  }

  _PR_CVAR_LOCK(cvar);
  _PR_THREAD_LOCK(thread);

  if (_PR_PENDING_INTERRUPT(thread)) {
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    thread->flags &= ~_PR_INTERRUPT;
    _PR_CVAR_UNLOCK(cvar);
    _PR_THREAD_UNLOCK(thread);
    if (!_PR_IS_NATIVE_THREAD(thread)) {
      _PR_INTSON(is);
    }
    return PR_FAILURE;
  }

  thread->state = _PR_COND_WAIT;
  thread->wait.cvar = cvar;

  /*
  ** Put the caller thread on the condition variable's wait Q
  */
  PR_APPEND_LINK(&thread->waitQLinks, &cvar->condQ);

  /* Note- for global scope threads, we don't put them on the
   *       global sleepQ, so each global thread must put itself
   *       to sleep only for the time it wants to.
   */
  if (!_PR_IS_NATIVE_THREAD(thread)) {
    _PR_SLEEPQ_LOCK(thread->cpu);
    _PR_ADD_SLEEPQ(thread, timeout);
    _PR_SLEEPQ_UNLOCK(thread->cpu);
  }
  _PR_CVAR_UNLOCK(cvar);
  _PR_THREAD_UNLOCK(thread);

  /*
  ** Release lock protecting the condition variable and thereby giving time
  ** to the next thread which can potentially notify on the condition variable
  */
  PR_Unlock(lock);

  PR_LOG(_pr_cvar_lm, PR_LOG_MIN,
         ("PR_Wait: cvar=%p waiting for %d", cvar, timeout));

  rv = _PR_MD_WAIT(thread, timeout);

  _PR_CVAR_LOCK(cvar);
  PR_REMOVE_LINK(&thread->waitQLinks);
  _PR_CVAR_UNLOCK(cvar);

  PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("PR_Wait: cvar=%p done waiting", cvar));

  if (!_PR_IS_NATIVE_THREAD(thread)) {
    _PR_INTSON(is);
  }

  /* Acquire lock again that we had just relinquished */
  PR_Lock(lock);

  if (_PR_PENDING_INTERRUPT(thread)) {
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    thread->flags &= ~_PR_INTERRUPT;
    return PR_FAILURE;
  }

  return rv;
#endif /* _PR_GLOBAL_THREADS_ONLY */
}

void _PR_NotifyCondVar(PRCondVar* cvar, PRThread* me) {
#ifdef _PR_GLOBAL_THREADS_ONLY
  _PR_MD_NOTIFY_CV(&cvar->md, &cvar->lock->ilock);
#else /* _PR_GLOBAL_THREADS_ONLY */

  PRCList* q;
  PRIntn is;

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  PR_ASSERT(_PR_IS_NATIVE_THREAD(me) || _PR_MD_GET_INTSOFF() != 0);

  _PR_CVAR_LOCK(cvar);
  q = cvar->condQ.next;
  while (q != &cvar->condQ) {
    PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("_PR_NotifyCondVar: cvar=%p", cvar));
    if (_PR_THREAD_CONDQ_PTR(q)->wait.cvar) {
      if (_PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me) == PR_TRUE) {
        break;
      }
    }
    q = q->next;
  }
  _PR_CVAR_UNLOCK(cvar);

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }

#endif /* _PR_GLOBAL_THREADS_ONLY */
}

/*
** Cndition variable debugging log info.
*/
PRUint32 _PR_CondVarToString(PRCondVar* cvar, char* buf, PRUint32 buflen) {
  PRUint32 nb;

  if (cvar->lock->owner) {
    nb = PR_snprintf(buf, buflen, "[%p] owner=%ld[%p]", cvar,
                     cvar->lock->owner->id, cvar->lock->owner);
  } else {
    nb = PR_snprintf(buf, buflen, "[%p]", cvar);
  }
  return nb;
}

/*
** Expire condition variable waits that are ready to expire. "now" is the
*current
** time.
*/
void _PR_ClockInterrupt(void) {
  PRThread *thread, *me = _PR_MD_CURRENT_THREAD();
  _PRCPU* cpu = me->cpu;
  PRIntervalTime elapsed, now;

  PR_ASSERT(_PR_MD_GET_INTSOFF() != 0);
  /* Figure out how much time elapsed since the last clock tick */
  now = PR_IntervalNow();
  elapsed = now - cpu->last_clock;
  cpu->last_clock = now;

  PR_LOG(_pr_clock_lm, PR_LOG_MAX, ("ExpireWaits: elapsed=%lld usec", elapsed));

  while (1) {
    _PR_SLEEPQ_LOCK(cpu);
    if (_PR_SLEEPQ(cpu).next == &_PR_SLEEPQ(cpu)) {
      _PR_SLEEPQ_UNLOCK(cpu);
      break;
    }

    thread = _PR_THREAD_PTR(_PR_SLEEPQ(cpu).next);
    PR_ASSERT(thread->cpu == cpu);

    if (elapsed < thread->sleep) {
      thread->sleep -= elapsed;
      _PR_SLEEPQMAX(thread->cpu) -= elapsed;
      _PR_SLEEPQ_UNLOCK(cpu);
      break;
    }
    _PR_SLEEPQ_UNLOCK(cpu);

    PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));

    _PR_THREAD_LOCK(thread);

    if (thread->cpu != cpu) {
      /*
      ** The thread was switched to another CPU
      ** between the time we unlocked the sleep
      ** queue and the time we acquired the thread
      ** lock, so it is none of our business now.
      */
      _PR_THREAD_UNLOCK(thread);
      continue;
    }

    /*
    ** Consume this sleeper's amount of elapsed time from the elapsed
    ** time value. The next remaining piece of elapsed time will be
    ** available for the next sleeping thread's timer.
    */
    _PR_SLEEPQ_LOCK(cpu);
    PR_ASSERT(!(thread->flags & _PR_ON_PAUSEQ));
    if (thread->flags & _PR_ON_SLEEPQ) {
      _PR_DEL_SLEEPQ(thread, PR_FALSE);
      elapsed -= thread->sleep;
      _PR_SLEEPQ_UNLOCK(cpu);
    } else {
      /* Thread was already handled; Go get another one */
      _PR_SLEEPQ_UNLOCK(cpu);
      _PR_THREAD_UNLOCK(thread);
      continue;
    }

    /* Notify the thread waiting on the condition variable */
    if (thread->flags & _PR_SUSPENDING) {
      PR_ASSERT((thread->state == _PR_IO_WAIT) ||
                (thread->state == _PR_COND_WAIT));
      /*
      ** Thread is suspended and its condition timeout
      ** expired. Transfer thread from sleepQ to suspendQ.
      */
      thread->wait.cvar = NULL;
      _PR_MISCQ_LOCK(cpu);
      thread->state = _PR_SUSPENDED;
      _PR_ADD_SUSPENDQ(thread, cpu);
      _PR_MISCQ_UNLOCK(cpu);
    } else {
      if (thread->wait.cvar) {
        PRThreadPriority pri;

        /* Do work very similar to what _PR_NotifyThread does */
        PR_ASSERT(!_PR_IS_NATIVE_THREAD(thread));

        /* Make thread runnable */
        pri = thread->priority;
        thread->state = _PR_RUNNABLE;
        PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));

        PR_ASSERT(thread->cpu == cpu);
        _PR_RUNQ_LOCK(cpu);
        _PR_ADD_RUNQ(thread, cpu, pri);
        _PR_RUNQ_UNLOCK(cpu);

        if (pri > me->priority) {
          _PR_SET_RESCHED_FLAG();
        }

        thread->wait.cvar = NULL;

        _PR_MD_WAKEUP_WAITER(thread);

      } else if (thread->io_pending == PR_TRUE) {
        /* Need to put IO sleeper back on runq */
        int pri = thread->priority;

        thread->io_suspended = PR_TRUE;
#ifdef WINNT
        /*
         * For NT, record the cpu on which I/O was issued
         * I/O cancellation is done on the same cpu
         */
        thread->md.thr_bound_cpu = cpu;
#endif

        PR_ASSERT(!(thread->flags & _PR_IDLE_THREAD));
        PR_ASSERT(thread->cpu == cpu);
        thread->state = _PR_RUNNABLE;
        _PR_RUNQ_LOCK(cpu);
        _PR_ADD_RUNQ(thread, cpu, pri);
        _PR_RUNQ_UNLOCK(cpu);
      }
    }
    _PR_THREAD_UNLOCK(thread);
  }
}

/************************************************************************/

/*
** Create a new condition variable.
**  "lock" is the lock to use with the condition variable.
**
** Condition variables are synchronization objects that threads can use
** to wait for some condition to occur.
**
** This may fail if memory is tight or if some operating system resource
** is low.
*/
PR_IMPLEMENT(PRCondVar*) PR_NewCondVar(PRLock* lock) {
  PRCondVar* cvar;

  cvar = PR_NEWZAP(PRCondVar);
  if (cvar) {
    if (_PR_InitCondVar(cvar, lock) != PR_SUCCESS) {
      PR_DELETE(cvar);
      return NULL;
    }
  } else {
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
  }
  return cvar;
}

PRStatus _PR_InitCondVar(PRCondVar* cvar, PRLock* lock) {
  PR_ASSERT(lock != NULL);

#ifdef _PR_GLOBAL_THREADS_ONLY
  if (_PR_MD_NEW_CV(&cvar->md)) {
    PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
    return PR_FAILURE;
  }
#endif
  if (_PR_MD_NEW_LOCK(&(cvar->ilock)) != PR_SUCCESS) {
    PR_SetError(PR_INSUFFICIENT_RESOURCES_ERROR, 0);
    return PR_FAILURE;
  }
  cvar->lock = lock;
  PR_INIT_CLIST(&cvar->condQ);
  return PR_SUCCESS;
}

/*
** Destroy a condition variable. There must be no thread
** waiting on the condvar. The caller is responsible for guaranteeing
** that the condvar is no longer in use.
**
*/
PR_IMPLEMENT(void) PR_DestroyCondVar(PRCondVar* cvar) {
  _PR_FreeCondVar(cvar);
  PR_DELETE(cvar);
}

void _PR_FreeCondVar(PRCondVar* cvar) {
  PR_ASSERT(cvar->condQ.next == &cvar->condQ);

#ifdef _PR_GLOBAL_THREADS_ONLY
  _PR_MD_FREE_CV(&cvar->md);
#endif
  _PR_MD_FREE_LOCK(&(cvar->ilock));
}

/*
** Wait for a notify on the condition variable. Sleep for "tiemout" amount
** of ticks (if "timeout" is zero then the sleep is indefinite). While
** the thread is waiting it unlocks lock. When the wait has
** finished the thread regains control of the condition variable after
** locking the associated lock.
**
** The thread waiting on the condvar will be resumed when the condvar is
** notified (assuming the thread is the next in line to receive the
** notify) or when the timeout elapses.
**
** Returns PR_FAILURE if the caller has not locked the lock associated
** with the condition variable or the thread has been interrupted.
*/
extern PRThread* suspendAllThread;
PR_IMPLEMENT(PRStatus) PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout) {
  PRThread* me = _PR_MD_CURRENT_THREAD();

  PR_ASSERT(cvar->lock->owner == me);
  PR_ASSERT(me != suspendAllThread);
  if (cvar->lock->owner != me) {
    return PR_FAILURE;
  }

  return _PR_WaitCondVar(me, cvar, cvar->lock, timeout);
}

/*
** Notify the highest priority thread waiting on the condition
** variable. If a thread is waiting on the condition variable (using
** PR_Wait) then it is awakened and begins waiting on the lock.
*/
PR_IMPLEMENT(PRStatus) PR_NotifyCondVar(PRCondVar* cvar) {
  PRThread* me = _PR_MD_CURRENT_THREAD();

  PR_ASSERT(cvar->lock->owner == me);
  PR_ASSERT(me != suspendAllThread);
  if (cvar->lock->owner != me) {
    return PR_FAILURE;
  }

  _PR_NotifyCondVar(cvar, me);
  return PR_SUCCESS;
}

/*
** Notify all of the threads waiting on the condition variable. All of
** threads are notified in turn. The highest priority thread will
** probably acquire the lock.
*/
PR_IMPLEMENT(PRStatus) PR_NotifyAllCondVar(PRCondVar* cvar) {
  PRCList* q;
  PRIntn is;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  PR_ASSERT(cvar->lock->owner == me);
  if (cvar->lock->owner != me) {
    return PR_FAILURE;
  }

#ifdef _PR_GLOBAL_THREADS_ONLY
  _PR_MD_NOTIFYALL_CV(&cvar->md, &cvar->lock->ilock);
  return PR_SUCCESS;
#else  /* _PR_GLOBAL_THREADS_ONLY */
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  _PR_CVAR_LOCK(cvar);
  q = cvar->condQ.next;
  while (q != &cvar->condQ) {
    PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("PR_NotifyAll: cvar=%p", cvar));
    _PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me);
    q = q->next;
  }
  _PR_CVAR_UNLOCK(cvar);
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }

  return PR_SUCCESS;
#endif /* _PR_GLOBAL_THREADS_ONLY */
}

/*********************************************************************/
/*********************************************************************/
/********************ROUTINES FOR DCE EMULATION***********************/
/*********************************************************************/
/*********************************************************************/
#include "prpdce.h"

PR_IMPLEMENT(PRCondVar*) PRP_NewNakedCondVar(void) {
  PRCondVar* cvar = PR_NEWZAP(PRCondVar);
  if (NULL != cvar) {
    if (_PR_MD_NEW_LOCK(&(cvar->ilock)) == PR_FAILURE) {
      PR_DELETE(cvar);
      cvar = NULL;
    } else {
      PR_INIT_CLIST(&cvar->condQ);
      cvar->lock = _PR_NAKED_CV_LOCK;
    }
  }
  return cvar;
}

PR_IMPLEMENT(void) PRP_DestroyNakedCondVar(PRCondVar* cvar) {
  PR_ASSERT(cvar->condQ.next == &cvar->condQ);
  PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);

  _PR_MD_FREE_LOCK(&(cvar->ilock));

  PR_DELETE(cvar);
}

PR_IMPLEMENT(PRStatus)
PRP_NakedWait(PRCondVar* cvar, PRLock* lock, PRIntervalTime timeout) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);
  return _PR_WaitCondVar(me, cvar, lock, timeout);
} /* PRP_NakedWait */

PR_IMPLEMENT(PRStatus) PRP_NakedNotify(PRCondVar* cvar) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);

  _PR_NotifyCondVar(cvar, me);

  return PR_SUCCESS;
} /* PRP_NakedNotify */

PR_IMPLEMENT(PRStatus) PRP_NakedBroadcast(PRCondVar* cvar) {
  PRCList* q;
  PRIntn is;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PR_ASSERT(_PR_NAKED_CV_LOCK == cvar->lock);

  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSOFF(is);
  }
  _PR_MD_LOCK(&(cvar->ilock));
  q = cvar->condQ.next;
  while (q != &cvar->condQ) {
    PR_LOG(_pr_cvar_lm, PR_LOG_MIN, ("PR_NotifyAll: cvar=%p", cvar));
    _PR_NotifyThread(_PR_THREAD_CONDQ_PTR(q), me);
    q = q->next;
  }
  _PR_MD_UNLOCK(&(cvar->ilock));
  if (!_PR_IS_NATIVE_THREAD(me)) {
    _PR_INTSON(is);
  }

  return PR_SUCCESS;
} /* PRP_NakedBroadcast */
