/* DesktopCtrl.java

	Purpose:
		
	Description:
		
	History:
		Fri Jul 29 08:47:19     2005, Created by tomyeh

Copyright (C) 2005 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zk.ui.sys;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.zkoss.util.media.Media;
import org.zkoss.zk.au.AuRequest;
import org.zkoss.zk.au.AuResponse;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.DesktopUnavailableException;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.Session;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.impl.DesktopEventQueue;
import org.zkoss.zk.ui.util.EventInterceptor;

/**
 * An addition interface to {@link Desktop}
 * for implementation.
 *
 * <p>Note: applications shall never access this interface.
 *
 * @author tomyeh
 */
public interface DesktopCtrl {
	/** Returns the request queue.
	 */
	public RequestQueue getRequestQueue();

	/** Returns the next available key which is unique in the whole desktop.
	 */
	public int getNextKey();

	/** Returns the next available UUID for a page.
	 * The returned UUID is unique in the desktop.
	 * You can consider it as unique in the whole session, though
	 * it may not be true if {@link org.zkoss.zk.ui.ext.RawId} is used
	 * (developer's responsibility to avoid conflict),
	 * integer overflow (too many UUID in one session, which
	 * can be considered as impossible), or a custom ID generator
	 * ({@link org.zkoss.zk.ui.sys.IdGenerator}) is used.
	 * @since 5.0.3
	 */
	public String getNextUuid(Page page);

	/** Returns the next available UUID for a component.
	 * The returned UUID is unique in the desktop.
	 * You can consider it as unique in the whole session, though
	 * it may not be true if {@link org.zkoss.zk.ui.ext.RawId} is used
	 * (developer's responsibility to avoid conflict),
	 * integer overflow (too many UUID in one session, which
	 * can be considered as impossible), or a custom ID generator
	 * ({@link org.zkoss.zk.ui.sys.IdGenerator}) is used.
	 * @since 5.0.3
	 */
	public String getNextUuid(Component comp);

	/** Adds a component to this page.
	 * <p>It is used internally and developers shall not invoke it
	 * explicityly.
	 * @exception InternalError if there is a component associated with
	 * the same UUID
	 */
	public void addComponent(Component comp);

	/** Maps a component associated with the given UUID to this page.
	 * Notice that the given uuid can be different from comp's UUID
	 * ({@link Component#getUuid}).
	 * <p>If the given component is null, the mapping of UUID is removed.
	 * <p>Unlike {@link #addComponent} and {@link #removeComponent}, this method simply replaces
	 * the mapping if the given UUID is already mapped to the other component.
	 * <p>It is used internally and developers shall not invoke it
	 * explicitly.
	 * @param comp the component to associate with the given UUID.
	 * If not, the association (i.e., mapping) is removed.
	 * @return the previous component that was associated with the given UUID.
	 * @since 6.0.0
	 */
	public Component mapComponent(String uuid, Component comp);

	/** Removes a component to this page.
	 * <p>It is used internally and developers shall not invoke it
	 * explicitly.
	 * @param recycleUuidAllowed whether it is OK to recycle UUID.
	 * @return whether UUID is recycled. If true, the caller shall
	 * reset UUID of the give component.
	 * @since 5.0.4
	 */
	public boolean removeComponent(Component comp, boolean recycleUuidAllowed);

	/** Adds a page to this desktop.
	 * It must be called when a page is created.
	 *
	 * <p>This is one of the only few method you could access
	 * before activating an execution.
	 */
	public void addPage(Page page);

	/** Removes a page from this desktop.
	 * <p>NOTE: once a page is removed, you can NOT add it back.
	 * You shall just GC it.
	 */
	public void removePage(Page page);

	/** Sets the desktop identifier.
	 *
	 * <p>It is callable only if it is the recovering phase, i.e.,
	 * {@link ExecutionCtrl#isRecovering} is true.
	 * In other words, callable only in the invocation of
	 * {@link FailoverManager#recover}.
	 *
	 * @exception IllegalStateException if it is NOT in recovering.
	 */
	public void setId(String id);

