package ru.kirillius.pf.sdn.core;

import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import org.json.JSONObject;
import ru.kirillius.json.JSONSerializable;
import ru.kirillius.json.JSONSerializer;
import ru.kirillius.json.JSONUtility;
import ru.kirillius.json.SerializationException;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Stores per-component configuration instances and serializes them as JSON.
 */
@NoArgsConstructor
@JSONSerializable(ComponentConfigStorage.Serializer.class)
public class ComponentConfigStorage {

    /**
     * JSON serializer that materializes component configuration maps.
     */
    public final static class Serializer implements JSONSerializer<ComponentConfigStorage> {

        /**
         * Converts the stored configurations into a JSON object keyed by class name.
         */
        @Override
        public Object serialize(ComponentConfigStorage componentConfigStorage) throws SerializationException {
            var json = new JSONObject();
            componentConfigStorage.configs.forEach((key, value) -> {
                json.put(key.getName(), JSONUtility.serializeStructure(value));
            });
            return json;
        }

        /**
         * Restores component configuration instances from the provided JSON payload.
         */
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Override
        public ComponentConfigStorage deserialize(Object o, Class<?> aClass) throws SerializationException {
            var loader = getClass().getClassLoader();
            var json = (JSONObject) o;
            var storage = new ComponentConfigStorage();
            json.keySet().forEach(key -> {
                try {
                    var pluginClass = loader.loadClass(key);
                    var value = json.getJSONObject(key);
                    var configClass = Component.getConfigClass((Class) pluginClass);
                    storage.configs.put((Class) pluginClass, JSONUtility.deserializeStructure(value, configClass));
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            });
            return storage;
        }
    }

    private final Map<Class<? extends Component<?>>, Object> configs = new ConcurrentHashMap<>();

    /**
     * Returns (and instantiates if necessary) the configuration object for the given component class.
     */
    @SuppressWarnings("unchecked")
    @SneakyThrows
    public <CT> CT getConfig(Class<? extends Component<CT>> componentClass) {
        if (!configs.containsKey(componentClass)) {
            var configClass = Component.getConfigClass(componentClass);
            var instance = configClass.getConstructor().newInstance();
            configs.put(componentClass, instance);
        }
        return (CT) configs.get(componentClass);
    }

    /**
     * Stores the provided configuration instance for the specified component class.
     */
    @SneakyThrows
    public <CT> void setConfig(Class<? extends Component<CT>> componentClass, CT config) {
        var configClass = Component.getConfigClass(componentClass);
        configs.put(componentClass, config);
    }
}
