/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2002 - 2007 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package flex.messaging.endpoints.amf;

import flex.messaging.endpoints.BaseHTTPEndpoint;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.ActionContext;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.messages.Message;
import flex.messaging.messages.RemotingMessage;
import flex.messaging.messages.ErrorMessage;

import java.io.IOException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Array;

/**
 * AMF Headers are of limited use because the apply to the entire AMF packet, which
 * may contain a batch of several requests.
 * <p>
 * Rather than relying on the Flash Player team to change the AMF specification,
 * Flex 1.5 introduced the concept of a Message Envelope that allowed them to provide
 * message level headers that apply to a single request body.
 * </p>
 * <p>
 * Essentially they introduced one more layer of indirection with an ASObject of type &quot;Envelope&quot;
 * that had two properties:<br />
 * - <i>headers</i>, which was an array of Header structures<br />
 * - <i>body</i>, which was the actual data of the request (typically an array of arguments)
 * </p>
 * <p>
 * To save space on the wire, a Header structure was simply an array. The first element was
 * the header name as a String, and was the only required field. The second element, a boolean,
 * indicated whether the header must be understood. The third element, any Object, represented
 * the header value, if required.
 * </p>
 *
 * @author Peter Farland
 */
public class LegacyFilter extends AMFFilter
{
    public static final String LEGACY_ENVELOPE_FLAG_KEY = "_flag";
    public static final String LEGACY_ENVELOPE_FLAG_VALUE = "Envelope";
    public static final String LEGACY_SECURITY_HEADER_NAME = "Credentials";
    public static final String LEGACY_SECURITY_PRINCIPAL = "userid";
    public static final String LEGACY_SECURITY_CREDENTIALS = "password";
    
    private BaseHTTPEndpoint endpoint;

    public LegacyFilter(BaseHTTPEndpoint endpoint)
    {
        this.endpoint = endpoint;
    }

    public void invoke(final ActionContext context) throws IOException
    {
        MessageBody requestBody = context.getRequestMessageBody();
        context.setLegacy(true);

        // Parameters are usually sent as an AMF Array
        Object data = requestBody.getData();
        List newParams = null;

        // Check whether we're a new Flex 2.0 Messaging request
        if (data != null)
        {
            if (data.getClass().isArray())
            {
                int paramLength = Array.getLength(data);
                if (paramLength == 1)
                {
                    Object obj = Array.get(data, 0);
                    if (obj != null && obj instanceof Message)
                    {
                        context.setLegacy(false);
                        newParams = new ArrayList();
                        newParams.add(obj);
                    }
                }

                // It was not a Flex 2.0 Message, but we have an array, use its contents as our params
                if (newParams == null)
                {
                    newParams = new ArrayList();
                    for (int i = 0; i < paramLength; i++)
                    {
                        try
                        {
                            newParams.add(Array.get(data, i));
                        }
                        catch (Throwable t)
                        {
                        }
                    }
                }
            }
            else if (data instanceof List)
            {
                List paramList = (List)data;
                if (paramList.size() == 1)
                {
                    Object obj = paramList.get(0);
                    if (obj != null && obj instanceof Message)
                    {
                        context.setLegacy(false);
                        newParams = new ArrayList();
                        newParams.add(obj);
                    }
                }

                // It was not a Flex 2.0 Message, but we have a list, so use it as our params
                if (newParams == null)
                {
                    newParams = (List)data;
                }
            }
        }

        // We still haven't found any lists of params, so create one with
        // whatever data we have.
        if (newParams == null)
        {
            newParams = new ArrayList();
            newParams.add(data);

        }

        if (context.isLegacy())
        {
            newParams = legacyRequest(context, newParams);
        }

        requestBody.setData(newParams);


        next.invoke(context);


        if (context.isLegacy())
        {
            MessageBody responseBody = context.getResponseMessageBody();
            Object response = responseBody.getData();

            if (response instanceof ErrorMessage)
            {
                ErrorMessage error = (ErrorMessage)response;
                ASObject aso = new ASObject();
                aso.put("message", error.faultString);
                aso.put("code", error.faultCode);
                aso.put("details", error.faultDetail);
                aso.put("rootCause", error.rootCause);
                response = aso;
            }
            else if (response instanceof Message)
            {
                response = ((Message)response).getBody();
            }
            responseBody.setData(response);
        }
    }