	/** Called when the recovering failed.
	 */
	public void recoverDidFail(Throwable ex);

	/** Notification that the session, which owns this desktop,
	 * is about to be passivated (a.k.a., serialized) by the Web container.
	 */
	public void sessionWillPassivate(Session sess);

	/** Notification that the session, which owns this desktop,
	 * has just been activated (a.k.a., deserialized) by the Web container.
	 */
	public void sessionDidActivate(Session sess);

	/** Called when the desktop is about to be destroyed.
	 */
	public void destroy();

	/** Called when the desktop has been recycled.
	 * More precisely, it is called when the desktop is no longer used
	 * and ready to be re-used later.
	 * @since 5.0.7
	 */
	public void recycle();

	/** Returns a collection of suspended event processing threads, or empty
	 * if no suspended thread at all.
	 *
	 * <p>An event processing thread is an instance of
	 * {@link EventProcessingThread}
	 *
	 * <p>Note: if you access this method NOT in an event listener for
	 * the SAME desktop, you have to synchronize the iteration
	 * (though the returned collection is synchronized).
	 * Of course, it is always safe to test whether it is empty
	 * ({@link Collection#isEmpty}).
	 *
	 * <pre><code>
	//Use the following pattern IF it is not in the SAME desktop's listener
	Collection c = otherDesktop.getSuspendedThreads();
	if (c.isEmpty()) {
	//do something accordingly
	} else {
	synchronized (c) {
	for (Iterator it = c.iterator(); it.hasNext();) {
	  //...
	}
	}
	}</code></pre>
	 */
	public Collection<EventProcessingThread> getSuspendedThreads();

	/** Ceases the specified event thread.
	 *
	 * @param cause an arbitrary text to describe the cause.
	 * It will be the message of the thrown InterruptedException.
	 * @return true if the event processing thread is ceased successfully;
	 * false if no such thread or it is not suspended.
	 */
	public boolean ceaseSuspendedThread(EventProcessingThread evtthd, String cause);

	/** Returns the media that is associated with
	 * {@link Desktop#getDownloadMediaURI}, or
	 * null if not found.
	 *
	 * <p>This method is used internally. Developers rarely need to
	 * access this method.
	 *
	 * @param reserved reserved for future use.
	 * @return the media or null if not found.
	 */
	public Media getDownloadMedia(String medId, boolean reserved);

	/** Called when a component added or removed a listener for
	 * {@link Events#ON_PIGGYBACK}.
	 *
	 * <p>The implementation usually uses it to optimize whether to
	 * call the listener when {@link #onPiggyback} is called.
	 *
	 * @param comp the component that adds an listener for
	 * {@link Events#ON_PIGGYBACK}.
	 * The component may or may not be a root component.
	 * @param listen whether the listener is added (or removed).
	 * @since 3.0.0
	 */
	public void onPiggybackListened(Component comp, boolean listen);

	/** Called each time when ZK Update Engine retrieves events.
	 * It is used to implement the piggyback feature by posting
	 * the events (see {@link Events#ON_PIGGYBACK}).
	 * The implementation could post events here. It should not process
	 * event here (since event thread might be used).
	 *
	 * <p>Used only internally. Application developers shall not call it.
	 *
	 * @since 3.0.0
	 */
	public void onPiggyback();

	/** Returns the server-push controller, or null if it is not enabled
	 * yet.
	 */
	public ServerPush getServerPush();

