/*
 *
 *
 * 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.main;

import com.sun.midp.lcdui.ForegroundEventProducer;

import com.sun.midp.midlet.MIDletEventProducer;

import com.sun.midp.suspend.SuspendDependency;

import java.util.Timer;
import java.util.TimerTask;

/**
 * Represents the state of a running MIDlet and its Display so that objects
 * do not have to be shared across Isolates. The states in this object are
 * updated by the MIDletProxyList upon receiving a notification event.
 * This class also provides methods for asynchronously changing a MIDlet's
 * state.
 */
public class MIDletProxy implements SuspendDependency {

    /** Constant for active state of a MIDlet. */
    public static final int MIDLET_ACTIVE = 0;

    /** Constant for paused state of a MIDlet. */
    public static final int MIDLET_PAUSED = 1;

    /** Constant for destroyed state of a MIDlet. */
    public static final int MIDLET_DESTROYED = 2;

    /** Cached reference to the ForegroundEventProducer. */
    private static ForegroundEventProducer foregroundEventProducer;

    /** Cached reference to the MIDletEventProducer. */
    private static MIDletEventProducer midletEventProducer;

    /** ID given to this midlet by an external application manager. */
    private int externalId;

    /** ID of the Isolate the MIDlet is running in. */
    private int isolateId;

    /** ID of the MIDlet's Display. */
    private int displayId;

    /** ID of the suite the MIDlet belongs to. */
    private int suiteId;

    /** Class name of the MIDlet. */
    private String className;

    /** Display name of the MIDlet to show the user. */
    private String displayName;

    /**
     * MIDlet life cycle state. Will be either MIDLET_ACTIVE, MIDLET_PAUSED,
     * or MIDLET_DESTROYED.
     */
    private int midletState;

    /** True if the MIDlet want's its Display in the foreground. */
    private boolean wantsForegroundState;

    /** True if the MIDlet has the foreground at least once. */
    private boolean requestedForeground;

    /** The display that is preempting this MIDlet. */
    private MIDletProxy preempting;

    /** True if alert is waiting for the foreground. */
    private boolean alertWaiting;

    /**
     * The display to put in the foreground after this display stops
     * preempting. If no display in this isolate had the foreground
     * then this will be null.
     */
    private MIDletProxy preempted;

    /**
     * Timer for the MIDlet proxy.  Used when a midlet is hanging.
     */
    private Timer proxyTimer;

    /** Parent list. */
    private MIDletProxyList parent;

    /**
     * Initialize the MIDletProxy class. Should only be called by the
     * MIDletProxyList.
     *
     * @param  theForegroundEventProducer reference to the event producer
     * @param  theMIDletEventProducer reference to the event producer
     */
    static void initClass(
        ForegroundEventProducer theForegroundEventProducer,
        MIDletEventProducer theMIDletEventProducer) {

        foregroundEventProducer = theForegroundEventProducer;
        midletEventProducer = theMIDletEventProducer;
    }

    /**
     * Construct a new MIDletProxy.
     *
     * @param  theParentList parent MIDlet proxy list
     * @param  theExternalAppId ID of given by an external application manager
     * @param  theIsolateId ID of the Isolate the MIDlet is running in.
     * @param  theSuiteId   ID of the suite MIDlet
     * @param  theClassName Class name of the MIDlet
     * @param  theDisplayName Display name of the MIDlet to show the user
     * @param  theMidletState MIDlet lifecycle state.
     */
    MIDletProxy(MIDletProxyList theParentList, int theExternalAppId,
         int theIsolateId, int theSuiteId,
         String theClassName, String theDisplayName, int theMidletState) {

        parent = theParentList;
        externalId = theExternalAppId;
        isolateId = theIsolateId;
        suiteId = theSuiteId;
        className = theClassName;
        displayName = theDisplayName;
        midletState = theMidletState;
    }

    /**
     * Get the external application ID used for forwarding changes.
     *
     * @return ID assigned by the external application manager
     */
    public int getExternalAppId() {
        return externalId;
    }

    /**
     * Get the ID of the Isolate the MIDlet is running in. Public for testing
     * purposes.
     *
     * @return ID of the Isolate the MIDlet is running in
     */
    public int getIsolateId() {
        return isolateId;
    }

