/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package javax.microedition.lcdui;

import org.eclipse.swt.widgets.Widget;


/*
 * Event dispatcher singleton class. Takes care of dispatching the UI callbacks
 * to the MIDlet in a dedicated dispatcher thread. Owns and encapsulates an
 * event queue of callback events.
 */
final class EventDispatcher
{

    private static EventDispatcher singleton;
    private static EventQueue eventQueue;
    private static Thread dispatcherThread;
    private static boolean terminated;
    private static Object wakeLock;
    private static Object callbackLock;
    private static boolean pendingWake;
    private static Runnable terminatedNotification;
    private final static String logName = "LCDUI event dispatcher: ";

    /*
     * LCDUI-internal event class for callback events.
     */
    static final class LCDUIEvent
    {
        /*
         * High bits of the event event type identifier are used to group the
         * events that are handled the same way in the Event Dispatcher.
         */
        static final int CANVASBIT = 0x80000000;
        static final int CUSTOMITEMBIT = 0x40000000;
        static final int DISPLAYABLEBIT = 0x20000000;
        static final int EVENTTYPEMASK = CANVASBIT|CUSTOMITEMBIT|DISPLAYABLEBIT;

        /*
         * Event type identifiers.
         */
        static final int CANVAS_HIDENOTIFY = 1|CANVASBIT;
        static final int CANVAS_KEYPRESSED = 2|CANVASBIT;
        static final int CANVAS_KEYREPEATED = 3|CANVASBIT;
        static final int CANVAS_KEYRELEASED = 4|CANVASBIT;
        static final int CANVAS_PAINT_NATIVE_REQUEST = 5|CANVASBIT;
        static final int CANVAS_PAINT_MIDLET_REQUEST = 6|CANVASBIT;
        static final int CANVAS_POINTERDRAGGED = 7|CANVASBIT;
        static final int CANVAS_POINTERPRESSED = 8|CANVASBIT;
        static final int CANVAS_POINTERRELEASED = 9|CANVASBIT;
        static final int CANVAS_SHOWNOTIFY = 10|CANVASBIT;
        static final int DISPLAYABLE_SIZECHANGED = 11|DISPLAYABLEBIT;
        static final int DISPLAYABLE_COMMANDACTION = 12|DISPLAYABLEBIT;
        static final int CUSTOMITEM_HIDENOTIFY = 13|CUSTOMITEMBIT;
        static final int CUSTOMITEM_KEYPRESSED = 14|CUSTOMITEMBIT;
        static final int CUSTOMITEM_KEYREPEATED = 15|CUSTOMITEMBIT;
        static final int CUSTOMITEM_KEYRELEASED = 16|CUSTOMITEMBIT;
        static final int CUSTOMITEM_PAINT_NATIVE_REQUEST = 17|CUSTOMITEMBIT;
        static final int CUSTOMITEM_PAINT_MIDLET_REQUEST = 18|CUSTOMITEMBIT;
        static final int CUSTOMITEM_POINTERDRAGGED = 19|CUSTOMITEMBIT;
        static final int CUSTOMITEM_POINTERPRESSED = 20|CUSTOMITEMBIT;
        static final int CUSTOMITEM_POINTERRELEASED = 21|CUSTOMITEMBIT;
        static final int CUSTOMITEM_SHOWNOTIFY = 22|CUSTOMITEMBIT;
        static final int CUSTOMITEM_SIZECHANGED = 23|CUSTOMITEMBIT;
        static final int CUSTOMITEM_TRAVERSE = 24|CUSTOMITEMBIT;
        static final int CUSTOMITEM_TRAVERSEOUT = 25|CUSTOMITEMBIT;
        static final int ITEM_COMMANDACTION = 26;
        static final int ITEMSTATELISTENER_ITEMSTATECHANGED = 27;
        static final int RUNNABLE_CALLSERIALLY = 28;