	/** Enables the server-push feature with the specified server-push
	 * controller.
	 * If you want to use the default serverpush, use {@link Desktop#enableServerPush}
	 * instead. This method allows the caller to provide a server push
	 * for more control.
	 * <p>Example:
	 * <pre><code>desktop.enableServerPush(new PollingServerPush(1000,6000,5));</code></pre>
	 *
	 * <p>Notice: a server push controller can be used in one desktop.
	 * It cannot be shared.
	 *
	 * @param serverpush the server-push controller. If null,
	 * the server-push feature is disabled (for this desktop).
	 * Note: this method will invoke {@link ServerPush#start}, so the
	 * caller doesn't need to do it.
	 * @since 3.0.0
	 * @see Desktop#enableServerPush
	 */
	public boolean enableServerPush(ServerPush serverpush);

	/**
	 * Enable/Disable serverpush using reference counting, so that multiple enablers can 
	 * use the same serverpush and deregister whenever they want.
	 * @param enable true/false enable/disable serverpush
	 * @param enabler the same reference must be used to disable again
	 * @return Currently only used by {@link DesktopEventQueue} to enable several
	 * eventqueues to use the same {@link ServerPush} 
	 * @since 6.5.4
	 */
	public boolean enableServerPush(boolean enable, Serializable enabler);

	/** Invokes {@link EventInterceptor#beforeSendEvent}
	 * registered by {@link Desktop#addListener}.
	 *
	 * <p>Note: it invokes
	 * {@link org.zkoss.zk.ui.util.Configuration#beforeSendEvent}
	 * automatically.
	 * @since 3.0.0
	 */
	public Event beforeSendEvent(Event event);

	/** Invokes {@link EventInterceptor#beforePostEvent}
	 * registered by {@link Desktop#addListener}.
	 *
	 * <p>Note: it invokes
	 * {@link org.zkoss.zk.ui.util.Configuration#beforePostEvent}
	 * automatically.
	 * @since 3.0.0
	 */
	public Event beforePostEvent(Event event);

	/** Invokes {@link EventInterceptor#beforeProcessEvent}
	 * registered by {@link Desktop#addListener}.
	 *
	 * <p>Note: it invokes
	 * {@link org.zkoss.zk.ui.util.Configuration#beforeProcessEvent}
	 * automatically.
	 * @since 3.0.0
	 */
	public Event beforeProcessEvent(Event event) throws Exception;

	/** Invokes {@link EventInterceptor#afterProcessEvent}
	 * registered by {@link Desktop#addListener}.
	 *
	 * <p>Note: it invokes
	 * {@link org.zkoss.zk.ui.util.Configuration#afterProcessEvent}
	 * automatically.
	 * @since 3.0.0
	 */
	public void afterProcessEvent(Event event) throws Exception;

	/** Invokes {@link org.zkoss.zk.ui.util.DesktopCleanup#cleanup} for each relevant
	 * listener registered by {@link Desktop#addListener}.
	 *
	 * <p>Used only internally.
	 *
	 * <p>It never throws an exception.
	 *
	 * @since 3.0.6
	 */
	public void invokeDesktopCleanups();

	/** Invokes {@link org.zkoss.zk.ui.util.ExecutionInit#init} for each relevant
	 * listener registered by {@link Desktop#addListener}.
	 *
	 * <p>Used only internally.
	 *
	 * @param exec the execution that is created
	 * @param parent the previous execution, or null if no previous at all
	 * @exception UiException to prevent an execution from being created
	 * @since 3.0.6
	 */
	public void invokeExecutionInits(Execution exec, Execution parent) throws UiException;

	/** Invokes {@link org.zkoss.zk.ui.util.ExecutionCleanup#cleanup} for each relevant
	 * listener registered by {@link Desktop#addListener}.
	 *
	 * <p>Used only internally.
	 *
	 * <p>It never throws an exception but logs and adds it to the errs argument,
	 * if not null.
	 *
	 * @param exec the execution that is being destroyed
	 * @param parent the previous execution, or null if no previous at all
	 * @param errs a list of exceptions (java.lang.Throwable) if any exception
	 * occurred before this method is called, or null if no exception at all.
	 * Note: you can manipulate the list directly to add or clean up exceptions.
	 * For example, if exceptions are fixed correctly, you can call errs.clear()
	 * such that no error message will be displayed at the client.
	 * @since 3.0.6
	 */
	public void invokeExecutionCleanups(Execution exec, Execution parent, List<Throwable> errs);

