/*
 *  @author Ranides Atterwim {@literal <ranides@gmail.com>}
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;

import net.ranides.assira.reflection.*;

import java.io.IOException;

/**
 * This class is used only to format objects into text in a bit more meaningful than default toString method.
 * Additionally, it can be used to convert any bean compatible object to/from JSON (including lexical cast).
 *
 * @author ranides
 */
public final class JSON {

    private static final TypeFactory TYPE_FACTORY = TypeFactory.defaultInstance();
    
    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.configure(SerializationFeature.INDENT_OUTPUT, true);
        MAPPER.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
    }

    /**
     * Lexical cast: it serializes object into text and then tries to create object of specified type.
     *
     * @param value input value
     * @param clazz target type
     * @param <T> target type
     * @return new object created from JSON
     * @throws JSONCastException when incompatible cast
     */
    public static <T> T cast(Object value, Class<T> clazz) throws JSONCastException {
        return cast(value, IClass.typeinfo(clazz));
    }

    /**
     * Lexical cast: it serializes object into text and then tries to create object of specified type.
     *
     * @param value input value
     * @param type target type
     * @param <T> target type
     * @return new object created from JSON
     * @throws JSONCastException when incompatible cast
     */
    @SuppressWarnings("unchecked")
    public static <T> T cast(Object value, IClass<T> type) throws JSONCastException {
        return (T) JSON.fromJSON(JSON.toJSON(value), (IClass<?>) type);
    }

    /**
     * Converts value into JSON using default ObjectMapper (it tries to follow JavaBean convention).
     *
     * @param value input
     * @return serialized object
     * @throws ClassCastException when incompatible cast
     */
    public static String toJSON(Object value) throws ClassCastException {
        try {
            return MAPPER.writeValueAsString(value);
        } catch(IOException cause) {
            throw new JSONCastException(value.getClass()+" cannot be converted to JSON", cause);
        }
    }

    /**
     * Converts JSON into object of specified type using default ObjectMapper  (it tries to follow JavaBean convention).
     *
     * @param value input text
     * @param type target type
     * @param <T> target type
     * @return new object
     * @throws JSONCastException when incompatible cast
     */
    public static <T> T fromJSON(String value, Class<T> type) throws JSONCastException {
        return fromJSON(value, IClass.typeinfo(type));
    }

    /**
     * Converts JSON into object of specified type using default ObjectMapper  (it tries to follow JavaBean convention).
     *
     * @param value input text
     * @param type target type
     * @param <T> target type
     * @return new object
     * @throws JSONCastException when incompatible cast
     */
    @SuppressWarnings("unchecked")
    public static <T> T fromJSON(String value, IClass<T> type) throws JSONCastException {
        try {
            return MAPPER.readValue(value, TYPE_FACTORY.constructType( type.reflective()) );
        } catch(IOException cause) {
            throw new JSONCastException(type + " cannot be created from JSON", cause);
        }
    }

    private static final class JSONCastException extends ClassCastException {
        
        private static final long serialVersionUID = 1L;

        public JSONCastException(String message, Throwable cause) {
            super(message);
            initCause(cause);
        }
        
    }

}
