/*
 * Copyright 2006-2019 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.component.model.endpoint.api;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import de.rcenvironment.core.component.api.LoopComponentConstants;
import de.rcenvironment.core.component.model.spi.PropertiesChangeSupport;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.EndpointType;
import de.rcenvironment.core.utils.common.StringUtils;

/**
 * Manages endpoint descriptions of a component. This class is not thread-safe. It is intended that this class is primary called by the GUI
 * thread.
 * 
 * @author Doreen Seider
 * 
 *         FIXME: Use a factory to create {@link EndpointDescription} instances and just provide add, delete, etc. for
 *         {@link EndpointDescription} instances --seid_do
 * 
 * Note: This class evolved over time and lost its straight-forward implementation. E.g., it got some methods required by the
 * WorkflowDescriptionPersistenceHandler to add endpoints even if a component is not installed and thus a matching
 * EndpointDefinition does not exist. An issue exists that covers the rework: https://mantis.sc.dlr.de/view.php?id=13191 --seid_do
 */
public class EndpointDescriptionsManager extends PropertiesChangeSupport implements Serializable {

    /** Property that is fired when an endpoint was changed. */
    public static final String PROPERTY_ENDPOINT = "de.rcenvironment.rce.component.endpoint.ComponentDescriptionsEndpoint";

    private static final String MESSAGE_DYNAMIC_ENDPOINT_DESCRIPTION_DOESNT_EXIST =
        "Dynamic endpoint description with name '%s' doesn't exist";

    private static final String NO_DYNAMIC_ENDPOINT_DEFINITION_WITH_ID_S_DECLARED = "No dynamic endpoint definition with id '%s' declared";

    private static final String MESSAGE_DESIRED_ENDPOINT_NAME_ALREADY_EXISTS = "Desired endpoint name already exists: ";

    private static final long serialVersionUID = -5776007445203691291L;

    private final EndpointType endpointType;

    private final EndpointDefinitionsProvider endpointDefinitionsProvider;

    private final Map<String, EndpointDescription> staticEndpointDescriptions;

    private Map<String, EndpointDescription> dynamicEndpointDescriptions = new HashMap<>();

    private final Map<String, EndpointGroupDescription> staticEndpointGroupDescriptions;

    private Map<String, EndpointGroupDescription> dynamicEndpointGroupDescriptions = new HashMap<>();

    public EndpointDescriptionsManager(EndpointDefinitionsProvider endpointDefinitionsProvider, EndpointType entdpointType) {
        this.endpointType = entdpointType;
        this.endpointDefinitionsProvider = endpointDefinitionsProvider;
        this.staticEndpointDescriptions = createStaticEndpointDescriptions(endpointDefinitionsProvider);
        this.staticEndpointGroupDescriptions = createStaticEndpointGroupDescriptions(endpointDefinitionsProvider);
    }

    private Map<String, EndpointDescription> createStaticEndpointDescriptions(EndpointDefinitionsProvider provider) {
        Map<String, EndpointDescription> descs = new HashMap<>();
        for (EndpointDefinition definition : provider.getStaticEndpointDefinitions()) {
            descs.put(definition.getName(), new EndpointDescription(definition, endpointType));
        }
        return descs;
    }

    private Map<String, EndpointGroupDescription> createStaticEndpointGroupDescriptions(EndpointDefinitionsProvider provider) {
        Map<String, EndpointGroupDescription> descs = new HashMap<>();
        for (EndpointGroupDefinition definition : provider.getStaticEndpointGroupDefinitions()) {
            descs.put(definition.getName(), new EndpointGroupDescription(definition));
        }
        return descs;
    }

    /**
     * Adds a dynamic {@link EndpointGroupDescription}.
     * 
     * @param endpointGroupId identifier of endpoint blueprint
     * @param name name of the {@link EndpointGroupDescription} to create
     */
    public void addDynamicEndpointGroupDescription(String endpointGroupId, String name) {
        addDynamicEndpointGroupDescription(endpointGroupId, name, true);
    }