	/** Invokes {@link org.zkoss.zk.ui.util.UiLifeCycle#afterComponentAttached}.
	 * @since 3.0.6
	 */
	public void afterComponentAttached(Component comp, Page page);

	/** Invokes {@link org.zkoss.zk.ui.util.UiLifeCycle#afterComponentDetached}.
	 * @since 3.0.6
	 */
	public void afterComponentDetached(Component comp, Page prevpage);

	/** Invokes {@link org.zkoss.zk.ui.util.UiLifeCycle#afterComponentMoved}.
	 *
	 * @param prevparent the previous parent. If it is the same as
	 * comp's {@link Component#getParent}, comp is moved in the same parent.
	 * @since 3.0.6
	 */
	public void afterComponentMoved(Component parent, Component child, Component prevparent);

	//Response Utilities//
	/** Called when ZK Update Engine has sent a response to the client.
	 *
	 * @param reqId the request ID that the response is generated for.
	 * Ignore if null.
	 * @param resInfo the response. Ignored if reqId is null.
	 * @since 5.0.0
	 */
	public void responseSent(String reqId, Object resInfo);

	/** Returns the response for the last request, or null
	 * if no response yet, or the specified request ID doesn't match
	 * the last one (passed to {@link #responseSent}).
	 * <p>The return value is the value passed to resInfo when calling
	 * {@link #responseSent}.
	 * @since 5.0.0
	 */
	public Object getLastResponse(String reqId);

	/** Returns the sequence ID of the response.
	 * The client and server uses the sequence ID to make sure
	 * the responses are processed in the correct order.
	 *
	 * <p>The range of the sequence IDs is 1~999.
	 *
	 * @param advance whether to advance the number before returning.
	 * If true, the ID is increased and then returned.
	 * If false, the previous value is returned
	 * @since 3.5.0
	 */
	public int getResponseId(boolean advance);

	/** Sets the sequence ID of the response.
	 *
	 * <p>It is rarely called other than in the recovering mode, i.e.,
	 * {@link ExecutionCtrl#isRecovering} is true.
	 *
	 * @param resId a value between 1 and 999.
	 * You can reset the ID by passing a non-positive value.
	 * @since 3.5.0
	 */
	public void setResponseId(int resId);

	/** Adds the responses to the so-called piggy-back queue.
	 * The responses in the piggy-back queue will be sent in
	 * the next AU request.
	 * <p>This method is useful for working thread that
	 * wants to sent the responses back to the client.
	 * A typical example is the Comet-based server push.
	 *
	 * @param response the responses to be appended to the piggy-back queue.
	 * @param reset whether to reset the piggy-back queue after
	 * returning the queued responses.
	 * @return all responses in the piggy-back queue, or null
	 * if nothing in the queue.
	 * @since 5.0.0
	 */
	public List<AuResponse> piggyResponse(Collection<AuResponse> response, boolean reset);

	/** Schedules a task to run under the server push of the given desktop asynchronously.
	 * It is called by {@link org.zkoss.zk.ui.Executions#schedule}.
	 * Don't call it directly.
	 * <p>Like {@link #activateServerPush} and {@link #deactivateServerPush}, this method could
	 * be called in any thread, so the implementation of this method has to be safe for concurrent access.
	 * @param task the task to execute
	 * @param event the event to be passed to the task (i.e., the event listener).
	 * It could null or any instance as long as the task recognizes it.
	 * @exception IllegalStateException if the server push is not enabled.
	 * @exception DesktopUnavailableException if the desktop is removed
	 * (when activating).
	 * @since 5.0.6
	 */
	public <T extends Event> void scheduleServerPush(EventListener<T> task, T event);

	/** Returns if there is any scheduled task for server push.
	 * @since 5.0.6
	 */
	public boolean scheduledServerPush();

