<?php
/**
 *  This file is part of amfPHP
 *
 * LICENSE
 *
 * This source file is subject to the license that is bundled
 * with this package in the file license.txt.
 * @package Amfphp_Plugins_FlexMessaging
 */

/**
*  includes
*  */
require_once dirname(__FILE__) . '/AcknowledgeMessage.php';
require_once dirname(__FILE__) . '/ErrorMessage.php';
/**
 * Support for flex messaging.
 * 
 * This plugin can be deactivated if the project doesn't need flex messaging, usually a RemoteObject in Flex.
 * 
 * Flex doesn't use the basic packet system. When using a remote objct, first a CommandMessage is sent, expecting an AcknowledgeMessage in return.
 * Then a RemotingMessage is sent, expecting an AcknowledgeMessage in return.
 * This plugin adds support for this message flow.
 * 
 * In case of an error, an ErrorMessage is expected
 * 
 * @link http://livedocs.adobe.com/flex/3/html/help.html?content=data_access_4.html
 * @package Amfphp_Plugins_FlexMessaging
 * @author Ariel Sommeria-Klein
 */
class AmfphpFlexMessaging{
    const FLEX_TYPE_COMMAND_MESSAGE = 'flex.messaging.messages.CommandMessage';
    const FLEX_TYPE_REMOTING_MESSAGE = 'flex.messaging.messages.RemotingMessage';
    const FLEX_TYPE_ACKNOWLEDGE_MESSAGE = 'flex.messaging.messages.AcknowledgeMessage';
    const FLEX_TYPE_ERROR_MESSAGE = 'flex.messaging.messages.ErrorMessage';
    
    const FIELD_MESSAGE_ID = 'messageId';

    /**
     * if this is set, special error handling applies
     * @var Boolean
     */
    protected $clientUsesFlexMessaging;

    /**
     * the messageId of the last flex message. Used for error generation
     * @var String
     */
    protected $lastFlexMessageId;

    /**
     * the response uri of the last flex message. Used for error generation
     * @var String
     */
    protected $lastFlexMessageResponseUri;

    protected $returnErrorDetails = false;
    /**
     * constructor.
     * @param array $config optional key/value pairs in an associative array. Used to override default configuration values.
     */
    public function  __construct(array $config = null) {
        Amfphp_Core_FilterManager::getInstance()->addFilter(Amfphp_Core_Amf_Handler::FILTER_AMF_REQUEST_MESSAGE_HANDLER, $this, 'filterAmfRequestMessageHandler');
        Amfphp_Core_FilterManager::getInstance()->addFilter(Amfphp_Core_Amf_Handler::FILTER_AMF_EXCEPTION_HANDLER, $this, 'filterAmfExceptionHandler');
        $this->clientUsesFlexMessaging = false;
        $this->returnErrorDetails = (isset ($config[Amfphp_Core_Config::CONFIG_RETURN_ERROR_DETAILS]) && $config[Amfphp_Core_Config::CONFIG_RETURN_ERROR_DETAILS]);

    }

    /**
     *
     * @param Object $handler. null at call. If the plugin takes over the handling of the request message,
     * it must set this to a proper handler for the message, probably itself.
     * @param Amfphp_Core_Amf_Message $requestMessage the request message
     * @return array
     */
    public function filterAmfRequestMessageHandler($handler, Amfphp_Core_Amf_Message $requestMessage){
        if($requestMessage->data == null){
            //all flex messages have data
            return;
        }

        $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;

        if(!isset ($requestMessage->data[0]) || !isset ($requestMessage->data[0]->$explicitTypeField)){
            //and all flex messages have data containing one object with an explicit type
            return;
        }

        $messageType = $requestMessage->data[0]->$explicitTypeField;
        if($messageType == self::FLEX_TYPE_COMMAND_MESSAGE || $messageType == self::FLEX_TYPE_REMOTING_MESSAGE){
            //recognized message type! This plugin will handle it
            $this->clientUsesFlexMessaging = true;
            return $this;
        }
    }

    /**
     *
     * @param Object $handler. null at call. If the plugin takes over the handling of the request message,
     * it must set this to a proper handler for the message, probably itself.
     * @return array
     */
    public function filterAmfExceptionHandler($handler){
        if($this->clientUsesFlexMessaging){
            return $this;
        }
    }

    /**
     * handle the request message instead of letting the Amf Handler do it.
     * @param Amfphp_Core_Amf_Message $requestMessage
     * @param Amfphp_Core_Common_ServiceRouter $serviceRouter
     * @return Amfphp_Core_Amf_Message
     */
    public function handleRequestMessage(Amfphp_Core_Amf_Message $requestMessage, Amfphp_Core_Common_ServiceRouter $serviceRouter){
        $explicitTypeField = Amfphp_Core_Amf_Constants::FIELD_EXPLICIT_TYPE;
        $messageType = $requestMessage->data[0]->$explicitTypeField;
        $messageIdField = self::FIELD_MESSAGE_ID;
        $this->lastFlexMessageId = $requestMessage->data[0]->$messageIdField;
        $this->lastFlexMessageResponseUri = $requestMessage->responseUri;


        if($messageType == self::FLEX_TYPE_COMMAND_MESSAGE){
            //command message. An empty AcknowledgeMessage is expected.
            $acknowledge = new AmfphpFlexMessaging_AcknowledgeMessage($requestMessage->data[0]->$messageIdField);
            return new Amfphp_Core_Amf_Message($requestMessage->responseUri . Amfphp_Core_Amf_Constants::CLIENT_SUCCESS_METHOD, null, $acknowledge);

        }


        if($messageType == self::FLEX_TYPE_REMOTING_MESSAGE){
            //remoting message. An AcknowledgeMessage with the result of the service call is expected.
            $remoting = $requestMessage->data[0];
            $serviceCallResult = $serviceRouter->executeServiceCall($remoting->source, $remoting->operation, $remoting->body);
            $acknowledge = new AmfphpFlexMessaging_AcknowledgeMessage($remoting->$messageIdField);
            $acknowledge->body = $serviceCallResult;
            return new Amfphp_Core_Amf_Message($requestMessage->responseUri . Amfphp_Core_Amf_Constants::CLIENT_SUCCESS_METHOD, null, $acknowledge);

        }
        throw new Amfphp_Core_Exception('unrecognized flex message');
    }

    /**
     * flex expects error messages formatted in a special way, using the ErrorMessage object.
     * @return Amfphp_Core_Amf_Packet
     * @param Exception $exception
     */
    public function generateErrorResponse(Exception $exception){
        $error = new AmfphpFlexMessaging_ErrorMessage($this->lastFlexMessageId);
        $error->faultCode = $exception->getCode();
        $error->faultString = $exception->getMessage();
        if($this->returnErrorDetails){
            $error->faultDetail = $exception->getTraceAsString();
            $error->rootCause = $exception;
        }
        $errorMessage = new Amfphp_Core_Amf_Message($this->lastFlexMessageResponseUri . Amfphp_Core_Amf_Constants::CLIENT_FAILURE_METHOD, null, $error);
        $errorPacket = new Amfphp_Core_Amf_Packet();
        $errorPacket->messages[] = $errorMessage;
        return $errorPacket;
    }
}

?>
