//  ==============================================================
//  A condition variable is a thread synchronization primitive,
//  that allows a thread or threads to wait for some condition,
//  and some thread to change the condition and wake up the thread
//  or threads waiting for it.
//
//  The Wait(), WaitFor() and WaitUntil() member functions allow
//  a thread to wait for some condition to happen. The waiting
//  must occur inside a lock statement.
//  The NotifyOne() member function wakes up one waiting thread.
//  The NotifyAll() member function wakes up all waiting threads.
//  Notification does not need to occur inside a lock statement,
//  but that is allowed.
//
//  On some platforms a thread waiting may also be waken up
//  spuriously, although the condition has not changed, so a
//  condition variable should be associated with a predicate that
//  contains the actual state of the condition. The condition
//  variable should be waited in a while loop that tests the
//  predicate.
//  ==============================================================

using System;

namespace System.Threading
{
    public delegate bool Predicate(object arg);
    public class delegate bool PredicateMethod(object arg);

    public enum CondVarStatus : byte
    {
        timeout, no_timeout
    }

    public class ConditionVariable
    {
        [vmf=initcv]
        public extern ConditionVariable();
        
        [vmf=notifyone]
        public extern void NotifyOne();

        [vmf=notifyall]
        public extern void NotifyAll();

        [vmf=waitcv]
        public extern void Wait(object lck);

        [vmf=waitcvfor]
        public extern CondVarStatus WaitFor(object lck, Duration dur);

        public CondVarStatus WaitUntil(object lck, TimePoint tp)
        {
            Duration dur = tp - Now();
            return WaitFor(lck, dur);
        }

        public void Wait(object lck, Predicate predicate, object arg)
        {
            while (!predicate(arg))
            {
                Wait(lck);
            }
        }

        public bool WaitFor(object lck, Predicate predicate, object arg, Duration dur)
        {
            while (!predicate(arg))
            {
                if (WaitFor(lck, dur) == CondVarStatus.timeout) 
                {
                    return predicate(arg);
                }
            }
            return true;
        }

        public bool WaitUntil(object lck, Predicate predicate, object arg, TimePoint tp)
        {
            Duration dur = tp - Now();
            return WaitFor(lck, predicate, arg, dur);
        }

        public void Wait(object lck, PredicateMethod predicateMethod, object arg)
        {
            while (!predicateMethod(arg))
            {
                Wait(lck);
            }
        }

        public bool WaitFor(object lck, PredicateMethod predicateMethod, object arg, Duration dur)
        {
            while (!predicateMethod(arg))
            {
                if (WaitFor(lck, dur) == CondVarStatus.timeout) 
                {
                    return predicateMethod(arg);
                }
            }
            return true;
        }

        public bool WaitUntil(object lck, PredicateMethod predicateMethod, object arg, TimePoint tp)
        {
            Duration dur = tp - Now();
            return WaitFor(lck, predicateMethod, arg, dur);
        }

        public void Wait(object lck, ref bool condition)
        {
            bool cond = condition;
            while (!cond)
            {
                Wait(lck);
                cond = condition;
            }
        }

        public bool WaitFor(object lck, ref bool condition, Duration dur)
        {
            bool cond = condition;
            while (!cond)
            {
                if (WaitFor(lck, dur) == CondVarStatus.timeout) 
                {
                    cond = condition;
                    return cond;
                }
                cond = condition;
            }
            return true;
        }

        public bool WaitUntil(object lck, ref bool condition, TimePoint tp)
        {
            Duration dur = tp - Now();
            return WaitFor(lck, ref condition, dur);
        }

        public int Id
        {
            get { return id; }
        }

        private int id;
    }
}