/*
* JBoss, Home of Professional Open Source
* Copyright 2008-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.synchronization;

import org.jboss.byteman.rule.exception.ExecuteException;

/**
 * class used to manage rule rendezvous operations
 */
public class Rendezvous
{
    public Rendezvous(int expected)
    {
        this(expected, false);
    }

    public Rendezvous(int expected, boolean rejoinable)
    {
        this.expected = expected;
        this.rejoinable = rejoinable;
        this.needsRemove = false;
        this.isDeleted = false;
        this.counter = new Counter();
    }

    /**
     * enter this rendezvous. n.b. this must be called synchronized on the rendezvous object
     * in question
     * @param millis how long to wait (wait forever if 0)
     * @return the index in arrival order from 0 to expected of the calling thread or -1 if
     * either the rendezvous has completed and is not restartable or the rendezvous has been deleted
     */
    public int rendezvous(long millis)
    {
        Counter currentCounter = counter;

        // too late the rendezvous has expired

        if (isDeleted || (currentCounter.arrived == expected)) {
            return -1;
        }

        // n.b. getting here implies !currentCounter.isPoisoned

        int index = currentCounter.arrived++;

        if (currentCounter.arrived < expected) {
            long target_time=System.currentTimeMillis() + millis;
            // make sure we don't return before the rendezvous has actually happened
            while (currentCounter.arrived < expected) {
                try {
                    if(millis <= 0) {
                        this.wait();
                    } else {
                        long wait_time=target_time - System.currentTimeMillis();
                        if(wait_time > 0) {
                            this.wait(wait_time);
                        } else {
                            throw new ExecuteException("timeout occurred in rendezvous");
                        }
                    }
                } catch (InterruptedException e) {
                    // do nothing
                }

                // isPoisoned may have changed because a delete happened while we were waiting

                if (currentCounter.isPoisoned) {
                    return -1;
                }
            }
        } else {
            if (rejoinable) {
                // create a new counter for the next rendezvous -- this allows the current threads
                // to complete without counting them back out
                counter = new Counter();
            } else {
                // tag the rendezvous to indicate that it has been deleted and needs ot be removed. the
                // first thread emerging from a call to rendezvous must make sure it gets removed from
                // the rendezvous map.
                isDeleted = true;
                needsRemove = true;
            }
            this.notifyAll();
        }

        return index;
    }



    /**
     * delete this rendezvous causing any waiting threads to return -1 form the rendezvous call. n.b. this
     * must be called synchronized on the rendezvous object in question
     * @return false if a delete has already been requested otherwise true
     */
    public boolean delete()
    {
        if (isDeleted) {
            return false;
        }
        isDeleted = true;
        needsRemove = true;

        // if any threads arrived then make sure they are *all* poisoned
        if (counter.arrived > 0 && counter.arrived < expected) {
            counter.isPoisoned = true;
            this.notifyAll();
        }

        return true;
    }

    public int getExpected() {
        return expected;
    }
    /**
     * the number of threads which are expected to arrive at this rendezvous
     */
    private int expected;

    /**
     * the current counter for this rendezvous
     */
    private Counter counter;
    /**
     * true if this rendezvous can be repeatedly joined, false it it is a one-off meeting
     */
    private boolean rejoinable;

    /**
     * true if a rendezvous was deleted while a rendezbvous was in progress but had not completed
     */

    private boolean isDeleted;

    /**
     * true if a non-restartable rendezvous has completed and has not been removed from the rendezvous map
     */
    private boolean needsRemove;

    /**
     * retrieve the number of threads waiting at the rendezvous or -1 if the rendezvous has
     * been deleted
     * @return  number of threads waiting or -1
     */
    public int getArrived() {
        if (isDeleted) {
            return -1;
        }
        return counter.arrived;
    }

    /**
     * check if the rendezvous has completed but has not yet been removed
     * @return the above
     */
    public boolean needsRemove() {
        return needsRemove;
    }

    /**
     * mark a completed rendezvous to indicate that it has been removed
     */
    public void setRemoved() {
        needsRemove = false;
    }

    /**
     * class encapsulating state for a specific rendezvous
     */

    public class Counter
    {
        /**
         *  count of the number of threads actually arrived at this rendezvous
         */
        public int arrived;
        /**
         *  true if this
         */
        public boolean isPoisoned;

        public Counter()
        {
            arrived = 0;
            isPoisoned = false;
        }
    }
}