    /**
     * Adds a dynamic {@link EndpointGroupDescription}.
     * 
     * @param endpointGroupId identifier of endpoint blueprint
     * @param name name of the {@link EndpointGroupDescription} to create
     * @param checkIfDeclared <code>true</code> if it should be checked that dynamic
     *        {@link EndpointDefinition} exists, otherwise <code>false</code>
     * @return {@link EndpointGroupDescription} added
     */
    public EndpointGroupDescription addDynamicEndpointGroupDescription(String endpointGroupId, String name, boolean checkIfDeclared) {

        if (checkIfDeclared && (!isDynamicEndpointGroupDefinitionDeclared(endpointGroupId) || !isValidEndpointGroupName(name))) {
            String message;
            if (!isDynamicEndpointGroupDefinitionDeclared(endpointGroupId)) {
                message = "No dynamic endpoint group definition with id '" + endpointGroupId + "' declared";
            } else {
                message = "Desired endpoint group name already exists: " + name;
            }
            throw new IllegalArgumentException(message);
        }
        EndpointGroupDescription desc = new EndpointGroupDescription(endpointDefinitionsProvider
            .getDynamicEndpointGroupDefinition(endpointGroupId));
        desc.setName(name);
        desc.setDynamicEndpointIdentifier(endpointGroupId);
        dynamicEndpointGroupDescriptions.put(name, desc);
        return desc;
    }

    /**
     * Removes dynamic {@link EndpointGroupDescription} with given name.
     * 
     * @param name name of {@link EndpoinGrouptDescription} to remove
     * @return {@link EndpointGroupDescription} object removed
     */
    public EndpointGroupDescription removeDynamicEndpointGroupDescription(String name) {
        return dynamicEndpointGroupDescriptions.remove(name);
    }

    /**
     * @return all dynamic {@link EndpointGroupDescription}s
     */
    public Set<EndpointGroupDescription> getEndpointGroupDescriptions() {
        Set<EndpointGroupDescription> descs = new HashSet<>();
        descs.addAll(staticEndpointGroupDescriptions.values());
        descs.addAll(dynamicEndpointGroupDescriptions.values());
        return Collections.unmodifiableSet(descs);
    }

    public Set<EndpointGroupDescription> getStaticEndpointGroupDescriptions() {
        return Collections.unmodifiableSet(new HashSet<>(staticEndpointGroupDescriptions.values()));
    }

    public Set<EndpointGroupDescription> getDynamicEndpointGroupDescriptions() {
        return Collections.unmodifiableSet(new HashSet<>(dynamicEndpointGroupDescriptions.values()));
    }

    /**
     * @param name name of the {@link EndpointGroupDescription} to get
     * @return {@link EndpointGroupDescription} with given group name or <code>null</code> if there
     *         is none
     */
    public EndpointGroupDescription getEndpointGroupDescription(String name) {
        EndpointGroupDescription desc = staticEndpointGroupDescriptions.get(name);
        if (desc == null) {
            desc = dynamicEndpointGroupDescriptions.get(name);
        }
        return desc;
    }

    /**
     * Adds initial endpoints, if there are some declared.
     */
    public void addInitialDynamicEndpointDescriptions() {

        for (EndpointDefinition definition : endpointDefinitionsProvider.getDynamicEndpointDefinitions()) {
            for (InitialDynamicEndpointDefinition initialDefinition : definition
                .getInitialDynamicEndpointDefinitions()) {
                addDynamicEndpointDescription(definition.getIdentifier(), initialDefinition.getName(),
                    initialDefinition.getDataType(), new HashMap<String, String>());
            }
        }

    }

    /**
     * @return all {@link EndpointDescription}s
     */
    public Set<EndpointDescription> getEndpointDescriptions() {
        Map<String, EndpointDescription> endpointDescs = new HashMap<String, EndpointDescription>();
        endpointDescs.putAll(staticEndpointDescriptions);
        endpointDescs.putAll(dynamicEndpointDescriptions);
        return cloneEndpointDescriptionValues(endpointDescs.values());
    }

