/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package flex.messaging.io;

import flex.messaging.FlexContext;
import flex.messaging.MessageBroker;
import flex.messaging.io.amf.ASObject;
import flex.messaging.io.amf.translator.ASTranslator;

import java.util.IdentityHashMap;
import java.util.Map;

/**
 * A simple context to hold type marshalling specific settings.
 */
public class TypeMarshallingContext {
    private static ThreadLocal<TypeMarshallingContext> contexts = new ThreadLocal<TypeMarshallingContext>();
    private static ThreadLocal<TypeMarshaller> marshallers = new ThreadLocal<TypeMarshaller>();
    private IdentityHashMap knownObjects;
    private ClassLoader classLoader;

    /**
     * Constructs a default type marshalling context.
     */
    public TypeMarshallingContext() {
    }

    /**
     * Establishes a TypeMarshallingContext for the current thread.
     * Users are not expected to call this function.
     *
     * @param context The current TypeMarshallingContext.
     */
    public static void setTypeMarshallingContext(TypeMarshallingContext context) {
        if (context == null)
            contexts.remove();
        else
            contexts.set(context);
    }

    /**
     * Get current TypeMarshallingContext object
     *
     * @return The current thread's TypeMarshallingContext.
     */
    public static TypeMarshallingContext getTypeMarshallingContext() {
        TypeMarshallingContext context = contexts.get();
        if (context == null) {
            context = new TypeMarshallingContext();
            TypeMarshallingContext.setTypeMarshallingContext(context);
        }
        return context;
    }

    /**
     * Establishes a TypeMarshallingContext for the current thread.
     * Users are not expected to call this function.
     *
     * @param marshaller The current TypeMarshaller.
     */
    public static void setTypeMarshaller(TypeMarshaller marshaller) {
        if (marshaller == null)
            marshallers.remove();
        else
            marshallers.set(marshaller);
    }

    /**
     * Get current TypeMarshaller.
     *
     * @return The current thread's TypeMarshaller.
     */
    public static TypeMarshaller getTypeMarshaller() {
        TypeMarshaller marshaller = marshallers.get();
        if (marshaller == null) {
            marshaller = new ASTranslator();
            setTypeMarshaller(marshaller);
        }

        return marshaller;
    }

    /**
     * Returns the custom ClassLoader for this type marshalling session, or
     * defaults to the current MessageBroker's ClassLoader if none has been set.
     *
     * @return ClassLoader the ClassLoader in use
     */
    public ClassLoader getClassLoader() {
        if (classLoader != null)
            return classLoader;

        try {
            MessageBroker messageBroker = FlexContext.getMessageBroker();
            return messageBroker != null ? messageBroker.getClassLoader() : null;
        } catch (NoClassDefFoundError exception) // Could happen in client mode.
        {
            return null;
        }
    }

    /**
     * Sets a custom classloader for this type marshalling session that will
     * be used to create new instances of strongly typed objects.
     *
     * @param loader the ClassLoader to use
     */
    public void setClassLoader(ClassLoader loader) {
        classLoader = loader;
    }

    /**
     * A map of known objects already encountered in this type marshalling
     * session.
     *
     * @return IdentityHashMap the known objects
     */
    public IdentityHashMap getKnownObjects() {
        if (knownObjects == null)
            knownObjects = new IdentityHashMap(64);

        return knownObjects;
    }

    /**
     * Sets the list of the objects already encountered for this type
     * marshalling session.
     *
     * @param knownObjects the IdentityHashMap object
     */
    public void setKnownObjects(IdentityHashMap knownObjects) {
        this.knownObjects = knownObjects;
    }

    /**
     * Resets the list of known objects.
     */
    public void reset() {
        if (knownObjects != null)
            knownObjects.clear();
    }

    /**
     * A utility method to determine whether an anonymous type specifies
     * a strong type name, such as ASObject.getType() or the legacy Flash
     * Remoting convention of using a _remoteClass property.
     *
     * @param obj the object to check
     * @return The name of the strong type, or null if none was specified.
     */
    public static String getType(Object obj) {
        String type = null;

        if (obj != null && obj instanceof Map) {
            Map map = (Map) obj;

            //Check for an Object.registerClass Typed ASObject
            if (map instanceof ASObject) {
                ASObject aso = (ASObject) map;
                type = aso.getType();
            }

            SerializationContext sc = SerializationContext.getSerializationContext();

            if (type == null && sc.supportRemoteClass) {
                Object registerClass = map.get(MessageIOConstants.REMOTE_CLASS_FIELD);
                if (registerClass != null && registerClass instanceof String) {
                    type = (String) registerClass;
                }
            }
        }

        return type;
    }

    /**
     * Clears out the thread local state after the request completes.
     */
    public static void clearThreadLocalObjects() {
        if (contexts != null) {
            contexts.remove();
        }
        if (marshallers != null) {
            marshallers.remove();
        }
    }

    /**
     * Destroy static thread local storage.
     * Call ONLY on shutdown.
     */
    public static void releaseThreadLocalObjects() {
        clearThreadLocalObjects();

        contexts = null;
        marshallers = null;
    }

    /**
     * Create static thread local storage.
     */
    public static void createThreadLocalObjects() {
        if (contexts == null)
            contexts = new ThreadLocal();
        if (marshallers == null)
            marshallers = new ThreadLocal();
    }
}