    /**
     * Sets the ID of the MIDlet's Display.
     *
     * @param id of the MIDlet's Display
     */
    void setDisplayId(int id) {
        displayId = id;
    }

    /**
     * Get the ID of the MIDlet's Display. Public for testing purposes.
     *
     * @return ID of the MIDlet's Display
     */
    public int getDisplayId() {
        return displayId;
    }

    /**
     * Get the ID of the MIDlet's suite.
     *
     * @return ID of the MIDlet's suite
     */
    public int getSuiteId() {
        return suiteId;
    }

    /**
     * Get the class name of the MIDlet.
     *
     * @return class name of the MIDlet
     */
    public String getClassName() {
        return className;
    }

    /**
     * Get the Display name of the MIDlet.
     *
     * @return Display name of the MIDlet
     */
    public String getDisplayName() {
        return displayName;
    }

    /**
     * Set the MIDlet cycle state. Called by the
     * MIDlet proxy list when it receives an event from the MIDlet
     * to update this value.
     *
     * @param newMidletState new MIDlet state
     */
    void setMidletState(int newMidletState) {
        midletState = newMidletState;
    }

    /**
     * Get the MIDlet lifecycle state.
     *
     * @return MIDlet state
     */
    public int getMidletState() {
        return midletState;
    }

    /**
     * Set the wants foreground state in the proxy. Called by the
     * MIDlet proxy list when it receives an event from the MIDlet's
     * display to update this value.
     *
     * @param newWantsForeground new wants foreground value.
     * @param isAlert true if the displayable requesting the foreground,
     *        is an Alert, this parameter is ignored if newWantsForeground
     *        is false
     */
    void setWantsForeground(boolean newWantsForeground, boolean isAlert) {
        wantsForegroundState = newWantsForeground;

        if (newWantsForeground) {
            requestedForeground = true;
            alertWaiting = isAlert;
        } else {
            alertWaiting = false;
        }
    }

    /**
     * Check if the MIDlet want's its Display in the foreground.
     *
     * @return true if the MIDlet want's its Display in the foreground
     */
    public boolean wantsForeground() {
        return wantsForegroundState;
    }

    /**
     * Check if the MIDlet has not created its display.
     *
     * @return true if the MIDlet has no display.
     */
    public boolean noDisplay() {
        return displayId == 0;
    }

    /**
     * Check if the MIDlet has not set a displayable in its display.
     * Used by foreground selector to determine if the MIDlet it is
     * about to put in the foreground will draw the screen.
     *
     * @return true if the MIDlet has no displayable.
     */
    public boolean noDisplayable() {
        return !requestedForeground;
    }

    /**
     * Set the proxy of the display that is preempting this MIDlet.
     *
     * @param preemptingDisplay the preempting display
     */
    void setPreemptingDisplay(MIDletProxy preemptingDisplay) {
        // Turn on the user notification status for this proxy
        if (preemptingDisplay != null) {
            alertWaiting = true;
        } else {
            if (preempting != null) {
                /*
                 * There could be a proxy timer waiting to destroy the
                 * isolate if the user ended the alert with the end MIDlet
                 * button, so cancel the timer.
                 */
                preempting.setTimer(null);
            }

            alertWaiting = false;
        }

        preempting = preemptingDisplay;
    }

    /**
     * Get the proxy of the display that is preempting this MIDlet.
     *
     * @return the preempting display
     */
    MIDletProxy getPreemptingDisplay() {
        return preempting;
    }

    /**
     * Set the proxy of the MIDlet that should get the foreground
     * after preempting is done.
     *
     * @param preemptedDisplay the preempted display
     */
    void setPreemptedMidlet(MIDletProxy preemptedDisplay) {
        preempted = preemptedDisplay;
    }

    /**
     * Get the proxy of the MIDlet that should get the foreground
     * after preempting is done.
     *
     * @return the preempted display or null for none
     */
    MIDletProxy getPreemptedMidlet() {
        return preempted;
    }

    /**
     * Called to determine if alert is waiting for the foreground.
     *
     * @return true if an alert of the MIDlet is waiting in background.
     */
    public boolean isAlertWaiting() {
        return alertWaiting;
    }

