/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.cryptolib.commons.utils.serialization;

import static com.scytl.cryptolib.commons.utils.validations.Validate.notNullOrBlank;

import java.io.IOException;
import java.math.BigInteger;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.scytl.cryptolib.api.exceptions.GeneralCryptoLibException;

/**
 * Basic abstract implementation of {@link JsonSerializable}.
 * <p>
 * A typical implementation can use the following principles
 * <ul>
 * <li>First implement serialization and deserialization with Jackson FasterXML.
 * </li>
 * <li>Create a constructor or static factory method to be used by Jackson
 * FasterXML with annotation {@link JsonCreator} which implements the validation
 * logic.</li>
 * <li>If the structure of JSON differs significantly from the structure of the
 * class then use the Memento pattern.</li>
 * <li>Implement {@code fromJson} methods using {@link #fromJson(String, Class)}
 * method.</li>
 * </ul>
 * <p>
 * Simple example: <pre>
 * <code>
 * &#64;JsonRootName("simple")
 * public final class Simple extends AbstractJsonSerializable {
 *     ptivate final String name;
 *     private final int value;
 *     
 *     &#64;JsonCreator
 *     public Simple(@JsonProperty("name") String name, 
 *             &#64;JsonProperty("value") int value) throws GeneralCryptolibException {
 *         if (name == null || name.isEmpty()) {
 *             throw new GeneralCryptolivException("Invalid name.")
 *         }    
 *         if (value &le; 0) {
 *             throw new GeneralCryptolivException("Invalid value.")
 *         }
 *         this.name = name;
 *         this.value = value;
 *     }
 *     
 *     public static Simple fromJson(String json) throws GeneralCryptolibException {
 *         return fromJson(json, Simple.class);
 *     }
 *     
 *     &#64;JsonProperty("name")
 *     public int getName() {
 *         return name;
 *     }
 *     
 *     &#64;JsonProperty("value")
 *     public int getValue() {
 *         return value;
 *     }
 * }
 * </code> </pre> In the example above the class is isomorphic to the JSON so
 * its properties are annotated to be used during the serialization as is.
 * Validation and initialization is implemented in public constructor. Due to
 * {@link JsonCreator} annotation Jackson FasterXML uses the same constructor
 * during the deserialization.
 * <p>
 * Example with memento: <pre>
 * <code>
 * public final class Complex extends AbstractJsonSerializable {
 *     private final String host;
 *     private final int port;
 *     
 *     public Complex(String host, int value) throws GeneralCryptolibException {
 *         if (host == null || host.isEmpty()) {
 *             throw new GeneralCryptolivException("Invalid host.")
 *         }    
 *         if (port &le; 0) {
 *             throw new GeneralCryptolivException("Invalid port.")
 *         }
 *         this.host = host;
 *         this.port = port;
 *     }
 *     
 *     public static Complex fromJson(String json) throws GeneralCryptolibException {
 *         return fromJson(json, Complex.class);
 *     }
 *     
 *     &#64;JsonCreator
 *     static Complex fromMemento(Memento memento) throws GeneralCryptolibException {
 *         String host = getHost(memento.address);
 *         String port = getPort(memento.address);
 *         return new Complex(host, port)
 *     }
 *     
 *     public int getHost() {
 *         return host;
 *     }
 *     
 *     public int getPort() {
 *         return port;
 *     }
 *     
 *     &#64;JsonValue
 *     Memento toMemento() {
 *         Memento memento = new Memento();
 *         memento.address = host + ':' + port;
 *         return memento;
 *     }
 *     
 *     &#64;JsonRootName("complex")
 *     static final class Memento {
 *         &#64;JsonProperty("address")
 *         public string address;
 *     }
 * }
 * </code> </pre> In the example above memento is used for serialization and
 * deserialization. Initialization and validation is implemented in the
 * constructor. Due to {@link JsonCreator} annotation Jackson FasterXML uses the
 * {@code fromMemento} method which extracts values from memento and calls the
 * constructor.
 */
public class AbstractJsonSerializable implements JsonSerializable {
    private static final ObjectMapper MAPPER;

    static {
        MAPPER = new ObjectMapper();
        MAPPER.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
        MAPPER.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
        SimpleModule module = new SimpleModule();
        module.addSerializer(new BigIntegerSerializer());
        module.addDeserializer(BigInteger.class,
            new BigIntegerDeserializer());
        MAPPER.registerModule(module);
    }

    /**
     * Deserializes the instance from a given string in JSON format.
     * 
     * @param <T>
     *            the object type
     * @param json
     *            the JSON
     * @param objectClass
     *            the object class
     * @return the instance
     * @throws GeneralCryptoLibException
     *             failed to deserialize the instance.
     */
    protected static <T> T fromJson(String json, Class<T> objectClass)
            throws GeneralCryptoLibException {
        notNullOrBlank(json, objectClass.getSimpleName() + " JSON string");
        try {
            return MAPPER.readValue(json, objectClass);
        } catch (JsonMappingException e) {
            Throwable cause = e.getCause();
            if (cause instanceof GeneralCryptoLibException) {
                throw (GeneralCryptoLibException) cause;
            } else {
                throw new GeneralCryptoLibException(
                    "Failed to deserialize instance from JSON.", e);
            }
        } catch (IOException e) {
            throw new GeneralCryptoLibException(
                "Failed to deserialize instance from JSON.", e);
        }
    }

    /**
     * Serializes the instance to a string in JSON format.
     * 
     * @return the string in JSON format.
     * @throws GeneralCryptoLibException
     *             if the serialization process fails.
     */
    @Override
    public final String toJson() throws GeneralCryptoLibException {
        try {
            return MAPPER.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            throw new GeneralCryptoLibException(
                "Failed to serialize instance to JSON.", e);
        }
    }
}
