package com.fasterxml.jackson.datatype.jsr310.deser;

import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.format.ResolverStyle;
import java.util.Locale;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Feature;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;

import com.fasterxml.jackson.core.JsonParser;

import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;

@SuppressWarnings("serial")
public abstract class JSR310DateTimeDeserializerBase<T>
    extends JSR310DeserializerBase<T>
    implements ContextualDeserializer
{
    protected final DateTimeFormatter _formatter;

    /**
     * Setting that indicates the {@Link JsonFormat.Shape} specified for this deserializer
     * as a {@link com.fasterxml.jackson.annotation.JsonFormat.Shape} annotation on
     * property or class, or due to per-type "config override", or from global settings:
     * If Shape is NUMBER_INT, the input value is considered to be epoch days. If not a
     * NUMBER_INT, and the deserializer was not specified with the leniency setting of true,
     * then an exception will be thrown.
     *
     * @since 2.11
     */
    protected final Shape _shape;

    protected JSR310DateTimeDeserializerBase(Class<T> supportedType, DateTimeFormatter f) {
        super(supportedType);
        _formatter = f;
        _shape = null;
    }

    /**
     * @since 2.11
     */
    public JSR310DateTimeDeserializerBase(Class<T> supportedType, DateTimeFormatter f, Boolean leniency) {
        super(supportedType, leniency);
        _formatter = f;
        _shape = null;
    }

    /**
     * @since 2.10
     */
    protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
            DateTimeFormatter f) {
        super(base);
        _formatter = f;
        _shape = base._shape;
    }
    
    /**
     * @since 2.10
     */
    protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
            Boolean leniency) {
        super(base, leniency);
        _formatter = base._formatter;
        _shape = base._shape;
    }

    /**
     * @since 2.11
     */
    protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
            Shape shape) {
        super(base);
        _formatter = base._formatter;
        _shape = shape;
    }

    /**
     * @since 2.16
     */
    protected JSR310DateTimeDeserializerBase(JSR310DateTimeDeserializerBase<T> base,
        Boolean leniency,
        DateTimeFormatter formatter,
        JsonFormat.Shape shape) {
        super(base, leniency);
        _formatter = formatter;
        _shape = shape;
    }

    protected abstract JSR310DateTimeDeserializerBase<T> withDateFormat(DateTimeFormatter dtf);

    /**
     * @since 2.10
     */
    @Override
    protected abstract JSR310DateTimeDeserializerBase<T> withLeniency(Boolean leniency);

    /**
     * The default implementation returns this, because shape is more likely applicable in case of the serialization,
     * usage during deserialization could cover only very specific cases.
     *
     * @since 2.11
     */
    protected JSR310DateTimeDeserializerBase<T> withShape(Shape shape) {
        return this;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException
    {
        JsonFormat.Value format = findFormatOverrides(ctxt, property, handledType());
        return (format == null) ? this : _withFormatOverrides(ctxt, property, format);
    }

    /**
     * @param ctxt Active deserialization context
     * @param property (optional) Property on which this deserializer is used, or {@code null}
     *     for root value
     * @param formatOverrides Format overrides to use (non-null)
     *
     * @return Either this deserializer as is, or newly constructed variant if created
     *    for different configuration
     *
     * @since 2.12.1
     */
    protected JSR310DateTimeDeserializerBase<?> _withFormatOverrides(DeserializationContext ctxt,
            BeanProperty property, JsonFormat.Value formatOverrides)
    {
        JSR310DateTimeDeserializerBase<?> deser = this;

        // 17-Aug-2019, tatu: For 2.10 let's start considering leniency/strictness too
        if (formatOverrides.hasLenient()) {
            Boolean leniency = formatOverrides.getLenient();
            if (leniency != null) {
                deser = deser.withLeniency(leniency);
            }
        }
        if (formatOverrides.hasPattern()) {
            final String pattern = formatOverrides.getPattern();
            final Locale locale = formatOverrides.hasLocale() ? formatOverrides.getLocale() : ctxt.getLocale();
            DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
            if (acceptCaseInsensitiveValues(ctxt, formatOverrides)) {
                builder.parseCaseInsensitive();
            }
            builder.appendPattern(pattern);
            DateTimeFormatter df;
            if (locale == null) {
                df = builder.toFormatter();
            } else {
                df = builder.toFormatter(locale);
            }

            // [#148]: allow strict parsing
            if (!deser.isLenient()) {
                df = df.withResolverStyle(ResolverStyle.STRICT);
            }

            // [#69]: For instant serializers/deserializers we need to configure the formatter with
            //a time zone picked up from JsonFormat annotation, otherwise serialization might not work
            if (formatOverrides.hasTimeZone()) {
                df = df.withZone(formatOverrides.getTimeZone().toZoneId());
            }
            deser = deser.withDateFormat(df);
        }
        // [#58]: For LocalDate deserializers we need to configure the formatter with
        //a shape picked up from JsonFormat annotation, to decide if the value is EpochSeconds
        JsonFormat.Shape shape = formatOverrides.getShape();
        if (shape != null && shape != _shape) {
            deser = deser.withShape(shape);
        }
        // any use for TimeZone?

        return deser;
    }
    
    private boolean acceptCaseInsensitiveValues(DeserializationContext ctxt, JsonFormat.Value format) 
    {
        Boolean enabled = format.getFeature(Feature.ACCEPT_CASE_INSENSITIVE_VALUES);
        if (enabled == null) {
            enabled = ctxt.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_VALUES);
        }
        return enabled;
    }
    
    protected void _throwNoNumericTimestampNeedTimeZone(JsonParser p, DeserializationContext ctxt)
        throws IOException
    {
        ctxt.reportInputMismatch(handledType(),
"raw timestamp (%d) not allowed for `%s`: need additional information such as an offset or time-zone (see class Javadocs)",
p.getNumberValue(), handledType().getName());
    }
}
