/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */
package de.rcenvironment.components.optimizer.execution;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;

import de.rcenvironment.components.optimizer.common.MethodDescription;
import de.rcenvironment.components.optimizer.common.OptimizerComponentConstants;
import de.rcenvironment.components.optimizer.common.OptimizerFileLoader;
import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.update.api.PersistentComponentDescription;
import de.rcenvironment.core.component.update.api.PersistentComponentDescriptionConstants;
import de.rcenvironment.core.component.update.api.PersistentComponentDescriptionUpdaterUtils;
import de.rcenvironment.core.component.update.api.PersistentDescriptionFormatVersion;
import de.rcenvironment.core.component.update.spi.PersistentComponentDescriptionUpdater;
import de.rcenvironment.core.utils.common.JsonUtils;
import de.rcenvironment.core.utils.common.StringUtils;

/**
 * Implementation of {@link PersistentComponentDescriptionUpdater}.
 * 
 * @author Sascha Zur
 * @author Doreen Seider
 * @author Kathrin Schaffert (updated refactored constant declaration PersistentComponentDescriptionConstants.java)
 */
public class OptimizerPersistentComponentDescriptionUpdater implements PersistentComponentDescriptionUpdater {

    // 15.01.2020: several constant declarations are a duplicate of PersistentComponentDescriptionConstants.java and could be replaced
    // (K. Schaffert)

    private static final String REQUIRED = "Required";

    private static final String INPUT_EXECUTION_CONSTRAINT = "inputExecutionConstraint_4aae3eea";

    private static final String PRE_CALC_FILE_PATH = "preCalcFilePath";

    private static final String SELF_LOOP_ENDPOINT = "SelfLoopEndpoint";

    private static final String OUTER_LOOP_ENDPOINT = "OuterLoopEndpoint";

    private static final String DYNAMIC_OUTPUTS = "dynamicOutputs";

    private static final String GOAL = "goal";

    private static final String DYNAMIC_INPUTS = "dynamicInputs";

    private static final String SPECIFIC_SETTINGS = "specificSettings";

    private static final String DAKOTA_COLINY_EVOLUTIONARY_ALGORITHM = "Dakota Coliny Evolutionary Algorithm";

    private static final String DAKOTA_COLINY_COBYLA_CONSTRAINT_OPTIMIZATION_BY_LINEAR_APPROXIMATIONS =
        "Dakota Coliny COBYLA (Constraint Optimization By Linear Approximations)";

    private static final String DAKOTA_QUASI_NEWTON_METHOD = "Dakota Quasi-Newton method";

    private static final String EP_IDENTIFIER = "epIdentifier";

    private static final String CONSTRAINT = "Constraint";

    private static final String OBJECTIVE = "Objective";

    private static final String NAME = "name";

    private static final String STATIC_OUTPUTS = "staticOutputs";

    private static final String NAN = "NaN";

    private static final String WEIGHT = "weight";

    private static final String METADATA = "metadata";

    private static final String METHOD_CONFIGURATIONS = "methodConfigurations";

    private static final String ALGORITHM = "algorithm";

    private static final String CONFIGURATION = "configuration";

    private static final String LOOP_ENDPOINT_TYPE = "loopEndpointType_5e0ed1cd";

    private static final Log LOGGER = LogFactory.getLog(OptimizerPersistentComponentDescriptionUpdater.class);

    private static final String COLON = ":";

    private static final String V1_0 = "1.0";

    private static final String V3_0 = "3.0";

    private static final String V5_0 = "5.0";

    private static final String V5_1 = "5.1";

    private static final String V6_0 = "6.0";

    private static final String V6_1 = "6.1";

    private static final String V6_2 = "6.2";

    private static final String V7_0 = "7.0";

    private static final String V7_1 = "7.1";

    private static final String V8 = "8";

    private static ObjectMapper mapper = JsonUtils.getDefaultObjectMapper();

    @Override
    public String[] getComponentIdentifiersAffectedByUpdate() {
        return OptimizerComponentConstants.COMPONENT_IDS;
    }