    public Set<EndpointDescription> getStaticEndpointDescriptions() {
        return cloneEndpointDescriptionValues(staticEndpointDescriptions.values());
    }

    public Set<EndpointDescription> getDynamicEndpointDescriptions() {
        return cloneEndpointDescriptionValues(dynamicEndpointDescriptions.values());
    }

    public Set<EndpointDefinition> getStaticEndpointDefinitions() {
        return Collections.unmodifiableSet(endpointDefinitionsProvider.getStaticEndpointDefinitions());
    }

    /**
     * @param names names of {@link EndpointDefinition}s to get
     * @return list of {@link EndpointDefinition}s for given names
     */
    public Set<EndpointDefinition> getStaticEndpointDefinitions(List<String> names) {
        Set<EndpointDefinition> endpointDefinitions = new HashSet<>();
        for (String name : names) {
            endpointDefinitions.add(getStaticEndpointDefinition(name));
        }
        return endpointDefinitions;
    }

    /**
     * @param name name of {@link EndpointDefinition} to get
     * @return {@link EndpointDefinition} with given name
     */
    public EndpointDefinition getStaticEndpointDefinition(String name) {
        return endpointDefinitionsProvider.getStaticEndpointDefinition(name);
    }

    public Set<EndpointDefinition> getDynamicEndpointDefinitions() {
        return Collections.unmodifiableSet(endpointDefinitionsProvider.getDynamicEndpointDefinitions());
    }
    
    /**
     * @param ids identifiers of {@link EndpointDefinition}s to get
     * @return list of {@link EndpointDefinition}s for given identifiers
     */
    public Set<EndpointDefinition> getDynamicEndpointDefinitions(List<String> ids) {
        Set<EndpointDefinition> endpointDefinitions = new HashSet<>();
        for (String id : ids) {
            endpointDefinitions.add(getDynamicEndpointDefinition(id));
        }
        return endpointDefinitions;
    }

    /**
     * @param id identifier of {@link EndpointDefinition} to get
     * @return {@link EndpointDefinition} with given id
     */
    public EndpointDefinition getDynamicEndpointDefinition(String id) {
        return endpointDefinitionsProvider.getDynamicEndpointDefinition(id);
    }

    private Set<EndpointDescription> cloneEndpointDescriptionValues(Collection<EndpointDescription> endpointDescriptions) {
        Set<EndpointDescription> clonedDescs = new HashSet<EndpointDescription>();
        for (EndpointDescription desc : endpointDescriptions) {
            clonedDescs.add(EndpointDescription.copy(desc));
        }
        return clonedDescs;
    }

    /**
     * @param name name of {@link EndpointDescription} to get
     * @return {@link EndpointDescription} object with given name or <code>null</code> if given name
     *         doesn't exist
     */
    public EndpointDescription getEndpointDescription(String name) {
        EndpointDescription endpointDesc = getNotClonedEndpointDescription(name);
        if (endpointDesc != null) {
            return EndpointDescription.copy(endpointDesc);
        }
        return null;
    }

    /**
     * Add a dynamic endpoint to the list of dynamic endpoints.
     * 
     * @param endpointId identifier of dynamic {@link EndpointDefinition} to chose as the underlying
     *        {@link EndpointDefinition}
     * @param name name to set
     * @param dataType data type to set
     * @param metaData meta data to set
     * @return {@link EndpointDescription} object created and added or <code>null</code> if the name
     *         already exists
     * @throws IllegalArgumentException if dynamic endpoint description with given name already
     *         exists or new name is invalid
     */
    public EndpointDescription addDynamicEndpointDescription(String endpointId, String name, DataType dataType,
        Map<String, String> metaData) throws IllegalArgumentException {
        if (!isDynamicEndpointDefinitionDeclared(endpointId)) {
            throw new IllegalArgumentException(StringUtils.format(NO_DYNAMIC_ENDPOINT_DEFINITION_WITH_ID_S_DECLARED, endpointId));
        }
        return addDynamicEndpointDescription(endpointId, name, dataType, metaData, UUID.randomUUID().toString(),
            getDynamicEndpointDefinition(endpointId).getParentGroupName(), true);
    }

