package ameba.message.jackson.internal;

import ameba.core.Application;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.message.filtering.spi.ObjectProvider;

import javax.inject.Provider;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * <p>JacksonUtils class.</p>
 *
 * @author icode
 * @since 0.1.6e
 */
public class JacksonUtils {

    private JacksonUtils() {
    }

    /**
     * <p>configFilterIntrospector.</p>
     *
     * @param mapper a {@link com.fasterxml.jackson.databind.ObjectMapper} object.
     * @return a {@link com.fasterxml.jackson.databind.ObjectMapper} object.
     */
    public static ObjectMapper configFilterIntrospector(final ObjectMapper mapper) {
        final AnnotationIntrospector customIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
        // Set the custom (user) introspector to be the primary one.
        return mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(customIntrospector,
                new JacksonAnnotationIntrospector() {
                    @Override
                    public Object findFilterId(final Annotated a) {
                        final Object filterId = super.findFilterId(a);

                        if (filterId != null) {
                            return filterId;
                        }

                        if (a instanceof AnnotatedMethod) {
                            final Method method = ((AnnotatedMethod) a).getAnnotated();

                            // Interested only in getters - trying to obtain "field" name from them.
                            if (ReflectionHelper.isGetter(method)) {
                                return ReflectionHelper.getPropertyName(method);
                            }
                        }
                        if (a instanceof AnnotatedField || a instanceof AnnotatedClass) {
                            return a.getName();
                        }

                        return null;
                    }
                }));

    }

    /**
     * <p>setObjectWriterInjector.</p>
     *
     * @param provider    a {@link javax.inject.Provider} object.
     * @param genericType a {@link java.lang.reflect.Type} object.
     * @param annotations an array of {@link java.lang.annotation.Annotation} objects.
     * @throws java.io.IOException if any.
     */
    public static void setObjectWriterInjector(Provider<ObjectProvider<FilterProvider>> provider,
                                               final Type genericType,
                                               final Annotation[] annotations) throws IOException {
        final FilterProvider filterProvider = provider.get().getFilteringObject(genericType, true, annotations);
        if (filterProvider != null) {
            ObjectWriterInjector.set(new FilteringObjectWriterModifier(filterProvider, ObjectWriterInjector.getAndClear()));
        }
    }

    /**
     * <p>configureMapper.</p>
     *
     * @param mapper a {@link com.fasterxml.jackson.databind.ObjectMapper} object.
     * @param mode   App mode
     */
    public static void configureMapper(ObjectMapper mapper, Application.Mode mode) {
        mapper.registerModule(new GuavaModule());
        mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
                .enable(SerializationFeature.WRITE_ENUMS_USING_INDEX)
                .enable(MapperFeature.PROPAGATE_TRANSIENT_MARKER)
                .disable(
                        SerializationFeature.WRITE_NULL_MAP_VALUES,
                        SerializationFeature.FAIL_ON_EMPTY_BEANS
                );
        if (!mode.isDev()) {
            mapper.disable(
                    DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
            );
        }
    }

    /**
     * <p>configureGenerator.</p>
     *
     * @param uriInfo   a {@link javax.ws.rs.core.UriInfo} object.
     * @param generator a {@link com.fasterxml.jackson.core.JsonGenerator} object.
     * @param isDev     a boolean.
     */
    public static void configureGenerator(UriInfo uriInfo, JsonGenerator generator, boolean isDev) {
        MultivaluedMap<String, String> params = uriInfo.getQueryParameters();
        String pretty = params.getFirst("pretty");
        if ("false".equalsIgnoreCase(pretty)) {
            generator.setPrettyPrinter(null);
        } else if (pretty != null && !"false".equalsIgnoreCase(pretty) || isDev) {
            generator.useDefaultPrettyPrinter();
        }
        String unicode = params.getFirst("unicode");
        if (unicode != null && !"false".equalsIgnoreCase(unicode)) {
            generator.enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);
        }
    }
}