    @Override
    public int getFormatVersionsAffectedByUpdate(String persistentComponentDescriptionVersion, boolean silent) {

        int versionsToUpdate = PersistentDescriptionFormatVersion.NONE;

        if (!silent && (persistentComponentDescriptionVersion == null
            || persistentComponentDescriptionVersion.compareTo(V1_0) < 0)) {
            versionsToUpdate = versionsToUpdate | PersistentDescriptionFormatVersion.BEFORE_VERSON_THREE;
        }
        if (!silent && persistentComponentDescriptionVersion != null
            && persistentComponentDescriptionVersion.compareTo(V3_0) < 0) {
            versionsToUpdate = versionsToUpdate | PersistentDescriptionFormatVersion.FOR_VERSION_THREE;
        }
        if (!silent && persistentComponentDescriptionVersion != null
            && persistentComponentDescriptionVersion.compareTo(V8) < 0) {
            versionsToUpdate = versionsToUpdate | PersistentDescriptionFormatVersion.AFTER_VERSION_THREE;
        }
        if (silent && persistentComponentDescriptionVersion != null
            && persistentComponentDescriptionVersion.compareTo(V7_1) < 0) {
            versionsToUpdate = versionsToUpdate | PersistentDescriptionFormatVersion.AFTER_VERSION_THREE;
        }
        return versionsToUpdate;
    }

    @Override
    public PersistentComponentDescription performComponentDescriptionUpdate(int formatVersion, PersistentComponentDescription description,
        boolean silent) throws IOException {
        if (!silent) { // called after "silent" was called
            if (formatVersion == PersistentDescriptionFormatVersion.BEFORE_VERSON_THREE) {
                description = updateBeforeVersion3(description);
            } else if (formatVersion == PersistentDescriptionFormatVersion.FOR_VERSION_THREE) {
                description = updateToVersion3(description);
            } else if (formatVersion == PersistentDescriptionFormatVersion.AFTER_VERSION_THREE) {
                switch (description.getComponentVersion()) {
                case V3_0:
                    description = updateToVersion50(description);
                case V5_0:
                    description = updateToVersion51(description);
                case V5_1:
                    description = updateToVersion60(description);
                case V6_0:
                    description = updateToVersion61(description);
                case V6_1:
                    description = updateFrom61To62(description);
                case V6_2:
                    description = updateToVersion70(description);
                case V7_0:
                    description = updateFrom70To71(description);
                case V7_1:
                    description = updateFrom71To8(description);
                default:
                    // nothing to do here
                }
            }
        } else { // called first (before non-silent)
            if (formatVersion == PersistentDescriptionFormatVersion.AFTER_VERSION_THREE) {
                switch (description.getComponentVersion()) {
                case V7_0:
                    description = updateFrom70To71(description);
                default:
                    // nothing to do here
                }
            }
        }
        return description;
    }

    private PersistentComponentDescription updateFrom71To8(PersistentComponentDescription description)
        throws JsonProcessingException, IOException {
        description =
            PersistentComponentDescriptionUpdaterUtils.removeOuterLoopDoneEndpoints(description);
        description = PersistentComponentDescriptionUpdaterUtils.removeEndpointCharacterInfoFromMetaData(description);
        description.setComponentVersion(V8);
        return description;
    }

    private PersistentComponentDescription updateFrom70To71(PersistentComponentDescription description)
        throws JsonProcessingException, IOException {
        PersistentComponentDescription updatedDesc =
            PersistentComponentDescriptionUpdaterUtils.updateFaultToleranceOfLoopDriver(description);

        JsonNode node = mapper.readTree(updatedDesc.getComponentDescriptionAsString());
        JsonNode configuration = node.get(CONFIGURATION);
        if (configuration != null) {
            ObjectNode configNode = (ObjectNode) configuration;
            configNode.put(OptimizerComponentConstants.USE_RESTART_FILE, "false");
            configNode.put(PRE_CALC_FILE_PATH, "");
        }
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
        PersistentComponentDescription newdesc = new PersistentComponentDescription(writer.writeValueAsString(node));
        newdesc.setComponentVersion(V7_1);
        return newdesc;
    }

