package com.rmn.testrail.util;

import org.codehaus.jackson.map.DeserializationConfig;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * @author mmerrell
 */
public class JSONUtils {

    /**
     * Takes a JSON string and attempts to "map" it to the given class. It assumes the JSON 
     * string is valid, and that it maps to the object you've indicated, and any problem with
     * either the JSON or the specified class will result in a RuntimeException. It will run 
     * with FAIL_ON_UNKNOWN_PROPERTIES set to false, which will allow some unmapped custom fields
     * to come through without throwing exceptions. If you have custom fields on your entities, you 
     * should subclass the entity provided here and add the custom fields to it
     * @param jsonObjectClass The Class you wish to map the contents to
     * @param json The JSON you wish to map to the given Class
     * @return An instance of the given Class, based on the attributes of the given JSON
     */
    public static <T> T getMappedJsonObject(Class<T> jsonObjectClass, String json) {
        return getMappedJsonObject(jsonObjectClass, json, false);
    }
    
    /**
     * Takes a JSON string and attempts to "map" it to the given class. It assumes the JSON 
     * string is valid, and that it maps to the object you've indicated. If you want to run outside of "strict" mode,
     * pass false for the failOnUnknownProperties flag
     * @param jsonObjectClass The Class you wish to map the contents to
     * @param json The JSON you wish to map to the given Class
     * @param failOnUnknownProperties Whether or not to throw an exception if an unknown JSON attribute is encountered
     * @return An instance of the given Class, based on the attributes of the given JSON
     */
    public static <T> T getMappedJsonObject( Class<T> jsonObjectClass, String json, boolean failOnUnknownProperties ) {
        TypeFactory t = TypeFactory.defaultInstance();
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties );

        T mappedObject;
        try {
            mappedObject = mapper.readValue( json, t.constructType( jsonObjectClass ) );
        } catch ( Exception e ) {
            throw new RuntimeException( "Could not instantiate object of class [" + jsonObjectClass.getName()+ "]: " + e );
        }
        return mappedObject;
    }

    /**
     * Returns a list of objects of the specified type. This runs in "strict" mode, so if it encounters
     * a property in the JSON object that isn't defined or accessible in the class definition, it will
     * throw an exception. See the alternate "getJsonObjectList( Class, String boolean ) if you would
     * like it to ignore unknown properties
     * @param jsonObjectClass The Class you wish to map the json to
     * @param json The JSON you wish to map to the given Class
     * @return An instance of the given Class, based on the attributes of the given JSON
     */
    public static <T> List<T> getMappedJsonObjectList(Class<T> jsonObjectClass, String json) {
        return getMappedJsonObjectList(jsonObjectClass, json, true);
    }

    /**
     * Returns a list of objects of the specified type. If you send "false" in the 3rd parameter, it will be 
     * forgiving of JSON properties that are not defined or inaccessible in the specified jsonObjectClass
     * @param jsonObjectClass The Class you wish to map the json to
     * @param json The JSON you wish to map to the given Class
     * @param failOnUnknownProperties Whether or not to throw an exception if an unknown JSON attribute is encountered
     * @return An instance of the given Class, based on the attributes of the given JSON
     */
    public static <T> List<T> getMappedJsonObjectList(Class<T> jsonObjectClass, String json, boolean failOnUnknownProperties) {
        List<T> list;
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
        
        TypeFactory t = TypeFactory.defaultInstance();
        try {
            list = mapper.readValue(json, t.constructCollectionType(ArrayList.class, jsonObjectClass));
            return list;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        throw new RuntimeException("Could not process JSON");
    }
}