/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.ambari.server.agent.stomp.dto;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.ambari.server.state.State;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang.StringUtils;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class TopologyComponent {
  private String componentName;
  private String serviceName;
  private String displayName;
  private String version;
  private Set<Long> hostIds = new HashSet<>();
  private Set<String> hostNames = new HashSet<>();
  private Set<String> publicHostNames = new HashSet<>();
  private TreeMap<String, String> componentLevelParams = new TreeMap<>();
  private TreeMap<String, String> commandParams = new TreeMap<>();
  private State lastComponentState;

  private TopologyComponent() {
  }

  public static Builder newBuilder() {
    return new TopologyComponent().new Builder();
  }

  public class Builder {
    private Builder() {

    }

    public Builder setComponentName(String componentName) {
      TopologyComponent.this.setComponentName(componentName);
      return this;
    }

    public Builder setServiceName(String serviceName) {
      TopologyComponent.this.setServiceName(serviceName);
      return this;
    }

    public Builder setDisplayName(String displayName) {
      TopologyComponent.this.setDisplayName(displayName);
      return this;
    }

    public Builder setVersion(String version) {
      TopologyComponent.this.setVersion(version);
      return this;
    }

    public Builder setHostIdentifiers(Set<Long> hostIds, Set<String> hostNames) {
      TopologyComponent.this.setHostIds(hostIds);
      TopologyComponent.this.setHostNames(hostNames);
      return this;
    }

    public Builder setPublicHostNames(Set<String> publicHostNames) {
      TopologyComponent.this.setPublicHostNames(publicHostNames);
      return this;
    }

    public Builder setComponentLevelParams(TreeMap<String, String> componentLevelParams) {
      TopologyComponent.this.setComponentLevelParams(componentLevelParams);
      return this;
    }

    public Builder setCommandParams(TreeMap<String, String> commandParams) {
      TopologyComponent.this.setCommandParams(commandParams);
      return this;
    }

    public Builder setLastComponentState(State lastComponentState) {
      TopologyComponent.this.setLastComponentState(lastComponentState);
      return this;
    }

    public TopologyComponent build() {
      return TopologyComponent.this;
    }
  }

  public boolean updateComponent(TopologyComponent componentToUpdate) {
    boolean changed = false;
    //TODO will be a need to change to multi-instance usage
    if (componentToUpdate.getComponentName().equals(getComponentName())) {
      if (StringUtils.isNotEmpty(componentToUpdate.getVersion()) && !componentToUpdate.getVersion().equals(getVersion())) {
        setVersion(componentToUpdate.getVersion());
        changed = true;
      }
      if (CollectionUtils.isNotEmpty(componentToUpdate.getHostIds())) {
        if (hostIds == null) {
          hostIds = new HashSet<>();
        }
        changed |= hostIds.addAll(componentToUpdate.getHostIds());
      }
      if (CollectionUtils.isNotEmpty(componentToUpdate.getHostNames())) {
        if (hostNames == null) {
          hostNames = new HashSet<>();
        }
        changed |= hostNames.addAll(componentToUpdate.getHostNames());
      }
      if (CollectionUtils.isNotEmpty(componentToUpdate.getPublicHostNames())) {
        if (publicHostNames == null) {
          publicHostNames = new HashSet<>();
        }
        changed |= publicHostNames.addAll(componentToUpdate.getPublicHostNames());
      }
      changed |= mergeParams(componentLevelParams, componentToUpdate.getComponentLevelParams());
      changed |= mergeParams(commandParams, componentToUpdate.getCommandParams());
    }
    return changed;
  }

  private boolean mergeParams(TreeMap<String, String> currentParams, TreeMap<String, String> updateParams) {
    boolean changed = false;
    if (MapUtils.isNotEmpty(updateParams)) {
      for (Map.Entry<String, String> updateParam : updateParams.entrySet()) {
        String updateParamName = updateParam.getKey();
        String updateParamValue = updateParam.getValue();
        if (!currentParams.containsKey(updateParamName) ||
            !StringUtils.equals(currentParams.get(updateParamName), updateParamValue)) {
          currentParams.put(updateParamName, updateParamValue);
          changed = true;
        }
      }
    }
    return changed;
  }

  public boolean removeComponent(TopologyComponent componentToRemove) {
    boolean changed = false;
    if (componentToRemove.getComponentName().equals(getComponentName())) {
      if (CollectionUtils.isNotEmpty(componentToRemove.getHostIds())) {
        if (hostIds != null) {
          hostIds.removeAll(componentToRemove.getHostIds());
          changed = true;
        }
      }
      if (CollectionUtils.isNotEmpty(componentToRemove.getHostNames())) {
        if (hostNames != null) {
          hostNames.removeAll(componentToRemove.getHostNames());
          changed = true;
        }
      }
      if (CollectionUtils.isNotEmpty(componentToRemove.getPublicHostNames())) {
        if (publicHostNames != null) {
          publicHostNames.removeAll(componentToRemove.getPublicHostNames());
          changed = true;
        }
      }
    }
    return changed;
  }

  public  TopologyComponent deepCopy() {
    return TopologyComponent.newBuilder().setComponentName(getComponentName())
        .setDisplayName(getDisplayName())
        .setServiceName(getServiceName())
        .setComponentLevelParams(getComponentLevelParams() == null ? null : new TreeMap<>(getComponentLevelParams()))
        .setHostIdentifiers(getHostIds() == null ? null : new HashSet<>(getHostIds()),
            getHostNames() == null ? null : new HashSet<>(getHostNames()))
        .setPublicHostNames(getPublicHostNames() == null ? null : new HashSet<>(getPublicHostNames()))
        .setCommandParams(getCommandParams() == null ? null : new TreeMap<>(getCommandParams()))
        .build();
  }

  public String getComponentName() {
    return componentName;
  }

  public void setComponentName(String componentName) {
    this.componentName = componentName;
  }

  public String getServiceName() {
    return serviceName;
  }

  public void setServiceName(String serviceName) {
    this.serviceName = serviceName;
  }

  public String getVersion() {
    return version;
  }

  public void setVersion(String version) {
    this.version = version;
  }

  public Set<Long> getHostIds() {
    return hostIds;
  }

  public void setHostIds(Set<Long> hostIds) {
    this.hostIds = hostIds;
  }

  public void addHostId(Long hostId) {
    this.hostIds.add(hostId);
  }

  public void addHostName(String hostName) {
    this.hostNames.add(hostName);
  }

  public TreeMap<String, String> getComponentLevelParams() {
    return componentLevelParams;
  }

  public void setComponentLevelParams(TreeMap<String, String> componentLevelParams) {
    this.componentLevelParams = componentLevelParams;
  }

  public Set<String> getHostNames() {
    return hostNames;
  }

  public void setHostNames(Set<String> hostNames) {
    this.hostNames = hostNames;
  }

  public String getDisplayName() {
    return displayName;
  }

  public void setDisplayName(String displayName) {
    this.displayName = displayName;
  }

  public Set<String> getPublicHostNames() {
    return publicHostNames;
  }

  public void setPublicHostNames(Set<String> publicHostNames) {
    this.publicHostNames = publicHostNames;
  }

  public TreeMap<String, String> getCommandParams() {
    return commandParams;
  }

  public void setCommandParams(TreeMap<String, String> commandParams) {
    this.commandParams = commandParams;
  }

  public State getLastComponentState() {
    return lastComponentState;
  }

  public void setLastComponentState(State lastComponentState) {
    this.lastComponentState = lastComponentState;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    TopologyComponent that = (TopologyComponent) o;

    if (!componentName.equals(that.componentName)) return false;
    return serviceName.equals(that.serviceName);
  }

  @Override
  public int hashCode() {
    int result = componentName.hashCode();
    result = 31 * result + serviceName.hashCode();
    return result;
  }
}