    /**
     * Add a dynamic endpoint to the list of dynamic endpoints.
     * 
     * @param endpointId identifier of dynamic {@link EndpointDefinition} to chose as the underlying
     *        {@link EndpointDefinition}
     * @param name name to set
     * @param dataType data type to set
     * @param metaData meta data to set
     * @param checkIfDeclared perform check if dynamic endpoint is declared
     * @return {@link EndpointDescription} object created and added or <code>null</code> if the name
     *         already exists
     * @throws IllegalArgumentException if dynamic endpoint description with given name already
     *         exists or new name is invalid
     */
    public EndpointDescription addDynamicEndpointDescription(String endpointId, String name, DataType dataType,
        Map<String, String> metaData, boolean checkIfDeclared) throws IllegalArgumentException {
        if (isDynamicEndpointDefinitionDeclared(endpointId)) {
            return addDynamicEndpointDescription(endpointId, name, dataType, metaData, UUID.randomUUID().toString(),
                getDynamicEndpointDefinition(endpointId).getParentGroupName(), checkIfDeclared);
        } else if (checkIfDeclared) {
            throw new IllegalArgumentException(StringUtils.format(NO_DYNAMIC_ENDPOINT_DEFINITION_WITH_ID_S_DECLARED, endpointId));
        } else {
            return addDynamicEndpointDescription(endpointId, name, dataType, metaData, UUID.randomUUID().toString(),
                null, checkIfDeclared);
        }
    }

