//
// $Id: ZoneProvider.java,v 1.5 2002/02/07 03:26:58 mdb Exp $

package com.threerings.whirled.zone.server;

import com.threerings.presents.server.InvocationProvider;
import com.threerings.presents.server.ServiceFailedException;

import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.LocationProvider;

import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.server.SceneRegistry;
import com.threerings.whirled.server.SceneManager;

import com.threerings.whirled.zone.Log;
import com.threerings.whirled.zone.client.ZoneCodes;
import com.threerings.whirled.zone.data.ZoneSummary;
import com.threerings.whirled.zone.data.ZonedBodyObject;

/**
 * Provides zone related services which are presently the ability to move
 * from zone to zone.
 */
public class ZoneProvider
    extends InvocationProvider implements ZoneCodes
{
    /**
     * Constructs a zone provider that will interoperate with the supplied
     * zone and scene registries. The zone provider will automatically be
     * constructed and registered by the {@link ZoneRegistry}, which a
     * zone-using system must create and initialize in their server.
     */
    public ZoneProvider (ZoneRegistry zonereg, SceneRegistry screg)
    {
        _zonereg = zonereg;
        _screg = screg;
    }

    /**
     * Processes a request from a client to move to a scene in a new zone.
     *
     * @param source the user requesting the move.
     * @param invid the invocation id of the request.
     * @param zoneId the qualified zone id of the new zone.
     * @param sceneId the identifier of the new scene.
     * @param sceneVew the version of the scene model currently held by
     * the client.
     */
    public void handleMoveToRequest (BodyObject source, int invid,
                                     int zoneId, int sceneId, int sceneVer)
    {
        // look up the zone manager for the zone
        ZoneManager zmgr = _zonereg.getZoneManager(zoneId);
        if (zmgr == null) {
            Log.warning("Requested to enter a zone for which we have no " +
                        "manager [user=" + source +
                        ", zoneId=" + zoneId + "].");
            sendResponse(source, invid, MOVE_FAILED_RESPONSE, NO_SUCH_ZONE);
            return;
        }

        // make sure they're not already in that zone
        if (((ZonedBodyObject)source).getZoneId() == zoneId) {
            sendResponse(source, invid, MOVE_FAILED_RESPONSE, ALREADY_THERE);
            return;
        }

        // avoid cluttering up the method declaration with final keywords
        final BodyObject fsource = source;
        final int finvid = invid;
        final int fsceneId = sceneId;
        final int fsceneVer = sceneVer;

        // resolve the zone!
        ZoneManager.ResolutionListener zl = new ZoneManager.ResolutionListener()
        {
            public void zoneWasResolved (ZoneSummary summary) {
                continueMoveToRequest(fsource, finvid, summary,
                                      fsceneId, fsceneVer);
            }

            public void zoneFailedToResolve (int zoneId, Exception reason) {
                Log.warning("Unable to resolve zone [zoneId=" + zoneId +
                            ", reason=" + reason + "].");
                sendResponse(fsource, finvid,
                             MOVE_FAILED_RESPONSE, NO_SUCH_ZONE);
            }
        };
        zmgr.resolveZone(zoneId, zl);
    }

    /**
     * This is called after we have resolved our zone.
     */
    protected void continueMoveToRequest (
        BodyObject source, int invid, ZoneSummary summary,
        int sceneId, int sceneVer)
    {
        // avoid cluttering up the method declaration with final keywords
        final BodyObject fsource = source;
        final int finvid = invid;
        final ZoneSummary fsum = summary;
        final int fsceneVer = sceneVer;

        // give the zone manager a chance to veto the request
        ZoneManager zmgr = _zonereg.getZoneManager(summary.zoneId);
        String errmsg = zmgr.ratifyBodyEntry(source, summary.zoneId);
        if (errmsg != null) {
            sendResponse(fsource, finvid, MOVE_FAILED_RESPONSE, errmsg);
            return;
        }

        // create a callback object that will handle the resolution or
        // failed resolution of the scene
        SceneRegistry.ResolutionListener rl =
            new SceneRegistry.ResolutionListener()
        {
            public void sceneWasResolved (SceneManager scmgr) {
                finishMoveToRequest(fsource, finvid, fsum, scmgr, fsceneVer);
            }

            public void sceneFailedToResolve (int sceneId, Exception reason) {
                Log.warning("Unable to resolve scene [sceneid=" + sceneId +
                            ", reason=" + reason + "].");
                // pretend like the scene doesn't exist to the client
                sendResponse(fsource, finvid,
                             MOVE_FAILED_RESPONSE, NO_SUCH_PLACE);
            }
        };

        // make sure the scene they are headed to is actually loaded into
        // the server
        _screg.resolveScene(sceneId, rl);
    }

    /**
     * This is called after the scene to which we are moving is guaranteed
     * to have been loaded into the server.
     */
    protected void finishMoveToRequest (
        BodyObject source, int invid, ZoneSummary summary,
        SceneManager scmgr, int sceneVersion)
    {
        // move to the place object associated with this scene
        PlaceObject plobj = scmgr.getPlaceObject();
        int ploid = plobj.getOid();

        try {
            // try doing the actual move
            PlaceConfig config = LocationProvider.moveTo(source, ploid);

            // now that we've finally moved, we can update the user object
            // with the new zone id
            ((ZonedBodyObject)source).setZoneId(summary.zoneId);

            // check to see if they need a newer version of the scene data
            SceneModel model = scmgr.getSceneModel();
            if (sceneVersion < model.version) {
                // then send the moveTo response
                sendResponse(source, invid, MOVE_SUCCEEDED_PLUS_UPDATE_RESPONSE,
                             new Object[] { new Integer(ploid), config,
                                            summary, model });

            } else {
                // then send the moveTo response
                sendResponse(source, invid, MOVE_SUCCEEDED_RESPONSE,
                             new Integer(ploid), config, summary);
            }

            // let the zone manager know that someone just came on in
            ZoneManager zmgr = _zonereg.getZoneManager(summary.zoneId);
            zmgr.bodyDidEnterZone(source, summary.zoneId);

        } catch (ServiceFailedException sfe) {
            sendResponse(source, invid,
                         MOVE_FAILED_RESPONSE, sfe.getMessage());
        }
    }

    /** The zone registry with which we communicate. */
    protected ZoneRegistry _zonereg;

    /** The scene registry with which we communicate. */
    protected SceneRegistry _screg;
}
