package com.kmall.common.utils;


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.Locale;

/**
 * JacksonUtils 工具类
 * @author Scott Chen
 * @version 1.0
 * 2017-09-18 18:42
 */
public class JacksonUtils {

    private static final Logger logger = LoggerFactory.getLogger(JacksonUtils.class);

    private static ObjectMapper objectMapper;

    private static final ThreadLocal<JacksonUtils> jacksonUtilThreadLocal = new ThreadLocal<>();

    private static final ThreadLocal<ObjectMapper> objectMapperThreadLocal = new ThreadLocal<>();

    /**
     * 禁止调用无参构造
     */
    private JacksonUtils() {}

    /**
     * JacksonUtils 工具类实例
     * @return
     */
    public static JacksonUtils instance() {
        JacksonUtils jacksonUtils = jacksonUtilThreadLocal.get();
        if (jacksonUtils == null) {
            jacksonUtils = new JacksonUtils();
            jacksonUtilThreadLocal.set(jacksonUtils);
        }
        return jacksonUtils;
    }

    /**
     * ObjectMapper实例
     * @return
     */
    public static ObjectMapper objectMapper() {
        objectMapper = objectMapperThreadLocal.get();
        if (objectMapper== null){
            objectMapper= new ObjectMapper();

            // 格式化国家环境指定
            objectMapper.setLocale(Locale.SIMPLIFIED_CHINESE);

            //序列化时,如果没有为类型找到访问者,则会抛出异常以将其指定为非可序列化类型; 如果禁用，它们将被序列化为空对象
            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            //反序列化时,遇到未知属性是否抛JsonMappingException异常.默认JsonMappingException
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //反序列化时,避免空值转换抛出异常
            objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) ;
            //序列化和反序列化时,避免转义字符抛出异常情况
            objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) ;