    private PersistentComponentDescription updateToVersion70(PersistentComponentDescription description)
        throws JsonParseException, JsonGenerationException, JsonMappingException, IOException {
        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        JsonNode staticOutputs = node.get(STATIC_OUTPUTS);
        if (staticOutputs != null) {
            for (JsonNode outputEndpoint : staticOutputs) {
                ObjectNode metaData = (ObjectNode) outputEndpoint.get(METADATA);
                if (metaData == null) {
                    metaData = JsonNodeFactory.instance.objectNode();
                    ((ObjectNode) outputEndpoint).put(METADATA, metaData);
                }
                if (outputEndpoint.get(NAME).textValue().equals("Outer loop done")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, "InnerLoopEndpoint");
                }
                if (outputEndpoint.get(NAME).textValue().equals("Iteration")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, SELF_LOOP_ENDPOINT);
                }
                if (outputEndpoint.get(NAME).textValue().equals("Gradient request")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, SELF_LOOP_ENDPOINT);
                }
                if (outputEndpoint.get(NAME).textValue().equals("Done")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, OUTER_LOOP_ENDPOINT);
                }
            }
        }
        JsonNode dynamicOutputs = node.get(DYNAMIC_OUTPUTS);
        if (dynamicOutputs != null) {
            for (JsonNode outputEndpoint : dynamicOutputs) {
                ObjectNode metaData = (ObjectNode) outputEndpoint.get(METADATA);
                if (outputEndpoint.get(EP_IDENTIFIER).textValue().equals("Design")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, SELF_LOOP_ENDPOINT);
                }
                if (outputEndpoint.get(EP_IDENTIFIER).textValue().equals("optima")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, OUTER_LOOP_ENDPOINT);
                }
            }
        }
        JsonNode dynamicInputs = node.get(DYNAMIC_INPUTS);
        if (dynamicInputs != null) {
            for (JsonNode inputEndpoint : dynamicInputs) {
                ObjectNode metaData = (ObjectNode) inputEndpoint.get(METADATA);
                if (inputEndpoint.get(EP_IDENTIFIER).textValue().equals("Objective")
                    || inputEndpoint.get(EP_IDENTIFIER).textValue().equals("Constraint")
                    || inputEndpoint.get(EP_IDENTIFIER).textValue().equals("gradients")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, SELF_LOOP_ENDPOINT);
                }
                if (inputEndpoint.get(EP_IDENTIFIER).textValue().equals("startvalues")
                    || inputEndpoint.get(EP_IDENTIFIER).textValue().equals("outerLoopDone")) {
                    metaData.put(LOOP_ENDPOINT_TYPE, OUTER_LOOP_ENDPOINT);
                    metaData.put(INPUT_EXECUTION_CONSTRAINT, REQUIRED);
                }
            }
        }
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
        PersistentComponentDescription newdesc = new PersistentComponentDescription(writer.writeValueAsString(node));
        newdesc.setComponentVersion(V7_0);
        return newdesc;
    }

    private PersistentComponentDescription updateFrom61To62(PersistentComponentDescription description)
        throws JsonParseException, IOException {
        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        JsonNode staticOutputs = node.get(STATIC_OUTPUTS);
        if (staticOutputs != null) {
            for (JsonNode outputEndpoint : staticOutputs) {
                ((ObjectNode) outputEndpoint).remove(EP_IDENTIFIER);
                if (outputEndpoint.get(NAME).textValue().equals("Optimizer is finished")) {
                    ((ObjectNode) outputEndpoint).put(NAME, LoopComponentConstants.ENDPOINT_NAME_LOOP_DONE);
                }
            }
        }
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();
        PersistentComponentDescription newdesc = new PersistentComponentDescription(writer.writeValueAsString(node));
        newdesc.setComponentVersion(V6_2);
        return newdesc;

    }

    @SuppressWarnings("unchecked")
    private PersistentComponentDescription updateToVersion61(PersistentComponentDescription description)
        throws JsonParseException, IOException {
        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        ((ObjectNode) node.get(CONFIGURATION)).put(PRE_CALC_FILE_PATH, "${preCalcFilePath}");
        TextNode methodConfigurations = (TextNode) node.get(CONFIGURATION).get(METHOD_CONFIGURATIONS);
        Map<String, Object> configs = mapper.readValue(methodConfigurations.textValue(), new HashMap<String, Object>().getClass());
        ObjectNode accuracyNode = mapper.createObjectNode();
        accuracyNode.put("GuiName", "Solution accuracy");
        accuracyNode.put("dataType", "Real");
        accuracyNode.put("SWTWidget", "Text");
        accuracyNode.put("DefaultValue", "1.E-4");
        accuracyNode.put("Value", "");
        accuracyNode.put("Validation", "required");
        if (configs != null && configs.get(DAKOTA_COLINY_COBYLA_CONSTRAINT_OPTIMIZATION_BY_LINEAR_APPROXIMATIONS) != null) {

            Map<String, Object> specifics =
                (Map<String, Object>) ((HashMap<String, Object>) configs
                    .get(DAKOTA_COLINY_COBYLA_CONSTRAINT_OPTIMIZATION_BY_LINEAR_APPROXIMATIONS)).get(
                        SPECIFIC_SETTINGS);
            specifics.put("solution_accuracy", accuracyNode);

        }
        if (configs != null && configs.get(DAKOTA_COLINY_EVOLUTIONARY_ALGORITHM) != null) {
            Map<String, Object> specifics =
                (Map<String, Object>) ((HashMap<String, Object>) configs.get(DAKOTA_COLINY_EVOLUTIONARY_ALGORITHM)).get(
                    SPECIFIC_SETTINGS);
            specifics.put("solution_accuracy", accuracyNode);

        }
        ((ObjectNode) node.get(CONFIGURATION)).remove(METHOD_CONFIGURATIONS);
        ((ObjectNode) node.get(CONFIGURATION)).put(METHOD_CONFIGURATIONS, mapper.writeValueAsString(configs));
        ArrayNode inputs = ((ArrayNode) node.get(DYNAMIC_INPUTS));
        if (inputs != null) {
            Iterator<JsonNode> it = inputs.iterator();
            while (it.hasNext()) {
                ObjectNode input = (ObjectNode) it.next();
                if (((ObjectNode) input.get(METADATA)).get(GOAL) != null
                    && ((ObjectNode) input.get(METADATA)).get(GOAL).textValue().equals("Solve for")) {
                    ((ObjectNode) input.get(METADATA)).put(GOAL, "Minimize");
                    ((ObjectNode) input.get(METADATA)).remove("solve");
                }
            }
        }
        description = new PersistentComponentDescription(writer.writeValueAsString(node));
        description.setComponentVersion(V6_1);
        return description;
    }

    @SuppressWarnings("unchecked")
    private PersistentComponentDescription updateToVersion60(PersistentComponentDescription description)
        throws JsonParseException, IOException {
        description = PersistentComponentDescriptionUpdaterUtils.updateSchedulingInformation(description);
        description = PersistentComponentDescriptionUpdaterUtils.updateIsNestedLoop(description);
        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        ArrayNode statEndpoints = (ArrayNode) node.get("staticOutputs");
        if (statEndpoints != null) {
            ObjectNode gradientRequest = mapper.createObjectNode();
            gradientRequest.put("identifier", UUID.randomUUID().toString());
            gradientRequest.put(NAME, OptimizerComponentConstants.DERIVATIVES_NEEDED);
            gradientRequest.put(EP_IDENTIFIER, NullNode.instance);
            gradientRequest.put("datatype", "Boolean");
            statEndpoints.add(gradientRequest);
            for (JsonNode o : statEndpoints) {
                ObjectNode outNode = (ObjectNode) o;
                if (outNode.get(NAME).textValue().equals("Iteration count")) {
                    outNode.remove(NAME);
                    outNode.put(NAME, "Iteration");
                }
            }
        }

        ((ObjectNode) node.get(CONFIGURATION)).put(PRE_CALC_FILE_PATH, "${preCalcFilePath}");
        TextNode methodConfigurations = (TextNode) node.get(CONFIGURATION).get(METHOD_CONFIGURATIONS);
        Map<String, Object> configs = mapper.readValue(methodConfigurations.textValue(), new HashMap<String, Object>().getClass());
        if (configs != null && configs.get("Dakota Surrogate-Based Local") != null) {
            ((HashMap<String, Object>) configs.get("Dakota Surrogate-Based Local")).put("methodCode", "surrogate_based_local");

        }
        if (configs != null && configs.get(DAKOTA_QUASI_NEWTON_METHOD) != null) {
            Map<String, Object> specifics =
                (Map<String, Object>) ((HashMap<String, Object>) configs.get(DAKOTA_QUASI_NEWTON_METHOD)).get(SPECIFIC_SETTINGS);
            specifics.remove("central_path");
            ((Map<String, Object>) specifics.get("merit_function")).put("dataType", "None");
            ((Map<String, Object>) specifics.get("merit_function")).put("defaultValue", "argaez_tapia");
        }
        Map<String, Object> moga =
            mapper.readValue(OptimizerFileLoader.class.getResourceAsStream("/resources/optimizer/dakota/dakotaMOGA.json"),
                new HashMap<String, Object>().getClass());
        Map<String, Object> soga =
            mapper.readValue(OptimizerFileLoader.class.getResourceAsStream("/resources/optimizer/dakota/dakotaSOGA.json"),
                new HashMap<String, Object>().getClass());
        Map<String, Object> defaults =
            mapper.readValue(OptimizerFileLoader.class.getResourceAsStream("/resources/optimizer/dakota/defaults.json"),
                new HashMap<String, Object>().getClass());
        moga.put("commonSettings", defaults);
        soga.put("commonSettings", defaults);

        if (configs != null) {
            configs.put("Dakota Multi Objective Genetic Algorithm", moga);
            configs.put("Dakota Single Objective Genetic Algorithm", soga);
        }
        ((ObjectNode) node.get(CONFIGURATION)).remove(METHOD_CONFIGURATIONS);
        ((ObjectNode) node.get(CONFIGURATION)).put(METHOD_CONFIGURATIONS, mapper.writeValueAsString(configs));

        description = new PersistentComponentDescription(writer.writeValueAsString(node));
        description.setComponentVersion(V6_0);
        return description;
    }

    private PersistentComponentDescription updateToVersion51(PersistentComponentDescription description) throws JsonParseException,
        IOException {
        if (!description.getComponentVersion().equals(V5_0)) {
            description = updateToVersion50(description);
        }
        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        ArrayNode dynEndpoints = (ArrayNode) node.get(DYNAMIC_INPUTS);

        if (dynEndpoints != null) {
            for (JsonNode endpoint : dynEndpoints) {
                if (endpoint.get(NAME) != null
                    && endpoint.get(NAME).textValue().contains(OptimizerComponentConstants.GRADIENT_DELTA)) {
                    ((ObjectNode) endpoint).put(EP_IDENTIFIER, "gradients");
                }
            }
        }
        description = new PersistentComponentDescription(writer.writeValueAsString(node));
        description.setComponentVersion(V5_1);
        return description;
    }

    private PersistentComponentDescription updateToVersion50(PersistentComponentDescription description) throws JsonParseException,
        IOException {

        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        ArrayNode dynEndpoints = (ArrayNode) node.get(DYNAMIC_OUTPUTS);

        if (dynEndpoints != null) {
            List<JsonNode> newNodes = new LinkedList<JsonNode>();
            for (JsonNode endpoint : dynEndpoints) {
                if (endpoint.get(NAME) != null
                    && dynEndpoints.get(endpoint.get(NAME).textValue()
                        + OptimizerComponentConstants.OPTIMUM_VARIABLE_SUFFIX) == null) {
                    ObjectNode optimalNode = (ObjectNode) copy(endpoint);
                    optimalNode.put("identifier", UUID.randomUUID().toString());
                    optimalNode.put(NAME, optimalNode.get(NAME).textValue() + OptimizerComponentConstants.OPTIMUM_VARIABLE_SUFFIX);
                    optimalNode.put(EP_IDENTIFIER, OptimizerComponentConstants.ID_OPTIMA);
                    newNodes.add(optimalNode);
                }

                if (endpoint.get(METADATA) != null) {
                    ((ObjectNode) endpoint.get(METADATA)).put(OptimizerComponentConstants.META_HAS_STARTVALUE, true);
                }
            }
            for (JsonNode e : newNodes) {
                dynEndpoints.add(e);
            }
        }
        description = new PersistentComponentDescription(writer.writeValueAsString(node));
        description.setComponentVersion(V5_0);
        return description;
    }

    @SuppressWarnings("unchecked")
    private <T extends JsonNode> T copy(T node) {
        try {
            return (T) JsonUtils.getDefaultObjectMapper().readTree(node.traverse());
        } catch (IOException e) {
            throw new AssertionError(e);
        }
    }

    private PersistentComponentDescription updateToVersion3(PersistentComponentDescription description) throws JsonParseException,
        IOException {
        description =
            PersistentComponentDescriptionUpdaterUtils.updateAllDynamicEndpointsToIdentifier(DYNAMIC_OUTPUTS, "Design", description);

        JsonNode node = mapper.readTree(description.getComponentDescriptionAsString());
        ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter();

        JsonNode dynEndpoints = node.get(DYNAMIC_INPUTS);
        if (dynEndpoints != null) {
            for (JsonNode endpoint : dynEndpoints) {
                if (endpoint.get(PersistentComponentDescriptionConstants.EP_IDENTIFIER) == null
                    || endpoint.get(PersistentComponentDescriptionConstants.EP_IDENTIFIER).isNull()
                    || endpoint.get(PersistentComponentDescriptionConstants.EP_IDENTIFIER).textValue().equals("null")) {
                    ObjectNode objectEndpoint = (ObjectNode) endpoint;
                    objectEndpoint.remove(PersistentComponentDescriptionConstants.EP_IDENTIFIER);
                    String identifier = "";
                    if ((objectEndpoint.get(METADATA)) != null
                        && !((ObjectNode) objectEndpoint.get(METADATA)).get(WEIGHT).textValue().equals(NAN)) {
                        identifier = OBJECTIVE;
                        updateMetaData(objectEndpoint, dynEndpoints);
                    } else if (((ObjectNode) objectEndpoint.get(METADATA)) != null) {
                        identifier = CONSTRAINT;
                        updateMetaData(objectEndpoint, dynEndpoints);
                    } else {
                        if (objectEndpoint.get(NAME).textValue().contains(OptimizerComponentConstants.GRADIENT_DELTA)) {
                            ObjectNode newMetadataObjectNode = JsonNodeFactory.instance.objectNode();
                            objectEndpoint.put(METADATA, newMetadataObjectNode);
                            String functionName =
                                objectEndpoint.get(NAME).textValue()
                                    .substring(1, objectEndpoint.get(NAME).textValue().indexOf('.'));
                            for (JsonNode otherEndpoint : dynEndpoints) {
                                if (otherEndpoint.get(NAME).textValue().equals(functionName)
                                    && (otherEndpoint.get(METADATA)) != null
                                    && !((ObjectNode) otherEndpoint.get(METADATA)).get(WEIGHT).textValue().equals(NAN)) {
                                    identifier = OBJECTIVE;
                                    break;
                                } else {
                                    identifier = CONSTRAINT;
                                }
                            }
                        }
                    }
                    objectEndpoint.put(PersistentComponentDescriptionConstants.EP_IDENTIFIER, TextNode.valueOf(identifier));

                }
            }
        }
        // Add Static endpoints
        description = new PersistentComponentDescription(writer.writeValueAsString(node));
        description.setComponentVersion(V3_0);
        return description;
    }

    private void updateMetaData(ObjectNode objectEndpoint, JsonNode dynEndpoints) {
        ObjectNode metadata = (ObjectNode) objectEndpoint.get(METADATA);
        if (metadata.get(GOAL).textValue().equals("0")) {
            metadata.put(GOAL, TextNode.valueOf("Minimize"));
        } else if (metadata.get(GOAL).textValue().equals("1")) {
            metadata.put(GOAL, TextNode.valueOf("Maximize"));
        } else {
            metadata.put(GOAL, TextNode.valueOf("Solve for"));
        }
        boolean hasMetaData = false;
        for (JsonNode otherEndpoint : dynEndpoints) {
            if (otherEndpoint.get(NAME).textValue().contains(OptimizerComponentConstants.GRADIENT_DELTA)
                && otherEndpoint.get(NAME).textValue()
                    .contains(OptimizerComponentConstants.GRADIENT_DELTA + objectEndpoint.get(NAME).textValue() + ".")) {
                hasMetaData = true;
            }
        }
        metadata.put("hasGradient", JsonNodeFactory.instance.booleanNode(hasMetaData));
        objectEndpoint.remove(METADATA);
        objectEndpoint.put(METADATA, metadata);
    }

    private PersistentComponentDescription updateBeforeVersion3(PersistentComponentDescription description) throws IOException,
        JsonParseException,
        JsonProcessingException, JsonGenerationException, JsonMappingException {
        JsonFactory jsonFactory = new JsonFactory();
        JsonNode completeComponent;
        try (JsonParser jsonParser = jsonFactory.createJsonParser(description.getComponentDescriptionAsString())) {
            completeComponent = mapper.readTree(jsonParser);
        }
        JsonNode completeConfiguration = completeComponent.get(CONFIGURATION);
        JsonNode algorithmNode = null;
        JsonNode methodsConfigurationNode = null;
        JsonNode optimizerPackageNode = null;
        boolean foundMethodConfigurations = false;
        boolean foundPackageDeclaration = false;
        for (int i = 0; i < completeConfiguration.size(); i++) {
            if (completeConfiguration.get(i).textValue() != null) {
                String[] configItem = completeConfiguration.get(i).textValue().split(COLON);
                if (configItem[0].equals(ALGORITHM)) {
                    algorithmNode = updateAlgorithm(completeConfiguration.get(i));
                }
                if (configItem[0].contains(METHOD_CONFIGURATIONS) && configItem.length > 2 && configItem[2] != null) {
                    String configs = completeConfiguration.get(i).textValue();
                    configs = configs.substring(configs.indexOf("{"));
                    methodsConfigurationNode = updateMethods(configs);
                    foundMethodConfigurations = true;
                }
                if (configItem[0].equals(OptimizerComponentConstants.OPTIMIZER_PACKAGE)) {
                    foundPackageDeclaration = true;
                    if (configItem.length > 2 && configItem[2] != null && !configItem[2].isEmpty()
                        && (algorithmNode != null && algorithmNode.textValue().contains("Pyranha"))) {
                        optimizerPackageNode = TextNode.valueOf(OptimizerComponentConstants.OPTIMIZER_PACKAGE
                            + ":java.lang.String:pyranha");
                    } else if (configItem.length > 2 && configItem[2] != null && !configItem[2].isEmpty()
                        && (algorithmNode != null && algorithmNode.textValue().contains("Dakota"))) {
                        optimizerPackageNode = TextNode.valueOf(OptimizerComponentConstants.OPTIMIZER_PACKAGE
                            + ":java.lang.String:dakota");
                    } else {
                        optimizerPackageNode = TextNode.valueOf(OptimizerComponentConstants.OPTIMIZER_PACKAGE
                            + ":java.lang.String:generic");
                    }
                }
            }
        }
        if (!foundMethodConfigurations) {
            methodsConfigurationNode = writeNewMethodConfigurationsNode();
        }
        if (!foundPackageDeclaration) {
            optimizerPackageNode = TextNode.valueOf(OptimizerComponentConstants.OPTIMIZER_PACKAGE
                + ":java.lang.String:dakota");
        }
        if (completeConfiguration.get("genericPythonPath") != null) {
            ((ObjectNode) completeConfiguration).put("genericPythonPath", TextNode.valueOf("${genericPythonPath}"));
        }
        if (completeConfiguration.get("pyranhaPythonPath") != null) {
            ((ObjectNode) completeConfiguration).put("pyranhaPythonPath", TextNode.valueOf("${pyranhaPythonPath}"));
        }
        ArrayNode newConfig = mapper.createArrayNode();
        newConfig.add(algorithmNode);
        if (methodsConfigurationNode != null) {
            newConfig.add(methodsConfigurationNode);
        }
        newConfig.add(optimizerPackageNode);
        ((ObjectNode) completeComponent).remove(CONFIGURATION);
        ((ObjectNode) completeComponent).put(CONFIGURATION, newConfig);
        PersistentComponentDescription newDesc =
            new PersistentComponentDescription(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(completeComponent));
        newDesc.setComponentVersion(V1_0);
        return newDesc;
    }

    @SuppressWarnings("unchecked")
    private JsonNode updateMethods(String methodConfigs) {
        JsonNode returnNode = null;

        try {
            String newMethodConfiguations = METHOD_CONFIGURATIONS + ":java.lang.String:";
            Map<String, MethodDescription> defaultMethodConfigurations = OptimizerFileLoader.getAllMethodDescriptions("/optimizer");
            for (String key : defaultMethodConfigurations.keySet()) {
                defaultMethodConfigurations.put(key, mapper.convertValue(defaultMethodConfigurations.get(key), MethodDescription.class));
            }
            Map<String, MethodDescription> methodConfigurations;
            if (!methodConfigs.equals("")) {
                methodConfigs = StringUtils.unescapeSeparator(methodConfigs);
                methodConfigurations = mapper.readValue(methodConfigs, new HashMap<String, MethodDescription>().getClass());
                for (String key : methodConfigurations.keySet()) {
                    methodConfigurations.put(key, mapper.convertValue(methodConfigurations.get(key), MethodDescription.class));
                }
                replaceConfigurations(methodConfigurations, defaultMethodConfigurations);

            } else {
                return writeNewMethodConfigurationsNode();
            }
            returnNode =
                TextNode.valueOf(newMethodConfiguations
                    + StringUtils.escapeSeparator(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(
                        methodConfigurations)));

        } catch (JsonParseException e) {
            LOGGER.error("Could not parse method file ", e);
        } catch (JsonMappingException e) {
            LOGGER.error("Could not map method file ", e);
        } catch (IOException e) {
            LOGGER.error("Could not load method file ", e);
        }
        return returnNode;
    }

    private static JsonNode writeNewMethodConfigurationsNode() {
        JsonNode returnNode = null;

        String newMethodConfiguations = METHOD_CONFIGURATIONS + ":java.lang.String:";
        Map<String, MethodDescription> defaultMethodConfigurations;
        try {
            defaultMethodConfigurations = OptimizerFileLoader.getAllMethodDescriptions("/optimizer");
            for (String key : defaultMethodConfigurations.keySet()) {
                defaultMethodConfigurations.put(key, mapper.convertValue(defaultMethodConfigurations.get(key), MethodDescription.class));
            }
            String configs = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defaultMethodConfigurations);
            configs = StringUtils.escapeSeparator(configs);
            returnNode = mapper.valueToTree(newMethodConfiguations + configs);
            return returnNode;
        } catch (JsonParseException e) {
            LOGGER.warn("", e);
        } catch (JsonMappingException e) {
            LOGGER.warn("", e);
        } catch (IOException e) {
            LOGGER.warn("", e);
        }
        return null;
    }

    private static JsonNode updateAlgorithm(JsonNode algorithm) {
        JsonNode returnNode = null;
        String prefix = "algorithm:java.lang.String:";
        String[] algorithmItem = algorithm.textValue().split(COLON);
        if (algorithmItem.length > 2) {
            if (algorithmItem[2] != null && algorithmItem[2].equals("HOPSPACK Asynch Pattern Search")) {
                returnNode = TextNode.valueOf(prefix + "Dakota HOPSPACK Asynch Pattern Search");
            }
            if (algorithmItem[2] != null && !algorithmItem[2].contains("Dakota")) {
                if (algorithmItem[2].contains("COBYLA")) {
                    returnNode = TextNode.valueOf(prefix + DAKOTA_COLINY_COBYLA_CONSTRAINT_OPTIMIZATION_BY_LINEAR_APPROXIMATIONS);
                }
                if (algorithmItem[2].contains("Newton")) {
                    returnNode = TextNode.valueOf(prefix + DAKOTA_QUASI_NEWTON_METHOD);
                }
                if (algorithmItem[2].contains("Evolutionary")) {
                    returnNode = TextNode.valueOf(prefix + DAKOTA_COLINY_EVOLUTIONARY_ALGORITHM);
                }
            }
        }
        if (returnNode != null) {
            return returnNode;
        }
        return algorithm;
    }

    private void replaceConfigurations(Map<String, MethodDescription> methodConfigurations,
        Map<String, MethodDescription> defaultMethodConfigurations) {

        // delete old methods from the configuration
        List<String> keysToDelete = new LinkedList<String>();
        for (String key : methodConfigurations.keySet()) {
            if (!defaultMethodConfigurations.containsKey(key)) {
                keysToDelete.add(key);
            }
        }
        for (String key : keysToDelete) {
            methodConfigurations.remove(key);
        }
        // replace existing keys
        for (String key : methodConfigurations.keySet()) {
            MethodDescription oldDescription = methodConfigurations.get(key);
            MethodDescription defaultdescription = defaultMethodConfigurations.get(key);

            oldDescription.setFollowingMethods(defaultdescription.getFollowingMethods());
            oldDescription.setMethodCode(defaultdescription.getMethodCode());
            oldDescription.setMethodName(defaultdescription.getMethodName());
            oldDescription.setOptimizerPackage(defaultdescription.getOptimizerPackage());

            replaceKeyValueMaps(oldDescription.getSpecificSettings(), defaultdescription.getSpecificSettings());
            replaceKeyValueMaps(oldDescription.getResponsesSettings(), defaultdescription.getResponsesSettings());

        }

        // add new methods to the configuration
        for (String key : defaultMethodConfigurations.keySet()) {
            if (!methodConfigurations.containsKey(key)) {
                methodConfigurations.put(key, defaultMethodConfigurations.get(key));
            }
        }
    }

    private void replaceKeyValueMaps(Map<String, Map<String, String>> currentSettings,
        Map<String, Map<String, String>> defaultSettings) {
        // delete old keys from the configuration
        if (currentSettings != null) {
            // delete old methods from the configuration
            List<String> keysToDelete = new LinkedList<String>();
            for (String key : currentSettings.keySet()) {
                if (!defaultSettings.containsKey(key)) {
                    keysToDelete.add(key);
                }
            }
            for (String key : keysToDelete) {
                currentSettings.remove(key);
            }
        }
        if (currentSettings != null) {

            for (String controlKey : currentSettings.keySet()) {
                Map<String, String> currentProperties = currentSettings.get(controlKey);
                Map<String, String> defaultProperties = defaultSettings.get(controlKey);
                // delete old properties
                for (String propertyKey : currentProperties.keySet()) {
                    if (!defaultProperties.containsKey(propertyKey)) {
                        currentProperties.remove(propertyKey);
                    }
                }
                // replace all other proerties but the value which could be user defined
                for (String propertyKey : currentProperties.keySet()) {
                    if (!propertyKey.equalsIgnoreCase(OptimizerComponentConstants.VALUE_KEY)) {
                        currentProperties.put(propertyKey, defaultProperties.get(propertyKey));
                    } else {
                        if (currentProperties.get(propertyKey).equals(
                            defaultProperties.get(OptimizerComponentConstants.DEFAULT_VALUE_KEY))) {
                            currentProperties.put(propertyKey, defaultProperties.get(propertyKey));
                        }
                    }
                }

                // add new properties
                for (String propertyKey : defaultProperties.keySet()) {
                    if (!currentProperties.containsKey(propertyKey)) {
                        currentProperties.put(propertyKey, defaultProperties.get(propertyKey));
                    }
                }
            }
        }
        // add new methods to the configuration
        if (defaultSettings != null && currentSettings != null) {
            for (String controlKey : defaultSettings.keySet()) {
                if (!currentSettings.containsKey(controlKey)) {
                    currentSettings.put(controlKey, defaultSettings.get(controlKey));
                }
            }
        }
    }

}
