package alien4cloud.orchestrators.services;

import java.io.IOException;

import javax.annotation.Resource;
import javax.inject.Inject;

import org.springframework.stereotype.Service;

import alien4cloud.dao.IGenericSearchDAO;
import alien4cloud.exception.NotFoundException;
import alien4cloud.model.orchestrators.Orchestrator;
import alien4cloud.model.orchestrators.OrchestratorConfiguration;
import alien4cloud.orchestrators.plugin.IOrchestratorPlugin;
import alien4cloud.orchestrators.plugin.IOrchestratorPluginFactory;
import alien4cloud.paas.OrchestratorPluginService;
import alien4cloud.paas.exception.PluginConfigurationException;
import alien4cloud.rest.utils.JsonUtil;

/**
 * Manages orchestrator configuration
 */
@Service
public class OrchestratorConfigurationService {
    @Inject
    private OrchestratorService orchestratorService;
    @Resource(name = "alien-es-dao")
    private IGenericSearchDAO alienDAO;
    @Inject
    private OrchestratorPluginService orchestratorPluginService;

    /**
     * Return the type of configuration for a given orchestrator.
     *
     * @param id Id of the orchestrator for which to get configuration.
     * @return The type of the orchestrator.
     */
    public Class<?> getConfigurationType(String id) {
        Orchestrator orchestrator = orchestratorService.getOrFail(id);
        return getConfigurationType(orchestrator);
    }

    /**
     * Return the type of configuration for a given orchestrator.
     *
     * @param orchestrator Orchestrator for which to get configuration.
     * @return The type of the orchestrator.
     */
    private Class<?> getConfigurationType(Orchestrator orchestrator) {
        IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator);
        return orchestratorFactory.getConfigurationType();
    }

    /**
     * Ensure that the configuration object parsed from json without typing is valid based on the orchestrator configuration type and return a valid typed
     * object.
     *
     * @param id if of the orchestrator.
     * @param configurationAsMap Configuration object (that may be a map parsed from json).
     * @return A typed configuration object.
     */
    public Object configurationAsValidObject(String id, Object configurationAsMap) throws IOException, PluginConfigurationException {
        Orchestrator orchestrator = orchestratorService.getOrFail(id);
        return configurationAsValidObject(orchestrator, configurationAsMap);
    }

    /**
     * Get the configuration for a given orchestrator.
     *
     * @param id Id of the orchestrator for which to get the configuration.
     * @return the instance of configuration for the given orchestrator
     */
    public OrchestratorConfiguration getConfigurationOrFail(String id) {
        OrchestratorConfiguration configuration = alienDAO.findById(OrchestratorConfiguration.class, id);
        if (configuration == null) {
            throw new NotFoundException("Orchestrator Configuration for id [" + id + "] doesn't exists.");
        }
        return configuration;
    }

    /**
     * Ensure that the configuration object parsed from json without typing is valid based on the orchestrator configuration type and return a valid typed
     * object.
     *
     * @param orchestrator Orchestrator for which to validated and compute a type configuration object.
     * @param configurationAsMap Configuration object (that may be a map parsed from json).
     * @return A typed configuration object.
     */
    private Object configurationAsValidObject(Orchestrator orchestrator, Object configurationAsMap) throws PluginConfigurationException, IOException {
        Class<?> configurationType = getConfigurationType(orchestrator);
        if (configurationType == null) {
            String message = "Orchestrator <" + orchestrator.getId() + "> using plugin <" + orchestrator.getPluginId() + "> <" + orchestrator.getPluginBean()
                    + "> cannot have configuration set (plugin has no configuration type).";
            throw new PluginConfigurationException(message);
        }

        return JsonUtil.readObject(JsonUtil.toString(configurationAsMap), configurationType);
    }

    /**
     * Update the configuration for the given cloud.
     *
     * @param id Id of the orchestrator for which to update the configuration.
     * @param newConfiguration The new configuration.
     */
    public synchronized void updateConfiguration(String id, Object newConfiguration) throws PluginConfigurationException {
        OrchestratorConfiguration configuration = alienDAO.findById(OrchestratorConfiguration.class, id);
        if (configuration == null) {
            throw new NotFoundException("No configuration exists for cloud [" + id + "].");
        }
        configuration.setConfiguration(newConfiguration);

        // Trigger update of the orchestrator's configuration if enabled.
        IOrchestratorPlugin orchestratorInstance = (IOrchestratorPlugin) orchestratorPluginService.get(id);
        if (orchestratorInstance != null) {
            orchestratorInstance.setConfiguration(newConfiguration);
        }

        alienDAO.save(configuration);
    }
}

@@ -130,16 +130,57 @@
 nject;%0A%0A
+import alien4cloud.utils.ReflectionUtil;%0A
 import o
@@ -4577,35 +4577,48 @@
 urationException
+, IOException
  %7B%0A
-
         Orchestr
@@ -4846,24 +4846,25 @@
       %7D%0A
+%0A
         
 configur
@@ -4859,55 +4859,349 @@
     
-configuration.setConfiguration(newConfiguration
+Object oldConfiguration = configuration.getConfiguration();%0A        Object oldConfigurationObj = configurationAsValidObject(id, oldConfiguration);%0A        // merge the config so that old values are preserved%0A        ReflectionUtil.mergeObject(newConfiguration, oldConfigurationObj);%0A        configuration.setConfiguration(oldConfigurationObj
 );%0A%0A
@@ -5476,19 +5476,19 @@
 uration(
-new
+old
 Configur
@@ -5484,32 +5484,35 @@
 oldConfiguration
+Obj
 );%0A        %7D%0A%0A  

merge orchestrator config when updating