package com.fasterxml.jackson.databind.type;

import java.lang.reflect.Array;

import com.fasterxml.jackson.databind.JavaType;

/**
 * Array types represent Java arrays, both primitive and object valued.
 * Further, Object-valued arrays can have element type of any other
 * legal {@link JavaType}.
 */
public final class ArrayType
    extends TypeBase
{
    private static final long serialVersionUID = 1L;

    /**
     * Type of elements in the array.
     */
    protected final JavaType _componentType;

    /**
     * We will also keep track of shareable instance of empty array,
     * since it usually needs to be constructed any way; and because
     * it is essentially immutable and thus can be shared.
     */
    protected final Object _emptyArray;

    protected ArrayType(JavaType componentType, Object emptyInstance,
            Object valueHandler, Object typeHandler, boolean asStatic)
    {
        super(emptyInstance.getClass(), componentType.hashCode(),
                valueHandler, typeHandler, asStatic);
        _componentType = componentType;
        _emptyArray = emptyInstance;
    }

    public static ArrayType construct(JavaType componentType,
            Object valueHandler, Object typeHandler)
    {
        /* This is bit messy: there is apparently no other way to
         * reconstruct actual concrete/raw array class from component
         * type, than to construct an instance, get class (same is
         * true for GenericArracyType as well; hence we won't bother
         * passing that in).
         */
        Object emptyInstance = Array.newInstance(componentType.getRawClass(), 0);
        return new ArrayType(componentType, emptyInstance, null, null, false);
    }                                   
    
    @Override
    public ArrayType withTypeHandler(Object h)
    {
        if (h == _typeHandler) {
            return this;
        }
        return new ArrayType(_componentType, _emptyArray, _valueHandler, h, _asStatic);
    }

    @Override
    public ArrayType withContentTypeHandler(Object h)
    {
        if (h == _componentType.<Object>getTypeHandler()) {
            return this;
        }
        return new ArrayType(_componentType.withTypeHandler(h), _emptyArray,
                _valueHandler, _typeHandler, _asStatic);
    }

    @Override
    public ArrayType withValueHandler(Object h) {
        if (h == _valueHandler) {
            return this;
        }
        return new ArrayType(_componentType, _emptyArray, h, _typeHandler,_asStatic);
    }

    @Override
    public ArrayType withContentValueHandler(Object h) {
        if (h == _componentType.<Object>getValueHandler()) {
            return this;
        }
        return new ArrayType(_componentType.withValueHandler(h), _emptyArray,
                _valueHandler, _typeHandler, _asStatic);
    }

    @Override
    public ArrayType withStaticTyping() {
        if (_asStatic) {
            return this;
        }
        return new ArrayType(_componentType.withStaticTyping(),
                _emptyArray, _valueHandler, _typeHandler, true);
    }

    @Override
    protected String buildCanonicalName() {
        return _class.getName();
    }

    /*
    /**********************************************************
    /* Methods for narrowing conversions
    /**********************************************************
     */

    /**
     * Handling of narrowing conversions for arrays is trickier: for now,
     * it is not even allowed.
     */
    @Override
    protected JavaType _narrow(Class<?> subclass)
    {
        /* Ok: need a bit of indirection here. First, must replace component
         * type (and check that it is compatible), then re-construct.
         */
        if (!subclass.isArray()) { // sanity check, should never occur
            throw new IllegalArgumentException("Incompatible narrowing operation: trying to narrow "+toString()+" to class "+subclass.getName());
        }
        /* Hmmh. This is an awkward back reference... but seems like the
         * only simple way to do it.
         */
        Class<?> newCompClass = subclass.getComponentType();
        /* 14-Mar-2011, tatu: it gets even worse, as we do not have access to
         *   currently configured TypeFactory. This could theoretically cause
         *   problems (when narrowing from array of Objects, to array of non-standard
         *   Maps, for example); but for now need to defer solving this until
         *   it actually becomes a real problem, not just potential one.
         *   (famous last words?)
         */
        JavaType newCompType = TypeFactory.defaultInstance().constructType(newCompClass);
        return construct(newCompType, _valueHandler, _typeHandler);
    }

    /**
     * For array types, both main type and content type can be modified;
     * but ultimately they are interchangeable.
     */
    @Override
    public JavaType narrowContentsBy(Class<?> contentClass)
    {
        // Can do a quick check first:
        if (contentClass == _componentType.getRawClass()) {
            return this;
        }
        return construct(_componentType.narrowBy(contentClass),
                _valueHandler, _typeHandler);
    }

    @Override
    public JavaType widenContentsBy(Class<?> contentClass)
    {
        // Can do a quick check first:
        if (contentClass == _componentType.getRawClass()) {
            return this;
        }
        return construct(_componentType.widenBy(contentClass),
                _valueHandler, _typeHandler);
    }
    
    /*
    /**********************************************************
    /* Overridden methods
    /**********************************************************
     */

    @Override
    public boolean isArrayType() { return true; }
    
    /**
     * For some odd reason, modifiers for array classes would
     * claim they are abstract types. Not so, at least for our
     * purposes.
     */
    @Override
    public boolean isAbstract() { return false; }

    /**
     * For some odd reason, modifiers for array classes would
     * claim they are abstract types. Not so, at least for our
     * purposes.
     */
    @Override
    public boolean isConcrete() { return true; }

    @Override
    public boolean hasGenericTypes() {
        // arrays are not parameterized, but element type may be:
        return _componentType.hasGenericTypes();
    }
    
    /**
     * Not sure what symbolic name is used internally, if any;
     * let's follow naming of Collection types here.
     * Should not really matter since array types have no
     * super types.
     */
    @Override
    public String containedTypeName(int index) {
        if (index == 0) return "E";
        return null;
    }

    /**
     * No parameterization for array types themselves; element type
     * may obviously have parameterization.
     */
    @Override
    public Class<?> getParameterSource() {
        return null;
    }
    
    /*
    /**********************************************************
    /* Public API
    /**********************************************************
     */

    @Override
    public boolean isContainerType() { return true; }

    @Override
    public JavaType getContentType() { return  _componentType; }

    @Override
    public int containedTypeCount() { return 1; }
    @Override
    public JavaType containedType(int index) {
            return (index == 0) ? _componentType : null;
    }
    
    @Override
    public StringBuilder getGenericSignature(StringBuilder sb) {
        sb.append('[');
        return _componentType.getGenericSignature(sb);
    }

    @Override
    public StringBuilder getErasedSignature(StringBuilder sb) {
        sb.append('[');
        return _componentType.getErasedSignature(sb);
    }
    
    /*
    /**********************************************************
    /* Standard methods
    /**********************************************************
     */

    @Override
    public String toString()
    {
        return "[array type, component type: "+_componentType+"]";
    }

    @Override
    public boolean equals(Object o)
    {
        if (o == this) return true;
        if (o == null) return false;
        if (o.getClass() != getClass()) return false;

        ArrayType other = (ArrayType) o;
        return _componentType.equals(other._componentType);
    }
}