    /**
     * Add a dynamic endpoint to the list of dynamic endpoints with a random identifier.
     * 
     * @param endpointId identifier of dynamic {@link EndpointDefinition} to chose as the underlying
     *        {@link EndpointDefinition}
     * @param name name to set
     * @param dataType data type to set
     * @param metaData meta data to set
     * @param parentGroup name of parent input group, <code>null</code> for using default group or
     *        none
     * @param checkIfDeclared perform check if dynamic endpoint is declared
     * @return {@link EndpointDescription} object created and added or <code>null</code> if the name
     *         already exists
     * @throws IllegalArgumentException if dynamic endpoint description with given name already
     *         exists or new name is invalid
     */
    public EndpointDescription addDynamicEndpointDescription(String endpointId, String name, DataType dataType,
        Map<String, String> metaData, String parentGroup, boolean checkIfDeclared) throws IllegalArgumentException {
        return addDynamicEndpointDescription(endpointId, name, dataType, metaData, UUID.randomUUID().toString(),
            parentGroup, checkIfDeclared);
    }
    /**
     * Add a dynamic endpoint to the list of dynamic endpoints.
     * 
     * @param endpointId identifier of dynamic {@link EndpointDefinition} to chose as the underlying
     *        {@link EndpointDefinition}
     * @param name name to set
     * @param dataType data type to set
     * @param metaData meta data to set
     * @param identifier identifier of the endpoint
     * @param parentGroup name of parent input group, <code>null</code> for using default group or
     *        none
     * @param checkIfDeclared perform check if dynamic endpoint is declared
     * @return {@link EndpointDescription} object created and added or <code>null</code> if the name
     *         already exists
     * @throws IllegalArgumentException if dynamic endpoint description with given name already
     *         exists or new name is invalid
     */
    public EndpointDescription addDynamicEndpointDescription(String endpointId, String name, DataType dataType,
        Map<String, String> metaData, String identifier, String parentGroup, boolean checkIfDeclared) throws IllegalArgumentException {

        if (checkIfDeclared && (!isDynamicEndpointDefinitionDeclared(endpointId) || !isValidEndpointName(name))) {
            String message;
            if (!isDynamicEndpointDefinitionDeclared(endpointId)) {
                message = StringUtils.format(NO_DYNAMIC_ENDPOINT_DEFINITION_WITH_ID_S_DECLARED, endpointId);
            } else {
                message = MESSAGE_DESIRED_ENDPOINT_NAME_ALREADY_EXISTS + name;
            }
            throw new IllegalArgumentException(message);
        }
        
        EndpointDescription desc = new EndpointDescription(endpointDefinitionsProvider
            .getDynamicEndpointDefinition(endpointId), identifier);

        desc.setName(name);
        desc.setDynamicEndpointIdentifier(endpointId);
        desc.setDataType(dataType);
        for (String key : metaData.keySet()) {
            desc.setMetaDataValue(key, metaData.get(key));
        }
        if (parentGroup != null) { // default (if there is one) should not be used as explicit
                                   // parent group is given
            desc.setParentGroupName(parentGroup);
        }
        dynamicEndpointDescriptions.put(name, desc);
        
        firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Added, desc, null));

        return desc;
    }

    /**
     * Add a dynamic endpoint to the list of dynamic endpoints.
     * 
     * @param endpointId identifier of dynamic {@link EndpointDefinition} to chose as the underlying
     *        {@link EndpointDefinition}
     * @param name name to set
     * @param dataType data type to set
     * @param metaData meta data to set
     * @param identifier identifier of the endpoint
     * @return {@link EndpointDescription} object created and added or <code>null</code> if the name
     *         already exists
     * @throws IllegalArgumentException if dynamic endpoint description with given name already
     *         exists or new name is invalid
     */
    public EndpointDescription addDynamicEndpointDescription(String endpointId, String name, DataType dataType,
        Map<String, String> metaData, String identifier) throws IllegalArgumentException {
        if (!isDynamicEndpointDefinitionDeclared(endpointId)) {
            throw new IllegalArgumentException(StringUtils.format(NO_DYNAMIC_ENDPOINT_DEFINITION_WITH_ID_S_DECLARED, endpointId));
        }
        return addDynamicEndpointDescription(endpointId, name, dataType, metaData, identifier,
            getDynamicEndpointDefinition(endpointId).getParentGroupName(), true);
    }

    private boolean isDynamicEndpointGroupDefinitionDeclared(String endpointGroupId) {
        return endpointDefinitionsProvider.getDynamicEndpointGroupDefinition(endpointGroupId) != null;
    }

    private boolean isDynamicEndpointDefinitionDeclared(String endpointId) {
        return endpointDefinitionsProvider.getDynamicEndpointDefinition(endpointId) != null;
    }

    /**
     * Removes dynamic {@link EndpointDescription} with given name.
     * 
     * @param name name of {@link EndpointDescription} to remove
     * @return {@link EndpointDescription} object removed
     */
    public EndpointDescription removeDynamicEndpointDescription(String name) {
        EndpointDescription desc = dynamicEndpointDescriptions.remove(name);

        firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Removed, null, desc));

        return desc;
    }

    /**
     * Removes dynamic {@link EndpointDescription} with given name without fireing a property change
     * event.
     * 
     * @param name name of {@link EndpointDescription} to remove
     * @return {@link EndpointDescription} object removed
     */
    public EndpointDescription removeDynamicEndpointDescriptionQuietely(String name) {
        return dynamicEndpointDescriptions.remove(name);
    }

    /**
     * Edits a dynamic {@link EndpointDescription}.
     * 
     * @param oldName old name of {@link EndpointDescription}
     * @param newName new name of {@link EndpointDescription}
     * @param newDataType new {@link DataType}
     * @param newMetaData new meta data {@link Map}
     * @param newDynEndpointId new name of endpoint the input belongs to
     * @return {@link EndpointDescription} edited
     * @throws IllegalArgumentException if no dynamic endpoint description with given name exists or
     *         new name is invalid
     */
    public synchronized EndpointDescription editDynamicEndpointDescription(String oldName, String newName, DataType newDataType,
        Map<String, String> newMetaData, String newDynEndpointId) throws IllegalArgumentException {
        return editDynamicEndpointDescription(oldName, newName, newDataType, newMetaData, newDynEndpointId,
            getDynamicEndpointDefinition(newDynEndpointId).getParentGroupName());
    }

    /**
     * Edits a dynamic {@link EndpointDescription}.
     * 
     * @param oldName old name of {@link EndpointDescription}
     * @param newName new name of {@link EndpointDescription}
     * @param newDataType new {@link DataType}
     * @param newMetaData new meta data {@link Map}
     * @param newDynEndpointId new name of endpoint the input belongs to
     * @param newParentGroup new name of endpoint parent group the input belongs to
     * @return {@link EndpointDescription} edited
     * @throws IllegalArgumentException if no dynamic endpoint description with given name exists or
     *         new name is invalid
     */
    public synchronized EndpointDescription editDynamicEndpointDescription(String oldName, String newName, DataType newDataType,
        Map<String, String> newMetaData, String newDynEndpointId, String newParentGroup) throws IllegalArgumentException {

        EndpointDescription description = dynamicEndpointDescriptions.remove(oldName);
        if (description == null) {
            throw new IllegalArgumentException(StringUtils.format(MESSAGE_DYNAMIC_ENDPOINT_DESCRIPTION_DOESNT_EXIST, oldName));
        }
        EndpointDescription oldDescription = EndpointDescription.copy(description);
        try {
            if (!isValidEndpointName(newName)) {
                throw new IllegalArgumentException(MESSAGE_DESIRED_ENDPOINT_NAME_ALREADY_EXISTS + newName);
            }
            description.setName(newName);
            description.setDataType(newDataType);
            description.setMetaData(newMetaData);
            description.getMetaData().put(LoopComponentConstants.META_KEY_LOOP_ENDPOINT_TYPE,
                oldDescription.getMetaDataValue(LoopComponentConstants.META_KEY_LOOP_ENDPOINT_TYPE));
            if (newDynEndpointId != null) {
                description.setDynamicEndpointIdentifier(newDynEndpointId);
            }
            description.setParentGroupName(newParentGroup);
        } catch (IllegalArgumentException e) {
            dynamicEndpointDescriptions.put(oldName, description);
            throw e;
        }

        dynamicEndpointDescriptions.put(newName, description);

        firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Modified, description, oldDescription));

        return description;
    }

    /**
     * Edits a dynamic {@link EndpointDescription}.
     * 
     * @param oldName old name of {@link EndpointDescription}
     * @param newName new name of {@link EndpointDescription}
     * @param newDataType new {@link DataType}
     * @param newMetaData new meta data {@link Map}
     * @return {@link EndpointDescription} edited
     * @throws IllegalArgumentException if no dynamic endpoint description with given name exists or
     *         new name is invalid
     */
    public synchronized EndpointDescription editDynamicEndpointDescription(String oldName, String newName, DataType newDataType,
        Map<String, String> newMetaData) throws IllegalArgumentException {
        if (getEndpointDescription(oldName) == null) {
            throw new IllegalArgumentException(StringUtils.format(MESSAGE_DYNAMIC_ENDPOINT_DESCRIPTION_DOESNT_EXIST, oldName));
        }
        return editDynamicEndpointDescription(oldName, newName, newDataType, newMetaData,
            getEndpointDescription(oldName).getDynamicEndpointIdentifier());
    }

    /**
     * Adds a static {@link EndpointDescription}.
     * 
     * @param description {@link EndpointDescription} to add
     */
    public synchronized void addStaticEndpointDescription(EndpointDescription description) {
        staticEndpointDescriptions.put(description.getName(), description);
    }

    /**
     * Edits a static {@link EndpointDescription}.
     * 
     * @param name name of {@link EndpointDescription} to edit
     * @param newDataType new {@link DataType}
     * @param newMetaData new meta data {@link Map}
     * @return {@link EndpointDescription} edited
     * @throws IllegalArgumentException if no dynamic endpoint description with given name exists
     */
    public synchronized EndpointDescription editStaticEndpointDescription(String name, DataType newDataType,
        Map<String, String> newMetaData) throws IllegalArgumentException {

        return editStaticEndpointDescription(name, newDataType, newMetaData, true);
    }

    /**
     * Edits a static {@link EndpointDescription}.
     * 
     * @param name name of {@link EndpointDescription} to edit
     * @param newDataType new {@link DataType}
     * @param newMetaData new meta data {@link Map}
     * @param checkIfDeclared perform check if static endpoint is declared
     * @return {@link EndpointDescription} edited
     * @throws IllegalArgumentException if no dynamic endpoint description with given name exists
     */
    public synchronized EndpointDescription editStaticEndpointDescription(String name, DataType newDataType,
        Map<String, String> newMetaData, boolean checkIfDeclared) throws IllegalArgumentException {

        EndpointDescription description = staticEndpointDescriptions.get(name);
        if (description == null) {
            if (checkIfDeclared) {
                throw new IllegalArgumentException("Static endpoint description with name '" + name + "' doesn't exist");
            } else { // add description
                description = new EndpointDescription(null, endpointType);
                description.setName(name);
                description.setDataType(newDataType);
                description.setMetaData(newMetaData);
                staticEndpointDescriptions.put(name, description);
                return description;
            }
        }

        EndpointDescription oldDescription = EndpointDescription.copy(description);

        description.setDataType(newDataType);
        description.setMetaData(newMetaData);

        firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Modified, description, oldDescription));

        return description;
    }

    /**
     * @param name name to validate
     * @return <code>true</code> if no other endpoint with given name exists, else
     *         <code>false</code>
     */
    public boolean isValidEndpointName(String name) {
        return name != null && !name.isEmpty() && !staticEndpointDescriptions.containsKey(name)
            && !dynamicEndpointDescriptions.containsKey(name);
    }

    /**
     * @param name name to validate
     * @return <code>true</code> if no other endpoint group with given name exists, else
     *         <code>false</code>
     */
    public boolean isValidEndpointGroupName(String name) {
        return name != null && !name.isEmpty() && !staticEndpointGroupDescriptions.containsKey(name)
            && !dynamicEndpointGroupDescriptions.containsKey(name);
    }

    /**
     * @param endpointName name affected {@link EndpointDescription}
     * @param dataType {@link DataType} to add as a connected one
     */
    public void addConnectedDataType(String endpointName, DataType dataType) {

        EndpointDescription endpointDesc = getNotClonedEndpointDescription(endpointName);

        if (endpointDesc != null) {
            EndpointDescription oldEndpointDesc = EndpointDescription.copy(endpointDesc);
            endpointDesc.addConnectedDataType(dataType);
            firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Modified, endpointDesc, oldEndpointDesc));
        }
    }

    /**
     * @param endpointName name affected {@link EndpointDescription}
     * @param dataType {@link DataType} to remove as a connected one
     */
    public void removeConnectedDataType(String endpointName, DataType dataType) {
        EndpointDescription endpointDesc = getNotClonedEndpointDescription(endpointName);

        if (endpointDesc != null) {
            EndpointDescription oldEndpointDesc = EndpointDescription.copy(endpointDesc);
            endpointDesc.removeConnectedDataType(dataType);
            firePropertyChange(PROPERTY_ENDPOINT, new EndpointChange(EndpointChange.Type.Modified, endpointDesc, oldEndpointDesc));
        }
    }

    public EndpointType getManagedEndpointType() {
        return endpointType;
    }

    private EndpointDescription getNotClonedEndpointDescription(String name) {
        EndpointDescription endpointDesc = staticEndpointDescriptions.get(name);
        if (endpointDesc == null) {
            return dynamicEndpointDescriptions.get(name);
        }
        return endpointDesc;
    }

}
