/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.ode.bpel.engine;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * An N state synchronization latch useful for implementing hydration/dehydration. The
 * latch functions as follows. At any time, the latch is in one of N states and has a
 * count. Clients can "latch" and "release" the latch, which increments/decrements the 
 * count; however, when latching, a state must be specified. If the state does not match
 * the current state, the latch blocks until the count is zero. Essentially, the latch
 * can change state only when the count is zero. Every time the latch changes state an 
 * optional {@link Runnable} corresponding to the new state is executed. 
 * 
 * @author Maciej Szefler ( m s z e f l e r @ g m a i l . c o m )
 */
public class NStateLatch {
    static final Logger __log = LoggerFactory.getLogger(NStateLatch.class);

    /** Current state. */
    private int _state = -1;

    /** Current depth (i.e. number of enter() calls) */
    private int _depth = 0;

    /** Action for state transition ?-->i */
    protected Runnable _transitions[];
    
    /** Synchronization lock .*/
    private Lock _lock; 
    
    /** _depth == 0 condition. */
    private Condition _depth0;
    
    private boolean _transitioning = false;

    /**
     * Constructor, the array of {@link Runnable}s defines the number of states and the transition 
     * actions.  
     * @param transitions action to perform when entering state x. 
     */
    public NStateLatch(Runnable [] transitions) {
        _transitions = transitions;
        _lock = new ReentrantLock();
        _depth0 = _lock.newCondition();
    }
    
    public void latch(int state) {
        if (state >= _transitions.length || state < 0)
            throw new IllegalArgumentException("Invalid state.");
        
        _lock.lock();
        try {
            if (_transitioning )
                throw new IllegalStateException("Manipulating latch from transition. ");
            
            if (_state != state) {
                // wait for the depth to become 0
                while (_depth != 0) 
                    _depth0.awaitUninterruptibly();
              
                if (_state != state) {
                    if (_transitions[state] != null) 
                        try {
                            _transitioning = true;
                            _transitions[state].run();
                        } finally {
                            _transitioning = false;
                        }
                    _state = state;
                }
            }
        } finally {
            _depth ++;
            _lock.unlock();
        }
    }
    
    public void release(int state) {
        _lock.lock();
        try {
            
            if (_transitioning )
                throw new IllegalStateException("Manipulating latch from transition. ");

            if (_state != state)
                __log.error("Latch error, was releasing for state " + state + " but actually in " + _state);
            if (_depth <= 0)
                throw new IllegalStateException("Too many release() calls.");
            
            _depth --;
            
            if (_depth == 0)
                _depth0.signal();
        } finally {
            _lock.unlock();
        }
    }
    
    public int getDepth(int state) {
        return (_state == state ? _depth : 0);
    }
}
