/*
* JBoss, Home of Professional Open Source
* Copyright 2009-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;

import java.util.List;
import java.util.LinkedList;

/**
 * class used by default helper to implement join dependencies between threads
 */
public class Joiner
{

    /**
     * status values returned from child add method
     */
    public enum Status {
        /**
         * a DUPLICATE status is returned when a child fails to add itself to the join list because it is already present
         */
        DUPLICATE,
        /**
         * an EXCESS status is returned when a child fails to add itself to a join list because it already contains the
         * expected number of children
         */
        EXCESS,
        /**
         * an ADDED status is returned when a child successfully adds itself to the join list but without reaching
         * the expected number of children
         */
        ADDED,
        /**
         * a FILLED status is returned when a child successfully adds itself to the join list reaching the expected
         * number of children but there is no parent thread waiting for the children
         */
        FILLED,
        /**
         * a DONE  status is returned when a child successfully adds itself to the join list reaching the expected
         * number of children and there is a parent thread waiting for the children
         */
        DONE
    }

    private List<Thread> children;
    private int max;
    private Thread parent;

    public Joiner(int max)
    {
        this.max = max;
        this.children = new LinkedList<Thread>();
        this.parent =  null;
    }

    public int getMax()
    {
        return max;
    }

    public synchronized Status addChild(Thread thread)
    {
        if (children.contains(thread)) {
            return Status.DUPLICATE;
        }

        int size = children.size();

        if (size == max) {
            return Status.EXCESS;
        }

        children.add(thread);
        size++;

        if (size == max) {
            if (parent ==  null) {
                return Status.FILLED;
            } else {
                notifyAll();
                return Status.DONE;
            }
        }
        return Status.ADDED;
    }

    public boolean joinChildren(Thread thread, long millis)
    {
        long target_time=System.currentTimeMillis() + millis;
        synchronized (this) {
            if (parent != null) {
                return false;
            }
            parent = thread;
            while (children.size() < max) {
                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 joinWait");
                        }
                    }
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
        }

        // since we are the parent and the waiting is over we don't need to stay synchronized
        for (int i = 0; i < max;) {
            Thread child = children.get(i);
            try {
                if (millis <= 0) {
                    child.join();
                } else {
                    long wait_time=target_time - System.currentTimeMillis();
                    if(wait_time > 0) {
                        child.join(wait_time);
                    } else {
                        throw new ExecuteException("timeout occurred in joinWait");
                    }
                }
            } catch (InterruptedException e) {
                // try again
                break;
            }
            i++;
        }
        return true;
    }
}
