package com.fasterxml.jackson.module.afterburner.deser;

import java.io.IOException;
import java.util.*;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.SerializedString;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.*;
import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap;
import com.fasterxml.jackson.databind.deser.impl.UnwrappedPropertyHandler;
import com.fasterxml.jackson.databind.util.NameTransformer;

/**
 * Optimized variant used for Beans with 1 to 6 properties; if so,
 * handling is unrolled to eliminate looping.
 */
public final class SuperSonicUnrolledDeserializer
    extends SuperSonicBDBase
{
    protected final int _propCount;

    // // // We store separate references in form more easily accessed
    // // // from switch statement
    
    protected SerializedString _name1;
    protected SettableBeanProperty _prop1;

    protected SerializedString _name2;
    protected SettableBeanProperty _prop2;

    protected SerializedString _name3;
    protected SettableBeanProperty _prop3;

    protected SerializedString _name4;
    protected SettableBeanProperty _prop4;

    protected SerializedString _name5;
    protected SettableBeanProperty _prop5;

    protected SerializedString _name6;
    protected SettableBeanProperty _prop6;
    
    /*
    /**********************************************************
    /* Life-cycle, construction, initialization
    /**********************************************************
     */

    public SuperSonicUnrolledDeserializer(BeanDeserializer src, List<SettableBeanProperty> props)
    {
        super(src, props);
        _propCount = props.size();
    }

    protected SuperSonicUnrolledDeserializer(SuperSonicUnrolledDeserializer src,
            UnwrappedPropertyHandler unwrapHandler, BeanPropertyMap renamedProperties,
            boolean ignoreAllUnknown)
    {
        super(src, unwrapHandler, renamedProperties, ignoreAllUnknown);
        _propCount = src._propCount;

        _prop1 = src._prop1;
        _name1 = src._name1;
        _prop2 = src._prop2;
        _name2 = src._name2;
        _prop3 = src._prop3;
        _name3 = src._name3;
        _prop4 = src._prop4;
        _name4 = src._name4;
        _prop5 = src._prop5;
        _name5 = src._name5;
        _prop6 = src._prop6;
        _name6 = src._name6;
    }

    @Override
    public JsonDeserializer<Object> unwrappingDeserializer(DeserializationContext ctxt,
            NameTransformer transformer)
    {
        // NOTE: copied verbatim from `BeanDeserializer`

        if (_currentlyTransforming == transformer) { // from [databind#383]
            return this;
        }
        _currentlyTransforming = transformer;
        try {
            UnwrappedPropertyHandler uwHandler = _unwrappedPropertyHandler;
            if (uwHandler != null) {
                uwHandler = uwHandler.renameAll(ctxt, transformer);
            }
            return new SuperSonicUnrolledDeserializer(this, uwHandler,
                    _beanProperties.renameAll(ctxt, transformer), true);
        } finally { _currentlyTransforming = null; }
    }

    @Override
    public void resolve(DeserializationContext ctxt)
        throws JsonMappingException
    {
        super.resolve(ctxt);
        // 19-Oct-2017, tatu: Not sure why but seems to occur sometimes...
        if (_orderedProperties != null) {
            SettableBeanProperty[] oProps = new SettableBeanProperty[6];
            SerializedString[] oNames = new SerializedString[6];
            int offset = 6 - _propCount;
            System.arraycopy(_orderedProperties, 0, oProps, offset, _propCount);
            System.arraycopy(_orderedPropertyNames, 0, oNames, offset, _propCount);
            
            _prop1 = oProps[0];
            _name1 = oNames[0];
            _prop2 = oProps[1];
            _name2 = oNames[1];
            _prop3 = oProps[2];
            _name3 = oNames[2];
            _prop4 = oProps[3];
            _name4 = oNames[3];
            _prop5 = oProps[4];
            _name5 = oNames[4];
            _prop6 = oProps[5];
            _name6 = oNames[5];
        }
    }

    // // Others, let's just leave as is; will not be optimized?
    
    //public BeanDeserializer withObjectIdReader(ObjectIdReader oir) {

    //public BeanDeserializer withIgnorableProperties(HashSet<String> ignorableProps)
    
    //protected BeanDeserializerBase asArrayDeserializer()

    /*
    /**********************************************************
    /* Deserialization method implementations
    /**********************************************************
     */

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        if (!_vanillaProcessing || (_objectIdReader != null)) {
            // should we ever get here? Just in case
            return super.deserialize(p, ctxt);
        }
        // common case first:
        if (!p.isExpectedStartObjectToken()) {
            return _deserializeOther(p, ctxt, p.currentToken());
        }
        if (_nonStandardCreation) {
            p.nextToken();
            if (_unwrappedPropertyHandler != null) {
                return deserializeWithUnwrapped(p, ctxt);
            }
            if (_externalTypeIdHandler != null) {
                return deserializeWithExternalTypeId(p, ctxt);
            }
            Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
            if (_injectables != null) {
                injectValues(ctxt, bean);
            }
            return bean;
        }
        final Object bean = _valueInstantiator.createUsingDefault(ctxt);
        // [databind#631]: Assign current value, to be accessible by custom serializers
        p.setCurrentValue(bean);
        if (p.canReadObjectId()) {
            Object id = p.getObjectId();
            if (id != null) {
                _handleTypedObjectId(p, ctxt, bean, id);
            }
        }
        if (_injectables != null) {
            injectValues(ctxt, bean);
        }
        SettableBeanProperty prop = null;

        try {
            switch (_propCount) {
            default:
            //case 6:
                prop = _prop1;
                if (!p.nextFieldName(_name1)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 5:
                prop = _prop2;
                if (!p.nextFieldName(_name2)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 4:
                prop = _prop3;
                if (!p.nextFieldName(_name3)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 3:
                prop = _prop4;
                if (!p.nextFieldName(_name4)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 2:
                prop = _prop5;
                if (!p.nextFieldName(_name5)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 1:
                prop = _prop6;
                if (!p.nextFieldName(_name6)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // also, need to ensure we get closing END_OBJECT...
                if (p.nextToken() == JsonToken.END_OBJECT) {
                    return bean;
                }
            }
        } catch (Exception e) {
            wrapAndThrow(e, bean, prop.getName(), ctxt);
        }
        // may have encountered pre-mature END_OBJECT too
        if (p.currentToken() == JsonToken.END_OBJECT) {
            return bean;
        }
        return _deserializeDisordered(p, ctxt, bean);
    }

    // much of below is cut'n pasted from BeanSerializer
    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt,
            Object bean) throws IOException
    {
        // [databind#631]: Assign current value, to be accessible by custom serializers
        p.setCurrentValue(bean);
        if (_injectables != null) {
            injectValues(ctxt, bean);
        }
        if (_unwrappedPropertyHandler != null) {
            return deserializeWithUnwrapped(p, ctxt, bean);
        }
        if (_externalTypeIdHandler != null) {
            return deserializeWithExternalTypeId(p, ctxt, bean);
        }
        SettableBeanProperty prop = _orderedProperties[0];
        // First: verify that first name is expected
        if (p.isExpectedStartObjectToken()) {
            if (!p.nextFieldName(_orderedPropertyNames[0])) {
                return super.deserialize(p,  ctxt, bean);
            }
        } else if (!p.hasToken(JsonToken.FIELD_NAME)
                || !prop.getName().equals(p.currentName())) {
            // no, something funky, use base impl for special cases
            return super.deserialize(p,  ctxt, bean);
        }
        p.nextToken();
        try {
            prop.deserializeAndSet(p, ctxt, bean);
        } catch (Exception e) {
            wrapAndThrow(e, bean, prop.getName(), ctxt);
        }
        // then rest of properties. NOTE! Off-by-one, to skip one we did above
        try {
            switch (_propCount) {
            default:
            //case 6:
                prop = _prop2;
                if (!p.nextFieldName(_name2)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 5:
                prop = _prop3;
                if (!p.nextFieldName(_name3)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 4:
                prop = _prop4;
                if (!p.nextFieldName(_name4)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 3:
                prop = _prop5;
                if (!p.nextFieldName(_name5)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 2:
                prop = _prop6;
                if (!p.nextFieldName(_name6)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 1:
                // NOTE! skip "last" one to compensate for OBO
                // But we still need to ensure we get closing END_OBJECT...
                if (p.nextToken() == JsonToken.END_OBJECT) {
                    return bean;
                }
            }
        } catch (Exception e) {
            wrapAndThrow(e, bean, prop.getName(), ctxt);
        }
        // may have encountered pre-mature END_OBJECT too
        if (p.currentToken() == JsonToken.END_OBJECT) {
            return bean;
        }
        return _deserializeDisordered(p, ctxt, bean);
    }

    // much of below is cut'n pasted from BeanSerializer
    @Override
    public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
    {
        // See BeanDeserializer.deserializeFromObject [databind#622]
        // Allow Object Id references to come in as JSON Objects as well...
        if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
            if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
                    && _objectIdReader.isValidReferencePropertyName(p.currentName(), p)) {
                return deserializeFromObjectId(p, ctxt);
            }
        }
        if (_nonStandardCreation) {
            if (_unwrappedPropertyHandler != null) {
                return deserializeWithUnwrapped(p, ctxt);
            }
            if (_externalTypeIdHandler != null) {
                return deserializeWithExternalTypeId(p, ctxt);
            }
            Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
            if (_injectables != null) {
                injectValues(ctxt, bean);
            }
            return bean;
        }
        final Object bean = _valueInstantiator.createUsingDefault(ctxt);
        // [databind#631]: Assign current value, to be accessible by custom serializers
        p.setCurrentValue(bean);
        if (p.canReadObjectId()) {
            Object id = p.getObjectId();
            if (id != null) {
                _handleTypedObjectId(p, ctxt, bean, id);
            }
        }
        if (_injectables != null) {
            injectValues(ctxt, bean);
        }
        SettableBeanProperty prop = _orderedProperties[0];
        // First: verify that first name is expected
        if (p.isExpectedStartObjectToken()) {
            if (!p.nextFieldName(_orderedPropertyNames[0])) {
                return _deserializeDisordered(p, ctxt, bean);
            }
        } else if (!p.hasToken(JsonToken.FIELD_NAME)
                || !prop.getName().equals(p.currentName())) {
            return _deserializeDisordered(p, ctxt, bean);
        }
        // and deserialize
        p.nextToken();
        try {
            prop.deserializeAndSet(p, ctxt, bean);
        } catch (Exception e) {
            wrapAndThrow(e, bean, prop.getName(), ctxt);
        }

        // then rest of properties. NOTE! Off-by-one, to skip one we did above
        try {
            switch (_propCount) {
            default:
            //case 6:
                prop = _prop2;
                if (!p.nextFieldName(_name2)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 5:
                prop = _prop3;
                if (!p.nextFieldName(_name3)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 4:
                prop = _prop4;
                if (!p.nextFieldName(_name4)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 3:
                prop = _prop5;
                if (!p.nextFieldName(_name5)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 2:
                prop = _prop6;
                if (!p.nextFieldName(_name6)) {
                    break;
                }
                p.nextToken();
                prop.deserializeAndSet(p, ctxt, bean);
                // fall through
            case 1:
                // NOTE! skip "last" one to compensate for OBO
                // But we still need to ensure we get closing END_OBJECT...
                if (p.nextToken() == JsonToken.END_OBJECT) {
                    return bean;
                }
            }
        } catch (Exception e) {
            wrapAndThrow(e, bean, prop.getName(), ctxt);
        }
        // may have encountered pre-mature END_OBJECT too
        if (p.currentToken() == JsonToken.END_OBJECT) {
            return bean;
        }
        return _deserializeDisordered(p, ctxt, bean);
    }
}