        /*
         * Event parameters.
         */
        int type;
        int x;
        int y;
        int width;
        int height;
        int keyCode;
        Runnable runnable;
        Command command;
        CommandListener commandListener;
        ItemCommandListener itemCommandListener;
        Item item;

        /*
         * Handler object. The object that the LCDUIEvent will be passed to
         * when the event is dispatched. Based on the event type it's known
         * what type of an object it must be. The handler object will
         * implement how to do the actual callback.
         */
        Object handlerObject;

        /*
         * The associated eSWT widget. Used to store the eSWT widget which
         * the original eSWT event was for. E.g. in Form the CustomItem's
         * widget may change while the event is in the queue.
         */
        Widget widget;

        /*
         * Used by EventQueue.
         */
        LCDUIEvent next;

        private LCDUIEvent()
        {
        }
    }

    /*
     * The event loop executed in a dedicated dispatcher thread. Events are
     * popped from the queue and dispatched oldest first until there are no
     * more events. Then the thread sleeps until new events are posted or
     * termination is requested. Any exceptions thrown from the event handlers
     * are let through to the runtime which should lead into termination of
     * all threads in the process.
     */
    private static class EventLoop implements Runnable
    {
        private boolean noException;
        public void run()
        {
            try
            {
                Logger.info(logName + "Dispatcher thread started");
                while(true)
                {
                    LCDUIEvent event;
                    do
                    {
                        synchronized(wakeLock)
                        {
                            if(terminated)
                            {
                                cleanup();
                                noException = true;
                                return;
                            }
                            event = eventQueue.pop();
                            pendingWake = false;
                        }

                        if(event != null)
                        {
                            synchronized(callbackLock)
                            {
                                // No callbacks are sent if already terminating
                                if(!terminated)
                                {
                                    handleEvent(event);
                                }
                            }
                        }
                    }
                    while(event != null);

                    // No more events
                    waitForWake();
                }
            }
            finally
            {
                if(noException)
                {
                    Logger.info(logName + "Dispatcher thread exiting normally");
                }
                else
                {
                    Logger.error(logName + "Dispatcher thread exiting abnormally");
                }
                if(terminatedNotification != null)
                {
                    terminatedNotification.run();
                    terminatedNotification = null;
                }
            }
        }
    }

    private EventDispatcher()
    {
        wakeLock = new Object();
        callbackLock = new Object();
        eventQueue = new EventQueue();
        dispatcherThread = new Thread(new EventLoop(), this.toString());
        dispatcherThread.start();
    }

    /*
     * Cleanup that is done when closing down in a controlled way.
     */
    private static void cleanup()
    {
        eventQueue = null;
        dispatcherThread = null;
    }

    /*
     * Synchronized to the EventDispatcher class to synchronize with multiple
     * clients requesting the instance concurrently.
     */
    static synchronized EventDispatcher instance()
    {
        if(singleton == null)
        {
            singleton = new EventDispatcher();
        }
        return singleton;
    }

    /*
     * LCDUIEvent factory.
     */
    LCDUIEvent newEvent(int type, Object handlerObject)
    {
        LCDUIEvent event = new LCDUIEvent();
        event.type = type;
        event.handlerObject = handlerObject;
        return event;
    }

    /*
     * UI thread and the application threads call to add an event to the queue.
     * Synchronized to the EventDispatcher instance to synchronize with queries about
     * the queue content.
     */
    synchronized void postEvent(LCDUIEvent event)
    {
        // New events ignored after terminate() has been called.
        if(terminated)
        {
            return;
        }
        eventQueue.push(event);
        wake();
    }

    /*
     * Asynchronously terminates the event dispatcher. If it's currently
     * executing a callback it will finish it but not execute any callbacks
     * after it. Events possibly remaining in the queue are discarded. The
     * method is synchronized to the EventDispatcher instance to synchronize
     * with queue content manipulation and query operations.
     *
     * @param runnable Called when dispatcher is out of the final callback and
     * is going to die.
     */
    synchronized void terminate(Runnable runnable)
    {
        synchronized(wakeLock)
        {
            wake();
            terminatedNotification = runnable;
            terminated = true;
        }
    }

