package org.codehaus.jackson.map.ser.impl;

import org.codehaus.jackson.map.BeanProperty;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;
import org.codehaus.jackson.type.JavaType;

/**
 * Helper container used for resolving serializers for dynamic (possibly but not
 * necessarily polymorphic) properties: properties whose type is not forced
 * to use dynamic (declared) type and that are not final.
 * If so, serializer to use can only be established once actual value type is known.
 * Since this happens a lot unless static typing is forced (or types are final)
 * this implementation is optimized for efficiency.
 * Instances are immutable; new instances are created with factory methods: this
 * is important to ensure correct multi-threaded access.
 * 
 * @since 1.7
 */
public abstract class PropertySerializerMap
{
    /**
     * Main lookup method. Takes a "raw" type since usage is always from
     * place where parameterization is fixed such that there can not be
     * type-parametric variations.
     */
    public abstract JsonSerializer<Object> serializerFor(Class<?> type);

    /**
     * Method called if initial lookup fails; will both find serializer
     * and construct new map instance if warranted, and return both
     * @throws JsonMappingException 
     */
    public final SerializerAndMapResult findAndAddSerializer(Class<?> type,
            SerializerProvider provider, BeanProperty property)
        throws JsonMappingException
    {
        JsonSerializer<Object> serializer = provider.findValueSerializer(type, property);
        return new SerializerAndMapResult(serializer, newWith(type, serializer));
    }

    public final SerializerAndMapResult findAndAddSerializer(JavaType type,
            SerializerProvider provider, BeanProperty property)
        throws JsonMappingException
    {
        JsonSerializer<Object> serializer = provider.findValueSerializer(type, property);
        return new SerializerAndMapResult(serializer, newWith(type.getRawClass(), serializer));
    }

    public abstract PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer);
    
    public static PropertySerializerMap emptyMap() {
        return Empty.instance;
    }
    
    /*
    /**********************************************************
    /* Helper classes
    /**********************************************************
     */

    /**
     * Value class used for returning tuple that has both serializer
     * that was retrieved and new map instance
     */
    public final static class SerializerAndMapResult
    {
        public final JsonSerializer<Object> serializer;
        public final PropertySerializerMap map;
        
        public SerializerAndMapResult(JsonSerializer<Object> serializer,
                PropertySerializerMap map)
        {
            this.serializer = serializer;
            this.map = map;
        }
    }

    /**
     * Trivial container for bundling type + serializer entries.
     */
    private final static class TypeAndSerializer
    {
        public final Class<?> type;
        public final JsonSerializer<Object> serializer;

        public TypeAndSerializer(Class<?> type, JsonSerializer<Object> serializer) {
            this.type = type;
            this.serializer = serializer;
        }
    }

    /*
    /**********************************************************
    /* Implementations
    /**********************************************************
     */

    /**
     * Bogus instance that contains no serializers; used as the default
     * map with new serializers.
     */
    private final static class Empty extends PropertySerializerMap
    {
        protected final static Empty instance = new Empty();

        @Override
        public JsonSerializer<Object> serializerFor(Class<?> type) {
            return null; // empty, nothing to find
        }        

        @Override
        public PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer) {
            return new Single(type, serializer);
        }
    }

    /**
     * Map that contains a single serializer; although seemingly silly
     * this is probably the most commonly used variant because many
     * theoretically dynamic or polymorphic types just have single
     * actual type.
     */
    private final static class Single extends PropertySerializerMap
    {
        private final Class<?> _type;
        private final JsonSerializer<Object> _serializer;

        public Single(Class<?> type, JsonSerializer<Object> serializer) {
            _type = type;
            _serializer = serializer;
        }

        @Override
        public JsonSerializer<Object> serializerFor(Class<?> type)
        {
            if (type == _type) {
                return _serializer;
            }
            return null;
        }

        @Override
        public PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer) {
            return new Double(_type, _serializer, type, serializer);
        }
    }

    private final static class Double extends PropertySerializerMap
    {
        private final Class<?> _type1, _type2;
        private final JsonSerializer<Object> _serializer1, _serializer2;

        public Double(Class<?> type1, JsonSerializer<Object> serializer1,
                Class<?> type2, JsonSerializer<Object> serializer2)
        {
            _type1 = type1;
            _serializer1 = serializer1;
            _type2 = type2;
            _serializer2 = serializer2;
        }

        @Override
        public JsonSerializer<Object> serializerFor(Class<?> type)
        {
            if (type == _type1) {
                return _serializer1;
            }
            if (type == _type2) {
                return _serializer2;
            }
            return null;
        }        

        @Override
        public PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer) {
            // Ok: let's just create generic one
            TypeAndSerializer[] ts = new TypeAndSerializer[2];
            ts[0] = new TypeAndSerializer(_type1, _serializer1);
            ts[1] = new TypeAndSerializer(_type2, _serializer2);
            return new Multi(ts);
        }
    }
    
    private final static class Multi extends PropertySerializerMap
    {
        /**
         * Let's limit number of serializers we actually cache; linear
         * lookup won't scale too well beyond smallish number, and if
         * we really want to support larger collections should use
         * a hash map. But it seems unlikely this is a common use
         * case so for now let's just stop building after hard-coded
         * limit. 8 sounds like a reasonable stab for now.
         */
        private final static int MAX_ENTRIES = 8;
        
        private final TypeAndSerializer[] _entries;

        public Multi(TypeAndSerializer[] entries) {
            _entries = entries;
        }

        @Override
        public JsonSerializer<Object> serializerFor(Class<?> type)
        {
            for (int i = 0, len = _entries.length; i < len; ++i) {
                TypeAndSerializer entry = _entries[i];
                if (entry.type == type) {
                    return entry.serializer;
                }
            }
            return null;
        }

        @Override
        public PropertySerializerMap newWith(Class<?> type, JsonSerializer<Object> serializer)
        {
            int len = _entries.length;
            // Will only grow up to N entries
            if (len == MAX_ENTRIES) {
                return this;
            }
            // 1.6 has nice resize methods but we are still 1.5
            TypeAndSerializer[] entries = new TypeAndSerializer[len+1];
            System.arraycopy(_entries, 0, entries, 0, len);
            entries[len] = new TypeAndSerializer(type, serializer);
            return new Multi(entries);
        }
    }
}