            //设置null值不参与序列化(字段不被显示)
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);


            //序列化Unicode编码非ASCII字符
            //objectMapper.registerModule(unicodeSerModule());

            objectMapperThreadLocal.set(objectMapper);
        }
        return objectMapper;
    }


    /**
     * 序列化Unicode编码非ASCII字符
     */
    private static SimpleModule unicodeSerModule() {
        SimpleModule unicodeSerModule = new SimpleModule();
        unicodeSerModule.addSerializer(String.class, new JacksonStringUnicodeSerializer());
        return unicodeSerModule;
    }

    /**
     * 创建Json处理器的静态方法
     * 用于序列化
     * @param content Json字符串
     * @return
     */
    private static JsonParser getParser(String content){
        try{
            return objectMapper().getFactory().createParser(content);
        }catch (IOException ioe){
            ioe.printStackTrace();
            return null;
        }
    }

    /**
     * 创建Byte Json处理器的静态方法
     * 用于序列化
     * @param data byte array
     * @return
     */
    private static JsonParser getByteParser(byte[] data){
        try{
            return objectMapper().getFactory().createParser(data);
        }catch (IOException ioe){
            ioe.printStackTrace();
            return null;
        }
    }

    /**
     * 创建Reader Json处理器的静态方法
     * 用于序列化
     * @param r Reader
     * @return
     */
    private static JsonParser getReaderParser(Reader r){
        try{
            return objectMapper().getFactory().createParser(r);
        }catch (IOException ioe){
            ioe.printStackTrace();
            return null;
        }
    }

    /**
     * 创建InputStream Json处理器的静态方法
     * 用于序列化
     * @param in InputStream
     * @return
     */
    private static JsonParser getReaderParser(InputStream in){
        try{
            return objectMapper().getFactory().createParser(in);
        }catch (IOException ioe){
            ioe.printStackTrace();
            return null;
        }
    }


    /**
     * 创建Json生成器的静态方法, 使用标准输出
     * 用于反序列化
     * @return
     */
    private static JsonGenerator getGenerator(StringWriter sw){
        try{
            return objectMapper().getFactory().createGenerator(sw);
        }catch (IOException ioe) {
            ioe.printStackTrace();
            return null;
        }
    }


    // -------------------- to json --------------------


    /**
     * Object对象序列化为Json字符串
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String toJson(T obj){
        if (obj == null){
            return null;
        }

        if (obj instanceof String) {
            return (String) obj;
        }

        StringWriter sw= new StringWriter();
        JsonGenerator jsonGen= getGenerator(sw);
        if (jsonGen == null){
            try {
                sw.close();
            } catch (IOException e) {
            }
            return null;
        }

        try {
            //由于在getGenerator方法中指定了OutputStream为sw
            //因此调用writeObject会将数据输出到sw
            jsonGen.writeObject(obj);
            //由于采用流式输出 在输出完毕后务必清空缓冲区并关闭输出流
            jsonGen.flush();
            jsonGen.close();

            return sw.toString();
        } catch (JsonGenerationException jge) {
            logger.error("toJSON序列化失败, 异常类型【JsonGenerationException】,错误原因:{}", jge.getMessage());
        } catch (IOException ioe) {
            logger.error("toJSON序列化失败, 异常类型【IOException】, 错误原因:{}", ioe.getMessage());
            ioe.printStackTrace();
        }
        return null;
    }

    /**
     * Object 转 json 字符串并格式化美化
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> String toJsonWithPretty(T obj){
        if (obj == null){
            return null;
        }

        if (obj instanceof String) {
            return (String) obj;
        }

        StringWriter sw= new StringWriter();
        JsonGenerator jsonGen= getGenerator(sw);
        if (jsonGen == null){
            try {
                sw.close();
            } catch (IOException e) {
            }
            return null;
        }

        try {
            //return objectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);

            jsonGen.useDefaultPrettyPrinter().writeObject(obj);
            jsonGen.flush();
            jsonGen.close();

            return sw.toString();
        } catch (Exception e) {
            System.out.println("Parse object to String error");
            e.printStackTrace();
            return null;
        }
    }

    // -------------------- from json --------------------

    /**
     * 将Json Byte反序列化成对象
     *
     * @param data
     * @param clazz
     * @return
     */
    public static <T> T fromByteJson(byte[] data, Class<T> clazz) {
        try {
            JsonParser jp= getByteParser(data);
            return jp.readValueAs(clazz);
        } catch (JsonParseException e){
            logger.error(String.format("fromByteJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
        } catch (JsonMappingException e){
            logger.error(String.format("fromByteJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
        } catch (IOException e){
            logger.error(String.format("fromByteJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
        }
        return null;
    }

    /**
     * 将Json String反序列化成对象
     *
     * @param json
     * @param clazz
     * @return
     */
    public static <T> T fromStringJson(String json, Class<T> clazz) {
        try {
            return clazz.equals(String.class) ? (T) json : getParser(json).readValueAs(clazz);
        } catch (JsonParseException e) {
            logger.error(String.format("fromStringJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        } catch (JsonMappingException e) {
            logger.error(String.format("fromStringJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        } catch (IOException e) {
            logger.error(String.format("fromStringJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        }
        return null;
    }

    /**
     * 将Json Array或List反序列化为对象
     *
     * @param json
     * @param typeReference 被转对象引用类型
     * @param <T>
     * @return
     */
    public static <T> T fromStringJson(String json, TypeReference<T> typeReference) {
        try {
            //写成List.class是不行的
            JsonParser jp= getParser(json);
            return (T) (typeReference.getType().equals(String.class) ? json : jp.readValueAs(typeReference));
        } catch (JsonParseException e) {
            logger.error(String.format("fromListJson反序列化失败, 异常类型【JsonParseException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        } catch (JsonMappingException e) {
            logger.error(String.format("fromListJson反序列化失败, 异常类型【JsonMappingException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        } catch (IOException e) {
            logger.error(String.format("fromListJson反序列化失败, 异常类型【IOException】, 错误原因:{}", e.getMessage()));
            logger.error("decode(String, Class<T>)", e);
        }
        return null;
    }

    /**
     * string转object 用于转为集合对象
     * @param json json字符串
     * @param collectionClass 被转集合class
     * @param elementClasses 被转集合中对象类型class
     * @param <T>
     * @return
     */
    public static <T> T fromStringJson(String json, Class<?> collectionClass, Class<?>... elementClasses){
        JavaType javaType = objectMapper()
                .getTypeFactory()
                .constructParametricType(collectionClass, elementClasses);
        try {
            return objectMapper().readValue(json, javaType);
        } catch (IOException e) {
            logger.error("Parse String to Object error");
            e.printStackTrace();
            return null;
        }
    }


}
