/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program 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
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.midp.lcdui;

import com.sun.midp.events.EventTypes;
import com.sun.midp.events.Event;
import com.sun.midp.events.EventListener;
import com.sun.midp.events.EventQueue;

import com.sun.midp.security.Permissions;
import com.sun.midp.security.SecurityToken;

import javax.microedition.lcdui.Display;

/**
 * This class performs all of the repaint event handling.
 */
public class RepaintEventProducer implements EventListener {

    /** Cached reference to the MIDP event queue. */
    private EventQueue eventQueue;

    /** Repaint event free for reuse. */
    private RepaintEvent pooledEvent1;
    /** Repaint event free for reuse. */
    private RepaintEvent pooledEvent2;
    /** Repaint event free for reuse. */
    private RepaintEvent pooledEvent3;

    /**
     * Repaint event in the queue or waiting to be moved to the processed
     * slot. Events are rotated to different slots in
     * <code>preprocessRepaints</code>.
     */
    private RepaintEvent queuedEvent;

    /**
     * Repaint event being processed (queue has event also) or
     * has been processed (no event in the queue).
     */
    private RepaintEvent eventInProcess;

    /**
     * The constructor LCDUI repaint events handler.
     *
     * @param theEventQueue the event queue
     */
    public RepaintEventProducer(EventQueue theEventQueue) {
        
        eventQueue = theEventQueue;

        pooledEvent1 = RepaintEvent.createRepaintEvent(null, 0, 0, 0, 0, null);
        pooledEvent2 = RepaintEvent.createRepaintEvent(null, 0, 0, 0, 0, null);
        pooledEvent3 = RepaintEvent.createRepaintEvent(null, 0, 0, 0, 0, null);
        
        eventQueue.registerEventListener(EventTypes.REPAINT_EVENT, this);
    }

    /**
     * Called to schedule a repaint of the current Displayable
     * as soon as possible.
     *
     * @param d     The Display
     * @param x     The x coordinate of the origin of the repaint rectangle
     * @param y     The y coordinate of the origin of the repaint rectangle
     * @param w     The width of the repaint rectangle
     * @param h     The height of the repaint rectangle
     * @param target An optional target Object, which may have been the
     *               original requestor for the repaint
     */
    public void scheduleRepaint(DisplayEventConsumer d,
                                int x, int y, int w, int h, Object target) {
        RepaintEvent freeEvent;

        synchronized (this) {
            freeEvent = pooledEvent1;

            freeEvent.setRepaintFields(d, x, y, w, h, target);

            if (queuedEvent == null) {
                /*
                 * Since the queue does not lock posting during event
                 * processing, we need to rotate 3 repaint events for reuse.
                 *
                 * 1 for the queued event, 1 for the event begin processed
                 * and 1 for temporary use when merging a new event with
                 * a queued event.
                 *
                 * Event pooling is done because game can generate upto 15
                 * repaints a second and cause a lot garbage collection if
                 * we created new repaint every time.
                 */
                pooledEvent1 = pooledEvent2;
                pooledEvent2 = pooledEvent3;
                pooledEvent3 = freeEvent;

                queuedEvent = freeEvent;

                eventQueue.post(queuedEvent);
            } else if (queuedEvent.display != d) {
                /*
                 * A new display has come to the foreground so don't
                 * bother paint the display in the queued event.
                 *
                 * Overwrite this fields in queue event with new values.
                 */
                 queuedEvent.setRepaintFields(d, x, y, w, h, target);
            } else {
                /*
                 * When there is a pending repaint
                 * union the dirty regions into one event
                 */
                if (queuedEvent.paintX1 > freeEvent.paintX1) {
                    queuedEvent.paintX1 = freeEvent.paintX1;
                }

                if (queuedEvent.paintY1 > freeEvent.paintY1) {
                    queuedEvent.paintY1 = freeEvent.paintY1;
                }

                if (queuedEvent.paintX2 < freeEvent.paintX2) {
                    queuedEvent.paintX2 = freeEvent.paintX2;
                }
                
                if (queuedEvent.paintY2 < freeEvent.paintY2) {
                    queuedEvent.paintY2 = freeEvent.paintY2;
                }

                queuedEvent.paintTarget = null;
            }
        }
    }

    /**
     * Preprocess an event that is being posted to the event queue.
     * 
     * @param event event being posted
     *
     * @param waitingEvent previous event of this type waiting in the
     *     queue to be processed
     * 
     * @return true to allow the post to continue, false to not post the
     *     event to the queue
     */
    public boolean preprocess(Event event, Event waitingEvent) {
        /*
         * Because of the needs of serviceRepaints the preprocessing
         * is done in scheduleRepaint.
         */
        return true;
    }

    /**
     * Process an event.
     *
     * @param genericEvent event to process
     */
    public void process(Event genericEvent) {
        RepaintEvent event = (RepaintEvent)genericEvent;

        synchronized (this) {
            queuedEvent = null;
            eventInProcess = event;
        }

        /*
         * Target DisplayEventConsumer is obtained directly from event field.
         * Assumed that target consumer is not null.
         */
        event.display.handleRepaintEvent(
                event.paintX1, event.paintY1, 
                event.paintX2, event.paintY2, 
                event.paintTarget);

        synchronized (this) {
            /* Change the ID here to signal waitForRepaint. */
            eventInProcess.perUseID++;
            
            eventInProcess = null;

            // ServiceRepaints may be blocking.
            notifyAll();
        }
    }

    /**
     * Called to block the calling MIDlet until the the pending repaint
     * operation is performed.
     * <p>
     * Does not return until the pending repaint (if any)
     * has been processed.</p>
     *
     */
    public void serviceRepaints() {
        if (EventQueue.isDispatchThread()) {
            Event event;

            if (eventInProcess != null) {
                /*
                 * We are in the midst of a calling the application's
                 * paint method, avoid recursion, simply return
                 */
                return;
            }

            /*
             * Since we are in the dispatch thread, at this point
             * there can only one repaint in the queue so remove it
             * from the queue and process it.
             */
            event = eventQueue.remove(EventTypes.REPAINT_EVENT);

            /* Do not hold a lock when calling out to the application */
            if (event != null) {
                process(event);
            }
        } else {
            /*
             * We only want paint events to be process in the dispatch
             * to avoid deadlocks. So wait for the repaint event to occur.
             */
            waitForCurrentRepaintEvents();
        }
    }

    /**
     * Wait for the event queue to process all of the events currently
     * in the queue but not any new ones after the start of this
     * method.
     */
    private void waitForCurrentRepaintEvents() {
        RepaintEvent eventToWaitFor = null;
        int currentEventUseID;

        synchronized (this) {
            if (queuedEvent != null) {
                eventToWaitFor = queuedEvent;
            } else if (eventInProcess != null) {
                eventToWaitFor = eventInProcess;
            } else {
                /* Nothing to wait for, done. */
                return;
            }

            currentEventUseID = eventToWaitFor.perUseID;
            while (eventToWaitFor.perUseID == currentEventUseID) {
                try {
                    wait();
                } catch (InterruptedException ie) {
                    /* The application wants this thread to unblock */
                    break;
                }
            }
                
        }
    }    
}
