/*******************************************************************************
 * Copyright (c) 2003, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM - Initial API and implementation
 *     Jeremiah Lott (jeremiah.lott@timesys.com) - fix for deadlock bug 76378
 *     
 *******************************************************************************/
package org.eclipse.ui.internal;

import org.eclipse.core.runtime.jobs.LockListener;
import org.eclipse.swt.widgets.Display;

/**
 * The UI lock listener is used to prevent the UI thread from deadlocking on
 * a lock when the thread owning the lock is attempting to syncExec.
 */
public class UILockListener extends LockListener {

    /**
     * The Queue is the construct that keeps track of Semaphores.
     */
    public class Queue {
        private static final int BASE_SIZE = 8;

        protected Semaphore[] elements = new Semaphore[BASE_SIZE];

        protected int head = 0;

        protected int tail = 0;

        /**
         * Add the semaphore to the queue.
         * @param element
         */
        public synchronized void add(Semaphore element) {
            int newTail = increment(tail);
            if (newTail == head) {
                grow();
                newTail = tail + 1;
            }
            elements[tail] = element;
            tail = newTail;
        }

        private void grow() {
            int newSize = elements.length * 2;
            Semaphore[] newElements = new Semaphore[newSize];
            if (tail >= head)
                System.arraycopy(elements, head, newElements, head, size());
            else {
                int newHead = newSize - (elements.length - head);
                System.arraycopy(elements, 0, newElements, 0, tail + 1);
                System.arraycopy(elements, head, newElements, newHead,
                        (newSize - newHead));
                head = newHead;
            }
            elements = newElements;
        }

        private int increment(int index) {
            return (index == (elements.length - 1)) ? 0 : index + 1;
        }

        /**
         * Remove the next semaphore to be woken up.
         * @return
         */
        public synchronized Semaphore remove() {
            if (tail == head)
                return null;
            Semaphore result = elements[head];
            elements[head] = null;
            head = increment(head);
            //reset the queue if it is empty and it has grown
            if (tail == head && elements.length > BASE_SIZE) {
                elements = new Semaphore[BASE_SIZE];
                tail = head = 0;
            }
            return result;
        }

        private int size() {
            return tail > head ? (tail - head)
                    : ((elements.length - head) + tail);
        }
    }

    protected Display display;

    protected final Queue pendingWork = new Queue();

    protected Semaphore currentWork = null;

    protected Thread ui;

    /**
     * Create a new instance of the receiver.
     * @param display
     */
    public UILockListener(Display display) {
        this.display = display;
    }

    public void aboutToRelease() {
        if (isUI())
            ui = null;
    }

    public boolean aboutToWait(Thread lockOwner) {
        if (isUI()) {
            // If a syncExec was executed from the current operation, it
            // has already acquired the lock. So, just return true.
            if (currentWork != null
                    && currentWork.getOperationThread() == lockOwner)
                return true;
            ui = Thread.currentThread();
            try {
                doPendingWork();
            } finally {
                //UI field may be nulled if there is a nested wait during execution
                //of pending work, so make sure it is assigned before we start waiting
                ui = Thread.currentThread();
            }
        }
        return false;
    }

    void addPendingWork(Semaphore work) {
        pendingWork.add(work);
    }

    /**
     * Should always be called from the UI thread.
     */
    void doPendingWork() {
        Semaphore work;
        while ((work = pendingWork.remove()) != null) {
        	//remember the old current work before replacing, to handle
        	//the nested waiting case (bug 76378)
        	Semaphore oldWork = currentWork;
            try {
                currentWork = work;
                Runnable runnable = work.getRunnable();
                if (runnable != null)
                    runnable.run();

            } finally {
                currentWork = oldWork;
                work.release();
            }
        }
    }

    void interruptUI() {
        display.getThread().interrupt();
    }

    boolean isLockOwner() {
        return isLockOwnerThread();
    }

    boolean isUI() {
        return (!display.isDisposed())
                && (display.getThread() == Thread.currentThread());
    }

    boolean isUIWaiting() {
        return (ui != null) && (Thread.currentThread() != ui);
    }
}