    /**
     * Asynchronously change the MIDlet's state to active.
     *
     * This method does NOT change the state in the proxy, but
     * sends a activate MIDlet event to the MIDlet's Display.
     * The state in the proxy is only update when the MIDlet sends
     * a MIDlet activated event to the proxy list.
     */
    public void activateMidlet() {
        if (midletState != MIDLET_DESTROYED) {
            midletEventProducer.sendMIDletActivateEvent(isolateId, className);
        }
    }

    /**
     * Asynchronously change the MIDlet's state to paused.
     *
     * This method does NOT change the state in the proxy, but
     * sends a pause MIDlet event to the MIDlet's Display.
     * The state in the proxy is only update when the MIDlet sends
     * a MIDlet paused event to the proxy list.
     */
    public void pauseMidlet() {
        if (midletState != MIDLET_DESTROYED) {
            midletEventProducer.sendMIDletPauseEvent(isolateId, className);
        }
    }

    /**
     * Terminates ther MIDlet if it is neither paused nor destroyed.
     */
    public void terminateNotPausedMidlet() {
        if (midletState != MIDLET_DESTROYED && midletState != MIDLET_PAUSED) {
            MIDletProxyUtils.terminateMIDletIsolate(this, parent);
        }
    }

    /**
     * Asynchronously change the MIDlet's state to destroyed.
     *
     * This method does NOT change the state in the proxy, but
     * sends request to destroy MIDlet event to the AMS.
     * The state in the proxy is only update when the MIDlet sends
     * a MIDlet destroyed event to the proxy list.
     */
    public void destroyMidlet() {
        if (midletState != MIDLET_DESTROYED) {
            if (getTimer() != null) {
                // A destroy MIDlet event has been sent.
                return;
            }

            MIDletDestroyTimer.start(this, parent);

            midletEventProducer.sendMIDletDestroyEvent(isolateId, className);
        }
    }

    /** Process a MIDlet destroyed notification. */
    void destroyedNotification() {
        setTimer(null);
        setMidletState(MIDLET_DESTROYED);
    }

    /**
     * Notify the midlet's display of a foreground change. Called by
     * the MIDlet proxy list to notify the old and new foreground displays
     * of a foreground change.
     *
     * @param hasForeground true if the target is being put in the foreground
     */
    void notifyMIDletHasForeground(boolean hasForeground) {
        if (hasForeground) {
            alertWaiting = false;
            foregroundEventProducer.sendDisplayForegroundNotifyEvent(
                isolateId, displayId);
        } else {
            foregroundEventProducer.sendDisplayBackgroundNotifyEvent(
                isolateId, displayId);
        }
    }

    /**
     * Sets the timer object
     *
     * @param t Timer object
     */
    void setTimer(Timer t) {
        proxyTimer = t;
    }

    /**
     * Gets the timer object
     *
     * @return Timer
     */
    Timer getTimer() {
        return proxyTimer;
    }

    /**
     * Print the state of the proxy.
     *
     * @return printable representation of the state of this object
     */
    public String toString() {
        return "MIDletProxy: suite id = " + suiteId +
            "\n    class name = " + className +
            "\n    display name = " + displayName +
            "\n    isolate id = " + isolateId +
            ", display id = " + displayId +
            ", midlet state = " + midletState +
            ", wantsForeground = " + wantsForegroundState +
            ", requestedForeground = " + requestedForeground +
            "\n    alertWaiting = " + alertWaiting;
    }
}

/**
 * If the MIDlet is hanging this class will start a
 * timer and terminate the MIDlet when the timer
 * expires.  The timer will not work in SVM mode.
 */
class MIDletDestroyTimer {
    /** Timeout to let MIDlet destroy itself. */
    private static final int TIMEOUT =
        1000 * Configuration.getIntProperty("destoryMIDletTimeout", 5);

    /**
     * Starts timer for the specified MIDlet (proxy) .
     *
     * @param mp MIDletProxy to terminate if not destroyed in time
     * @param mpl the MIDletProxyList
     */
    static void start(final MIDletProxy mp, final MIDletProxyList mpl) {
        Timer timer = new Timer();
        mp.setTimer(timer);

        TimerTask task = new TimerTask() {
            /** Terminates MIDlet isolate and updates the proxy list. */
            public void run() {
                if (mp.getTimer() != null) {
                    MIDletProxyUtils.terminateMIDletIsolate(mp, mpl);
                }
                cancel();
            }
        };

        timer.schedule(task, TIMEOUT);
    }
}