    private List legacyRequest(ActionContext context, List oldParams)
    {
        List newParams = new ArrayList(1);
        Map headerMap = new HashMap();
        Object body = oldParams;
        Message message = null;
        MessageBody requestBody = context.getRequestMessageBody();

        // Legacy Packet Security
        List packetHeaders = context.getRequestMessage().getHeaders();
        packetCredentials(packetHeaders, headerMap);
        

        // Legacy Body
        if (oldParams.size() == 1)
        {
            Object obj = oldParams.get(0);

            if (obj != null && obj instanceof ASObject)
            {
                ASObject aso = (ASObject)obj;

                // Unwrap legacy Flex 1.5 Envelope type
                if (isEnvelope(aso))
                {
                    body = aso.get("data");

                    // Envelope level headers
                    Object h = aso.get("headers");
                    if (h != null && h instanceof List)
                    {
                        readEnvelopeHeaders((List)h, headerMap);
                        envelopeCredentials(headerMap);
                    }
                }
            }
        }

        // Convert legacy body into a RemotingMessage
        message = createMessage(requestBody, body, headerMap);
        newParams.add(message);
        return newParams;
    }

    private boolean isEnvelope(ASObject aso)
    {
        String flag = null;
        Object f = aso.get(LEGACY_ENVELOPE_FLAG_KEY);
        if (f != null && f instanceof String)
            flag = (String)f;

        if (flag != null && flag.equalsIgnoreCase(LEGACY_ENVELOPE_FLAG_VALUE))
        {
            return true;
        }

        return false;
    }


    private RemotingMessage createMessage(MessageBody messageBody, Object body, Map headerMap)
    {
        RemotingMessage remotingMessage = new RemotingMessage();
        // Assigning an empty String, MessageBroker expects non-null messageId.        
        remotingMessage.setMessageId("");  
        remotingMessage.setBody(body);
        remotingMessage.setHeaders(headerMap);

        // Decode legacy target URI into destination.operation
        String targetURI = messageBody.getTargetURI();

        int dotIndex = targetURI.lastIndexOf(".");
        if (dotIndex > 0)
        {
            String destination = targetURI.substring(0, dotIndex);
            remotingMessage.setDestination(destination);
        }

        if (targetURI.length() > dotIndex)
        {
            String operation = targetURI.substring(dotIndex + 1);
            remotingMessage.setOperation(operation);
        }

        return remotingMessage;
    }


    private Map readEnvelopeHeaders(List headers, Map headerMap)
    {
        int count = headers.size();

        for (int i = 0; i < count; i++)
        {
            Object obj = headers.get(i);

            //We currently expect a plain old AS Array
            if (obj != null && obj instanceof List)
            {
                List h = (List)obj;

                Object name = null;
                //Object mustUnderstand = null;
                Object data = null;

                int numFields = h.size();

                //The array must have exactly three (3) fields
                if (numFields == 3)
                {
                    name = h.get(0);

                    if (name != null && name instanceof String)
                    {
                        //mustUnderstand = h.get(1);
                        data = h.get(2);
                        headerMap.put(name, data);
                    }
                }
            }
        }

        return headerMap;
    }

    private void envelopeCredentials(Map headers)
    {
        // Process Legacy Security Credentials
        Object obj = headers.get(LEGACY_SECURITY_HEADER_NAME);
        if (obj != null && obj instanceof ASObject)
        {
            ASObject header = (ASObject)obj;
            String principal = (String)header.get(LEGACY_SECURITY_PRINCIPAL);
            Object credentials = header.get(LEGACY_SECURITY_CREDENTIALS);
            endpoint.getMessageBroker().getLoginManager().login(principal, credentials.toString());
        }
        headers.remove(LEGACY_SECURITY_HEADER_NAME);
    }

    private void packetCredentials(List packetHeaders, Map headers)
    {
        if (packetHeaders.size() > 0)
        {
            for (Iterator iter = packetHeaders.iterator(); iter.hasNext();)
            {
                MessageHeader header = (MessageHeader)iter.next();
                if (header.getName().equals(LEGACY_SECURITY_HEADER_NAME))
                {
                    Map loginInfo = (Map)header.getData();
                    String principal = loginInfo.get(LEGACY_SECURITY_PRINCIPAL).toString();
                    Object credentials = loginInfo.get(LEGACY_SECURITY_CREDENTIALS);
                    endpoint.getMessageBroker().getLoginManager().login(principal, credentials.toString());
                    break;
                }
            }
        }
    }
}
