////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2005-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.rpc
{

import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.utils.describeType;
import flash.utils.flash_proxy;
import flash.utils.Proxy;

import mx.core.mx_internal;
import mx.messaging.ChannelSet;
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import mx.rpc.events.AbstractEvent;
import mx.utils.ObjectUtil;

use namespace flash_proxy;
use namespace mx_internal;

/**
 * The invoke event is dispatched when a service Operation is invoked so long as
 * an Error is not thrown before the Channel attempts to send the message.
 * @eventType mx.rpc.events.InvokeEvent.INVOKE 
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Event(name="invoke", type="mx.rpc.events.InvokeEvent")]

/**
 * The result event is dispatched when a service call successfully returns and
 * isn't handled by the Operation itself.
 * @eventType mx.rpc.events.ResultEvent.RESULT 
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Event(name="result", type="mx.rpc.events.ResultEvent")]

/**
 * The fault event is dispatched when a service call fails and isn't handled by
 * the Operation itself.
 * @eventType mx.rpc.events.FaultEvent.FAULT 
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
[Event(name="fault", type="mx.rpc.events.FaultEvent")]

[ResourceBundle("rpc")]

[Bindable(event="operationsChange")]

/**
 * The AbstractService class is the base class for the HTTPMultiService, WebService, 
 * and RemoteObject classes. This class does the work of creating Operations
 * which do the actual execution of remote procedure calls.
 *
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Flex 3
 */
public dynamic class AbstractService extends Proxy implements IEventDispatcher
{   
    //-------------------------------------------------------------------------
    //
    // Constructor
    //
    //-------------------------------------------------------------------------

    /**
     *  Constructor.
     *  
     *  @param destination The destination of the service.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function AbstractService(destination:String = null)
    {
        super();
        eventDispatcher = new EventDispatcher(this);
        asyncRequest = new AsyncRequest();

        if (destination)
        {
            this.destination = destination;
            asyncRequest.destination = destination;
        }

        _operations = {};
    }

    //-------------------------------------------------------------------------
    //
    // Variables
    //
    //-------------------------------------------------------------------------

    /**
     *  @private
     */
    private var resourceManager:IResourceManager =
                                    ResourceManager.getInstance();

    //-------------------------------------------------------------------------
    //
    //              Properties
    //
    //-------------------------------------------------------------------------

    //----------------------------------
    //  channelSet
    //----------------------------------

    /**
     *  Provides access to the ChannelSet used by the service. The
     *  ChannelSet can be manually constructed and assigned, or it will be 
     *  dynamically created to use the configured Channels for the
     *  <code>destination</code> for this service.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get channelSet():ChannelSet
    {
        return asyncRequest.channelSet;
    }

    /**
     *  @private
     */
    public function set channelSet(value:ChannelSet):void
    {
        if (channelSet != value)
        {
            asyncRequest.channelSet = value;
        }
    }

    //----------------------------------
    //  destination
    //----------------------------------

    [Inspectable(category="General")]

    /**
     * The destination of the service. This value should match a destination
     * entry in the services-config.xml file.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get destination():String
    {
        return asyncRequest.destination;
    }

    public function set destination(name:String):void
    {
        asyncRequest.destination = name;
    }


    //----------------------------------
    //  managers
    //----------------------------------

    private var _managers:Array;

    /**
     * The managers property stores a list of data managers which modify the
     * behavior of this service.  You can use this hook to define one or more
     * manager components associated with this service.  When this property is set,
     * if the managers have a property called "service" that property is set to 
     * the value of this service.  When this service is initialized, we also call
     * the initialize method on any manager components.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function get managers():Array
    {
        return _managers;
    }

    public function set managers(mgrs:Array):void
    {
        if (_managers != null)
        {
            for (var i:int = 0; i < _managers.length; i++)
            {
                var mgr:Object = _managers[i];
                if (mgr.hasOwnProperty("service"))
                    mgr.service = null;
            }
        }
        _managers = mgrs;
        for (i = 0; i < mgrs.length; i++)
        {
            mgr = _managers[i];
            if (mgr.hasOwnProperty("service"))
                mgr.service = this;
            if (_initialized && mgr.hasOwnProperty("initialize"))
                mgr.initialize();
        }
    }

    //----------------------------------
    //  operations
    //----------------------------------

    /**
     * @private
     */
    mx_internal var _operations:Object;

    /**
     * @private
     * This is required by data binding.
     */
    public function get operations():Object
    {
        return _operations;
    }

    /**
     * The Operations array is usually only set by the MXML compiler if you
     * create a service using an MXML tag.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function set operations(ops:Object):void
    {
        var op:AbstractOperation;
        for (var i:String in ops)
        {
            op = AbstractOperation(ops[i]);
            op.setService(this); // service is a write only property.
            if (!op.name)
                op.name = i;
            op.asyncRequest = asyncRequest;
        }
        _operations = ops;
        dispatchEvent(new flash.events.Event("operationsChange"));
    }

    //----------------------------------
    //  requestTimeout
    //----------------------------------

    [Inspectable(category="General")]

    /**
     *  Provides access to the request timeout in seconds for sent messages. 
     *  A value less than or equal to zero prevents request timeout.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */ 
    public function get requestTimeout():int
    {
        return asyncRequest.requestTimeout;
    }

    /**
     *  @private
     */
    public function set requestTimeout(value:int):void
    {
        if (requestTimeout != value)
        {
            asyncRequest.requestTimeout = value;
        }
    }
    
    //-------------------------------------------------------------------------
    //
    //              Methods
    //
    //-------------------------------------------------------------------------

    //---------------------------------
    //   EventDispatcher methods
    //---------------------------------

    /**
     * @private
     */
    public function addEventListener(type:String, listener:Function,
        useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
    {
        eventDispatcher.addEventListener(type, listener, useCapture, priority, useWeakReference);
    }

    /**
     * @private
     */
    public function dispatchEvent(event:Event):Boolean
    {
        return eventDispatcher.dispatchEvent(event);
    }

    /**
     * @private
     */
    public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
    {
        eventDispatcher.removeEventListener(type, listener, useCapture);
    }

    /**
     * @private
     */
    public function hasEventListener(type:String):Boolean
    {
        return eventDispatcher.hasEventListener(type);
    }
    
    /**
     * @private
     */
    public function willTrigger(type:String):Boolean
    {
        return eventDispatcher.willTrigger(type);
    }

    /**
     *  Called to initialize the service.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function initialize():void
    {
        if (!_initialized && _managers != null)
        {
            for (var i:int = 0; i < _managers.length; i++)
            {
                var mgr:Object = _managers[i];
                if (mgr.hasOwnProperty("initialize"))
                    mgr.initialize();
            }
            _initialized = true;
        }
    }

    //---------------------------------
    //   Proxy methods
    //---------------------------------
    /**
     * @private
     */
    override flash_proxy function getProperty(name:*):*
    {
        return getOperation(getLocalName(name));
    }

    /**
     * @private
     */
    override flash_proxy function setProperty(name:*, value:*):void
    {
        var message:String = resourceManager.getString(
            "rpc", "operationsNotAllowedInService", [ getLocalName(name) ]);
        throw new Error(message);
    }

    /**
     * @private
     */
    override flash_proxy function callProperty(name:*, ... args:Array):*
    {
        return getOperation(getLocalName(name)).send.apply(null, args);
    }

    //used to store the nextName values
    private var nextNameArray:Array;
    
    /**
     * @private
     */
    override flash_proxy function nextNameIndex(index:int):int
    {
        if (index == 0)
        {
            nextNameArray = [];
            for (var op:String in _operations)
            {
                nextNameArray.push(op);    
            }    
        }
        return index < nextNameArray.length ? index + 1 : 0;
    }

    /**
     * @private
     */
    override flash_proxy function nextName(index:int):String
    {
        return nextNameArray[index-1];
    }

    /**
     * @private
     */
    override flash_proxy function nextValue(index:int):*
    {
        return _operations[nextNameArray[index-1]];
    }

    mx_internal function getLocalName(name:Object):String
    {
        if (name is QName)
        {
            return QName(name).localName;
        }
        else
        {
            return String(name);
        }
    }

    //---------------------------------
    //   Public methods
    //---------------------------------

    /**
     * Returns an Operation of the given name. If the Operation wasn't
     * created beforehand, subclasses are responsible for creating it during
     * this call. Operations are usually accessible by simply naming them after
     * the service variable (<code>myService.someOperation</code>), but if your
     * Operation name happens to match a defined method on the service (like
     * <code>setCredentials</code>), you can use this method to get the
     * Operation instead.
     * @param name Name of the Operation.
     * @return Operation that executes for this name.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function getOperation(name:String):AbstractOperation
    {
        var o:Object = _operations[name];
        var op:AbstractOperation = (o is AbstractOperation) ? AbstractOperation(o) : null;
        return op;
    }

    /**
     *  Disconnects the service's network connection and removes any pending
     *  request responders.
     *  This method does not wait for outstanding network operations to complete.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function disconnect():void
    {
        asyncRequest.disconnect();
    }

    /**
     * Sets the credentials for the destination accessed by the service when using Data Services on the server side.
     * The credentials are applied to all services connected over the same
     * ChannelSet. Note that services that use a proxy or a third-party adapter
     * to a remote endpoint will need to setRemoteCredentials instead.
     * 
     * @param username The username for the destination.
     * @param password The password for the destination.
     * @param charset The character set encoding to use while encoding the
     * credentials. The default is null, which implies the legacy charset of
     * ISO-Latin-1. The only other supported charset is &quot;UTF-8&quot;.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function setCredentials(username:String, password:String, charset:String=null):void
    {
        asyncRequest.setCredentials(username, password, charset);
    }

    /**
     * Logs the user out of the destination. 
     * Logging out of a destination applies to everything connected using the
     * same ChannelSet as specified in the server configuration. For example,
     * if you're connected over the my-rtmp channel and you log out using one
     * of your RPC components, anything that was connected over the same
     * ChannelSet is logged out.
     *
     *  <p><b>Note:</b> Adobe recommends that you use the mx.messaging.ChannelSet.logout() method
     *  rather than this method. </p>
     *
     *  @see mx.messaging.ChannelSet#logout()   
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function logout():void
    {
        asyncRequest.logout();
    }

    /**
     * The username and password to be used to authenticate a user when
     * accessing a remote, third-party endpoint such as a web service through a
     * proxy or a remote object through a custom adapter when using Data Services on the server side.
     *
     * @param remoteUsername The username to pass to the remote endpoint
     * @param remotePassword The password to pass to the remote endpoint
     * @param charset The character set encoding to use while encoding the
     * remote credentials. The default is null, which implies the legacy charset
     * of ISO-Latin-1. The only other supported charset is &quot;UTF-8&quot;.
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Flex 3
     */
    public function setRemoteCredentials(remoteUsername:String, remotePassword:String, charset:String=null):void
    {
        asyncRequest.setRemoteCredentials(remoteUsername, remotePassword, charset);
    }
    
    //--------------------------------------------------------------
    //   Public methods from Object prototype not inherited by Proxy
    //--------------------------------------------------------------
    
    /**
     * Returns this service.
     * 
     * @private
     */  
    public function valueOf():Object
    {
        return this;
    }
    
    
    //--------------------------------------------------------------
    //   mx_internal for package methods
    //--------------------------------------------------------------
    
    /**
     * @private
     */
    mx_internal function hasTokenResponders(event:Event):Boolean
    {
        if (event is AbstractEvent)
        {
            var rpcEvent:AbstractEvent = event as AbstractEvent;
            if (rpcEvent.token != null && rpcEvent.token.hasResponder())
            {
                return true;
            }
        }

        return false;
    }   


    //--------------------------------------------------------------------------
    //
    // Variables
    // 
    //--------------------------------------------------------------------------

    mx_internal var _availableChannelIds:Array;
    mx_internal var asyncRequest:AsyncRequest;
    private var eventDispatcher:EventDispatcher;
    private var _initialized:Boolean = false;
}

}