	/** Activates the current thread for accessing this desktop by the server push.
	 * It is called by {@link org.zkoss.zk.ui.Executions#activate}.
	 * Don't call it directly.
	 *
	 * <p>Like {@link #scheduleServerPush}, this method could
	 * be called in any thread, so it has to be safe for concurrent access.
	 * <p>Note: the server push must be enabled first (by use of
	 * {@link Desktop#enableServerPush}).
	 *
	 * @param timeout the maximum time to wait in milliseconds.
	 * Ignored (i.e., never timeout) if non-positive.
	 * @exception IllegalStateException if the server push is not enabled.
	 * @since 3.5.2
	 */
	public boolean activateServerPush(long timeout) throws InterruptedException;

	/** Deactivates the thread that has invoked {@link #activateServerPush}
	 * successfully.
	 * It is called by {@link org.zkoss.zk.ui.Executions#deactivate}.
	 * Don't call it directly.
	 * @since 3.5.2
	 */
	public void deactivateServerPush();

	/** Processes an AU request.
	 * Notice that not only the requests for a desktop but also the requests
	 * for any component in the desktop will go thru this method.
	 *
	 * <p>To override the default processing, register an AU request service
	 * {@link org.zkoss.zk.au.AuService} by invoking {@link Desktop#addListener}.
	 *
	 * <ol>
	 * <li>This method first invokes the registered AU request service
	 * ({@link org.zkoss.zk.au.AuService}) one-by-one, until
	 * the first one returns true.</li>
	 * <li>If none of them returns true or no AU service at all,
	 * it checks if the request is targeting a component
	 * (i.e., {@link AuRequest#getComponent} is not null).</li>
	 * <li>If it is targeting a component, it invokes
	 * {@link ComponentCtrl#service} to handle the service.</li>
	 * <li>If it is not targeting a component (i.e., targeting to
	 * this desktop), it handles as follows.
	 * <ul>
	 * <li>It handles the recognized requests, including
	 * onBookmarkChange, onURIChange and onClientInfo.</li>
	 * <li>If the request is not one of above, it converts the request to
	 * an event (by {@link Event#getEvent}) and then posts the event
	 * (by {@link Events#postEvent}).</li></ul></li>
	 * </ol>
	 *
	 * <p>Notice that the registered AU request service
	 * ({@link org.zkoss.zk.au.AuService}) will be called, no matter
	 * the request is targeting a component or a desktop.
	 * And, it can 'intercept' or 'filter' it by returning false.
	 *
	 * <p>To send responses to the client, use
	 * {@link org.zkoss.zk.ui.AbstractComponent#smartUpdate},
	 * {@link org.zkoss.zk.ui.AbstractComponent#response}
	 * or {@link Component#invalidate()}.
	 *
	 * <p>If you want to intercept events, you can register a listener implementing
	 * {@link EventInterceptor}, or overriding {@link #afterProcessEvent}.
	 *
	 * @param everError if any error ever occurred before
	 * processing this request. In other words, indicates if the previous
	 * request causes any exception.
	 * @since 5.0.0
	 */
	public void service(AuRequest request, boolean everError);

	/** Sets the execution (used to represent a lock).
	 * <p>Used only to implement {@link UiEngine}.
	 */
	public void setExecution(Execution exec);

	/** Returns the visualizer associated with this desktop.
	 * <p>Used only to implement {@link UiEngine}.
	 * @since 3.6.2
	 */
	public Visualizer getVisualizer();

	/** Sets the visualizer associated with is desktop.
	 * <p>Used only to implement {@link UiEngine}.
	 * @since 3.6.2
	 */
	public void setVisualizer(Visualizer uv);

	/** Returns the lock used to activate an execution.
	 * Before calling {@link #setVisualizer}, this object returned
	 * by this method must be locked first.
	 * <p>Used only to implement {@link UiEngine}.
	 * @since 3.6.2
	 */
	public Object getActivationLock();
}
