using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace ch14_4
{
    class SynchronizedBuffer : Buffer 
    {
        // buffer shared by producer and consumer threads
        private int buffer = -1;

        // occupiedBufferCount maintains count of occupied buffers
        private int occupiedBufferCount = 0;

        // property Buffer
        public int Buffer
        {
            get
            {
                // obtain lock on this object
                Monitor.Enter(this);

                // if there is no data to read, place invoking 
                // thread in WaitSleepJoin state
                if (occupiedBufferCount == 0)
                {
                    CommonClass.output += "\n";
                    CommonClass.output += Thread.CurrentThread.Name + " trie to read. ";
                    DisplayState("Buffer empty. " +
                       Thread.CurrentThread.Name + " waits.");
                    Monitor.Wait(this); // enter WaitSleepJoin state
                } // end if

                // indicate that producer can store another value 
                // because consumer is about to retrieve a buffer value
                --occupiedBufferCount;

                DisplayState(Thread.CurrentThread.Name + " reads  " + buffer);

                // tell waiting thread (if there is one) to 
                // become ready to execute (Running state)
                Monitor.Pulse(this);

                // Get copy of buffer before releasing lock. 
                // It is possible that the producer could be
                // assigned the processor immediately after the
                // monitor is released and before the return 
                // statement executes. In this case, the producer 
                // would assign a new value to buffer before the 
                // return statement returns the value to the 
                // consumer. Thus, the consumer would receive the 
                // new value. Making a copy of buffer and 
                // returning the copy ensures that the
                // consumer receives the proper value.
                int bufferCopy = buffer;

                // release lock on this object
                Monitor.Exit(this);

                return bufferCopy;
            } // end get
            set
            {
                // acquire lock for this object
                Monitor.Enter(this);

                // if there are no empty locations, place invoking
                // thread in WaitSleepJoin state
                if (occupiedBufferCount == 1)
                {
                    Console.WriteLine(
                       Thread.CurrentThread.Name + " tries to write.");
                    DisplayState("Buffer full. " +
                       Thread.CurrentThread.Name + " waits.");
                    Monitor.Wait(this);// enter WaitSleepJoin state
                } // end if

                // set new buffer value
                buffer = value;

                // indicate consumer can retrieve another value 
                // because producer has just stored a buffer value
                ++occupiedBufferCount;

                DisplayState(Thread.CurrentThread.Name + " writes   " + buffer);

                // tell waiting thread (if there is one) to 
                // become ready to execute (Running state)
                Monitor.Pulse(this);

                // release lock on this object
                Monitor.Exit(this);
            } // end set
        } // end property Buffer

        // display current operation and buffer state
        public void DisplayState(string operation)
        {
            CommonClass.output  += "\n";
            CommonClass.output += operation.ToString() + "      " ;
            CommonClass.output += buffer.ToString() + "             ";
            CommonClass.output += occupiedBufferCount.ToString();
        } // end method DisplayState
    }
}
