package com.fasterxml.jackson.databind.type;

import java.util.*;

import com.fasterxml.jackson.databind.JavaType;

/**
 * Simple types are defined as anything other than one of recognized
 * container types (arrays, Collections, Maps). For our needs we
 * need not know anything further, since we have no way of dealing
 * with generic types other than Collections and Maps.
 */
public class SimpleType // note: until 2.6 was final
    extends TypeBase
{
    private static final long serialVersionUID = 1L;

    /*
    /**********************************************************
    /* Life-cycle
    /**********************************************************
     */

    /**
     * Constructor only used by core Jackson databind functionality;
     * should never be called by application code.
     *<p>
     * As with other direct construction that by-passes {@link TypeFactory},
     * no introspection occurs with respect to super-types; caller must be
     * aware of consequences if using this method.
     */
    protected SimpleType(Class<?> cls) {
        this(cls, TypeBindings.emptyBindings(), null, null);
    }

    protected SimpleType(Class<?> cls, TypeBindings bindings,
            JavaType superClass, JavaType[] superInts) {
        this(cls, bindings, superClass, superInts, null, null, false);
    }

    /**
     * Simple copy-constructor, usually used when upgrading/refining a simple type
     * into more specialized type.
     *
     * @since 2.7
     */
    protected SimpleType(TypeBase base) {
        super(base);
    }

    protected SimpleType(Class<?> cls, TypeBindings bindings,
            JavaType superClass, JavaType[] superInts,
            Object valueHandler, Object typeHandler, boolean asStatic)
    {
        super(cls, bindings, superClass, superInts,
                0, valueHandler, typeHandler, asStatic);
    }

    /**
     * Pass-through constructor used by {@link ReferenceType}.
     * 
     * @since 2.6
     */
    protected SimpleType(Class<?> cls, TypeBindings bindings,
            JavaType superClass, JavaType[] superInts, int extraHash,
            Object valueHandler, Object typeHandler, boolean asStatic)
    {
        super(cls, bindings, superClass, superInts, 
                extraHash, valueHandler, typeHandler, asStatic);
    }
    
    /**
     * Method used by core Jackson classes: NOT to be used by application code:
     * it does NOT properly handle inspection of super-types, so neither parent
     * Classes nor implemented Interfaces are accessible with resulting type
     * instance.
     *<p>
     * NOTE: public only because it is called by <code>ObjectMapper</code> which is
     * not in same package
     */
    public static SimpleType constructUnsafe(Class<?> raw) {
        return new SimpleType(raw, null,
                // 18-Oct-2015, tatu: Should be ok to omit possible super-types, right?
                null, null, null, null, false);
    }

    /**
     * Method that should NOT to be used by application code:
     * it does NOT properly handle inspection of super-types, so neither parent
     * Classes nor implemented Interfaces are accessible with resulting type
     * instance. Instead, please use {@link TypeFactory}'s <code>constructType</code>
     * methods which handle introspection appropriately.
     *<p>
     * Note that prior to 2.7, method usage was not limited and would typically
     * have worked acceptably: the problem comes from inability to resolve super-type
     * information, for which {@link TypeFactory} is needed.
     * 
     * @deprecated Since 2.7
     */
    @Deprecated
    public static SimpleType construct(Class<?> cls)
    {
        /* Let's add sanity checks, just to ensure no
         * Map/Collection entries are constructed
         */
        if (Map.class.isAssignableFrom(cls)) {
            throw new IllegalArgumentException("Can not construct SimpleType for a Map (class: "+cls.getName()+")");
        }
        if (Collection.class.isAssignableFrom(cls)) {
            throw new IllegalArgumentException("Can not construct SimpleType for a Collection (class: "+cls.getName()+")");
        }
        // ... and while we are at it, not array types either
        if (cls.isArray()) {
            throw new IllegalArgumentException("Can not construct SimpleType for an array (class: "+cls.getName()+")");
        }
        TypeBindings b = TypeBindings.emptyBindings();
        return new SimpleType(cls, b,
                _buildSuperClass(cls.getSuperclass(), b), null, null, null, false);
    }

    @Override
    @Deprecated
    protected JavaType _narrow(Class<?> subclass)
    {
        if (_class == subclass) {
            return this;
        }
        // Should we check that there is a sub-class relationship?
        // 15-Jan-2016, tatu: Almost yes, but there are some complications with
        //    placeholder values (`Void`, `NoClass`), so can not quite do yet.
        // TODO: fix in 2.8
        if (!_class.isAssignableFrom(subclass)) {
            /*
            throw new IllegalArgumentException("Class "+subclass.getName()+" not sub-type of "
                    +_class.getName());
                    */
            return new SimpleType(subclass, _bindings, this, _superInterfaces,
                    _valueHandler, _typeHandler, _asStatic);
        }
        // Otherwise, stitch together the hierarchy. First, super-class
        Class<?> next = subclass.getSuperclass();
        if (next == _class) { // straight up parent class? Great.
            return new SimpleType(subclass, _bindings, this,
                    _superInterfaces, _valueHandler, _typeHandler, _asStatic);
        }
        if ((next != null) && _class.isAssignableFrom(next)) {
            JavaType superb = _narrow(next);
            return new SimpleType(subclass, _bindings, superb,
                    null, _valueHandler, _typeHandler, _asStatic);
        }
        // if not found, try a super-interface
        Class<?>[] nextI = subclass.getInterfaces();
        for (Class<?> iface : nextI) {
            if (iface == _class) { // directly implemented
                return new SimpleType(subclass, _bindings, null,
                        new JavaType[] { this }, _valueHandler, _typeHandler, _asStatic);
            }
            if (_class.isAssignableFrom(iface)) { // indirect, so recurse
                JavaType superb = _narrow(iface);
                return new SimpleType(subclass, _bindings, null,
                        new JavaType[] { superb }, _valueHandler, _typeHandler, _asStatic);
            }
        }
        // should not get here but...
        throw new IllegalArgumentException("Internal error: Can not resolve sub-type for Class "+subclass.getName()+" to "
                +_class.getName());
    }
    
    @Override
    public JavaType withContentType(JavaType contentType) {
        throw new IllegalArgumentException("Simple types have no content types; can not call withContentType()");
    }
    
    @Override
    public SimpleType withTypeHandler(Object h) {
        if (_typeHandler == h) {
            return this;
        }
        return new SimpleType(_class, _bindings, _superClass, _superInterfaces, _valueHandler, h, _asStatic);
    }

    @Override
    public JavaType withContentTypeHandler(Object h) {
        // no content type, so:
        throw new IllegalArgumentException("Simple types have no content types; can not call withContenTypeHandler()");
    }

    @Override
    public SimpleType withValueHandler(Object h) {
        if (h == _valueHandler) {
            return this;
        }
        return new SimpleType(_class, _bindings, _superClass, _superInterfaces, h, _typeHandler, _asStatic);
    }
    
    @Override
    public  SimpleType withContentValueHandler(Object h) {
        // no content type, so:
        throw new IllegalArgumentException("Simple types have no content types; can not call withContenValueHandler()");
    }

    @Override
    public SimpleType withStaticTyping() {
        return _asStatic ? this : new SimpleType(_class, _bindings,
                _superClass, _superInterfaces, _valueHandler, _typeHandler, true);
    }

    @Override
    public JavaType refine(Class<?> rawType, TypeBindings bindings,
            JavaType superClass, JavaType[] superInterfaces) {
        // SimpleType means something not-specialized, so:
        return null;
    }
    
    @Override
    protected String buildCanonicalName()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(_class.getName());

        final int count = _bindings.size();
        if (count > 0) {
            sb.append('<');
            for (int i = 0; i < count; ++i) {
                JavaType t = containedType(i);
                if (i > 0) {
                    sb.append(',');
                }
                sb.append(t.toCanonical());
            }
            sb.append('>');
        }
        return sb.toString();
    }

    /*
    /**********************************************************
    /* Public API
    /**********************************************************
     */

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

    @Override
    public StringBuilder getErasedSignature(StringBuilder sb) {
        return _classSignature(_class, sb, true);
    }
    
    @Override
    public StringBuilder getGenericSignature(StringBuilder sb)
    {
        _classSignature(_class, sb, false);

        final int count = _bindings.size();
        if (count > 0) {
            sb.append('<');
            for (int i = 0; i < count; ++i) {
                sb = containedType(i).getGenericSignature(sb);
            }
            sb.append('>');
        }
        sb.append(';');
        return sb;
    }

    /*
    /**********************************************************
    /* Internal methods
    /**********************************************************
     */

    /**
     * Helper method we need to recursively build skeletal representations
     * of superclasses.
     * 
     * @since 2.7 -- remove when not needed (2.8?)
     */
    private static JavaType _buildSuperClass(Class<?> superClass, TypeBindings b)
    {
        if (superClass == null) {
            return null;
        }
        if (superClass == Object.class) {
            return TypeFactory.unknownType();
        }
        JavaType superSuper = _buildSuperClass(superClass.getSuperclass(), b);
        return new SimpleType(superClass, b,
                superSuper, null, null, null, false);
    }

    /*
    /**********************************************************
    /* Standard methods
    /**********************************************************
     */

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder(40);
        sb.append("[simple type, class ").append(buildCanonicalName()).append(']');
        return sb.toString();
    }

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

        SimpleType other = (SimpleType) o;

        // Classes must be identical... 
        if (other._class != this._class) return false;

        // And finally, generic bindings, if any
        TypeBindings b1 = _bindings;
        TypeBindings b2 = other._bindings;
        return b1.equals(b2);
    }
}