    /*
     * Canvas.serviceRepaints support is implemented in EventDispatcher so that
     * it's possible to synchronize properly with termination and other
     * callbacks without exposing the event dispatcher's internal
     * implementation details to other classes. This method must be called
     * directly in the application thread calling Canvas.serviceRepaints(). It
     * can be either the dispatcher thread or any application thread. Never
     * the UI thread.
     */
    void serviceRepaints(Canvas canvas)
    {
        // Synchronizing to callbackLock guarantees that dispatcher thread is
        // not in a callback and is not going to make a callback until the lock
        // is freed from here. If this is the dispatcher thread then it already
        // holds the lock.
        synchronized(callbackLock)
        {
            // Synchronized to the EventDispatcher instance to synchronize with
            // terminate() calls. Lock is freed before the callback so that
            // it's possible to complete a terminate() call during the
            // callback. Event loop is going to check after acquiring the
            // callbackLock if terminate() was called during this callback.
            synchronized(this)
            {
                if(terminated)
                {
                    return;
                }
            }
            // Canvas.paint() is called back directly in the thread that called
            // serviceRepaints(). Event is not really needed but is created so
            // that the same APIs can be used.
            LCDUIEvent event = newEvent(LCDUIEvent.CANVAS_PAINT_MIDLET_REQUEST, canvas);
            event.widget = canvas.getContentComp();
            canvas.doCallback(event);
        }
    }

    private static void wake()
    {
        synchronized(wakeLock)
        {
            wakeLock.notify();
            pendingWake = true;
        }
    }

    private static void waitForWake()
    {
        try
        {
            synchronized(wakeLock)
            {
                // If there has been a call to wake() then one more iteration
                // must be executed before entering wait().
                if(!pendingWake)
                {
                    wakeLock.wait();
                }
            }
        }
        catch(InterruptedException interruptedException)
        {
        }
    }

    private static void handleEvent(LCDUIEvent event)
    {
        switch(event.type & LCDUIEvent.EVENTTYPEMASK)
        {
        case LCDUIEvent.CANVASBIT:
            handleCanvasEvent(event);
            break;
        case LCDUIEvent.CUSTOMITEMBIT:
            handleCustomItemEvent(event);
            break;
        case LCDUIEvent.DISPLAYABLEBIT:
            handleDisplayableEvent(event);
            break;
        default:
            handleOtherEvent(event);
            break;
        }
        // When returning from here all the references to the event have been
        // lost and the objects referenced by the event can be gc'd. No need
        // to set the fields to null.
    }

    private static void handleCanvasEvent(LCDUIEvent event)
    {
        Canvas canvas = (Canvas)event.handlerObject;
        canvas.doCallback(event);
    }

    private static void handleCustomItemEvent(LCDUIEvent event)
    {
        Form form = (Form)event.handlerObject;
        form.doCallback(event);
    }

    private static void handleDisplayableEvent(LCDUIEvent event)
    {
        Displayable displayable = (Displayable)event.handlerObject;
        displayable.doCallback(event);
    }

    private static void handleOtherEvent(LCDUIEvent event)
    {
        switch(event.type)
        {
        case LCDUIEvent.RUNNABLE_CALLSERIALLY:
            handleCallSerially(event);
            break;
        case LCDUIEvent.ITEM_COMMANDACTION:
            handleItemCommandListenerCommandAction(event);
            break;
        default:
            Logger.error(logName + "Unknown event type: " + event.type);
            break;
        }
    }

    private static void handleCallSerially(LCDUIEvent event)
    {
        Display display = (Display)event.handlerObject;
        display.doCallback(event);
    }

    private static void handleItemCommandListenerCommandAction(LCDUIEvent event)
    {
        Item item = (Item)event.handlerObject;
        item.doCallback(event);
    }
}
