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

import java.io.IOException;

import org.codehaus.jackson.*;
import org.codehaus.jackson.annotate.JsonTypeInfo.As;
import org.codehaus.jackson.map.*;
import org.codehaus.jackson.map.jsontype.TypeIdResolver;
import org.codehaus.jackson.type.JavaType;

/**
 * Type deserializer used with {@link As#WRAPPER_ARRAY}
 * inclusion mechanism. Simple since JSON structure used is always
 * the same, regardless of structure used for actual value: wrapping
 * is done using a 2-element JSON Array where type id is the first
 * element, and actual object data as second element.
 * 
 * @author tatus
 */
public class AsArrayTypeDeserializer extends TypeDeserializerBase
{
    @Deprecated // since 1.9
    public AsArrayTypeDeserializer(JavaType bt, TypeIdResolver idRes, BeanProperty property) {
        this(bt, idRes, property, null);
    }

    public AsArrayTypeDeserializer(JavaType bt, TypeIdResolver idRes, BeanProperty property,
            Class<?> defaultImpl)
    {
        super(bt, idRes, property, defaultImpl);
    }
    
    @Override
    public As getTypeInclusion() {
        return As.WRAPPER_ARRAY;
    }

    /**
     * Method called when actual object is serialized as JSON Array.
     */
    @Override
    public Object deserializeTypedFromArray(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        return _deserialize(jp, ctxt);
    }

    /**
     * Method called when actual object is serialized as JSON Object
     */
    @Override
    public Object deserializeTypedFromObject(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        return _deserialize(jp, ctxt);
    }
    
    @Override
    public Object deserializeTypedFromScalar(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        return _deserialize(jp, ctxt);
    }    

    @Override
    public Object deserializeTypedFromAny(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        return _deserialize(jp, ctxt);
    }    
    
    /*
    /***************************************************************
    /* Internal methods
    /***************************************************************
     */

    /**
     * Method that handles type information wrapper, locates actual
     * subtype deserializer to use, and calls it to do actual
     * deserialization.
     */
    private final Object _deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        boolean hadStartArray = jp.isExpectedStartArrayToken();
        JsonDeserializer<Object> deser = _findDeserializer(ctxt, _locateTypeId(jp, ctxt));
        Object value = deser.deserialize(jp, ctxt);
        // And then need the closing END_ARRAY
        if (hadStartArray && jp.nextToken() != JsonToken.END_ARRAY) {
            throw ctxt.wrongTokenException(jp, JsonToken.END_ARRAY,
                    "expected closing END_ARRAY after type information and deserialized value");
        }
        return value;
    }    
    
    protected final String _locateTypeId(JsonParser jp, DeserializationContext ctxt)
        throws IOException, JsonProcessingException
    {
        if (!jp.isExpectedStartArrayToken()) {
            // Add instanceof check because we cannot add method to TypeIdResolver interface due to backwards
            // compatibility issues.  We can however add it to the base method, which gets at least some of
            // the functionality we want.  Falls back to old exception for other types of TypeIdResolvers
            if (_idResolver instanceof TypeIdResolverBase) {
                if (_defaultImpl != null) { // but let's require existence of default impl, as a safeguard
                    return ((TypeIdResolverBase) _idResolver).idFromBaseType();
                }
            }
            throw ctxt.wrongTokenException(jp, JsonToken.START_ARRAY,
                    "need JSON Array to contain As.WRAPPER_ARRAY type information for class "+baseTypeName());
        }
        // And then type id as a String
        if (jp.nextToken() != JsonToken.VALUE_STRING) {
            if (_idResolver instanceof TypeIdResolverBase) {
                if (_defaultImpl != null) { // but let's require existence of default impl, as a safeguard
                    return ((TypeIdResolverBase) _idResolver).idFromBaseType();
                }
            }
            throw ctxt.wrongTokenException(jp, JsonToken.VALUE_STRING,
                    "need JSON String that contains type id (for subtype of "+baseTypeName()+")");
        }
        String result = jp.getText();
        jp.nextToken();
        return result;
    }
}
