/*
 * Copyright 2017 LinkedIn Corp. All rights reserved.
 *
 * Licensed 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.
 */
package com.github.ambry.clustermap;

import java.io.IOException;
import java.util.ArrayList;
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 org.apache.helix.HelixAdmin;
import org.apache.helix.model.ClusterConstraints;
import org.apache.helix.model.ConstraintItem;
import org.apache.helix.model.ExternalView;
import org.apache.helix.model.HelixConfigScope;
import org.apache.helix.model.IdealState;
import org.apache.helix.model.InstanceConfig;
import org.apache.helix.model.StateModelDefinition;


/**
 * Mock implementation of {@link HelixAdmin} which stores all information internally.
 */
class MockHelixAdmin implements HelixAdmin {
  private String clusterName;
  private final Map<String, InstanceConfig> instanceNameToinstanceConfigs = new HashMap<>();
  private final Map<String, IdealState> resourcesToIdealStates = new HashMap<>();
  private final Set<String> upInstances = new HashSet<>();
  private final Set<String> downInstances = new HashSet<>();
  private long totalDiskCount = 0;
  private final List<MockHelixManager> helixManagersForThisAdmin = new ArrayList<>();
  private Map<String, Set<String>> partitionToInstances = new HashMap<>();
  private long totalDiskCapacity;

  @Override
  public List<String> getClusters() {
    return Collections.singletonList(clusterName);
  }

  @Override
  public boolean addCluster(String clusterName) {
    if (this.clusterName == null) {
      this.clusterName = clusterName;
      return true;
    } else {
      throw new IllegalStateException("A cluster has already been added");
    }
  }

  @Override
  public void addStateModelDef(String clusterName, String stateModelDef, StateModelDefinition record) {
  }

  @Override
  public void addInstance(String clusterName, InstanceConfig instanceConfig) {
    instanceNameToinstanceConfigs.put(instanceConfig.getInstanceName(), instanceConfig);
    upInstances.add(instanceConfig.getInstanceName());
    Map<String, Map<String, String>> diskInfos = instanceConfig.getRecord().getMapFields();
    totalDiskCount += diskInfos.size();
    for (Map<String, String> diskInfo : diskInfos.values()) {
      totalDiskCapacity += Long.valueOf(diskInfo.get(ClusterMapUtils.DISK_CAPACITY_STR));
    }
  }

  @Override
  public void addResource(String clusterName, String resourceName, IdealState idealstate) {
    resourcesToIdealStates.put(resourceName, idealstate);
    for (String partition : idealstate.getPartitionSet()) {
      if (partitionToInstances.get(partition) == null) {
        Set<String> instanceSet = new HashSet<>();
        partitionToInstances.put(partition, instanceSet);
      }
      partitionToInstances.get(partition).addAll(idealstate.getInstanceSet(partition));
    }
  }

  @Override
  public List<String> getResourcesInCluster(String clusterName) {
    List<String> resources = new ArrayList<>();
    resources.addAll(resourcesToIdealStates.keySet());
    return resources;
  }

  @Override
  public IdealState getResourceIdealState(String clusterName, String resourceName) {
    return resourcesToIdealStates.get(resourceName);
  }

  @Override
  public List<String> getInstancesInCluster(String clusterName) {
    List<String> instances = new ArrayList<>();
    instances.addAll(instanceNameToinstanceConfigs.keySet());
    return instances;
  }

  @Override
  public InstanceConfig getInstanceConfig(String clusterName, String instanceName) {
    return instanceNameToinstanceConfigs.get(instanceName);
  }

  @Override
  public boolean setInstanceConfig(String clusterName, String instanceName, InstanceConfig instanceConfig) {
    instanceNameToinstanceConfigs.put(instanceName, instanceConfig);
    return true;
  }

  /**
   * Associate the given Helix manager with this admin.
   * @param helixManager the {@link MockHelixManager} to associate this admin with.
   */
  void addHelixManager(MockHelixManager helixManager) {
    helixManagersForThisAdmin.add(helixManager);
  }

  /**
   * @return all instances registered via this Helix admin that are up.
   */
  List<String> getUpInstances() {
    List<String> upList = new ArrayList<>();
    upList.addAll(upInstances);
    return upList;
  }

  /**
   * Bring up the given instance (that was registered via this Helix admin).
   * @param instance the instance to be brought up.
   */
  void bringInstanceUp(String instance) {
    downInstances.remove(instance);
    upInstances.add(instance);
    triggerLiveInstanceChangeNotification();
  }

  /**
   * @return all instances registered via this Helix admin that are down.
   */
  List<String> getDownInstances() {
    List<String> downList = new ArrayList<>();
    downList.addAll(downInstances);
    return downList;
  }

  /**
   * Bring down the given instance (that was registered via this Helix admin).
   * @param instance the instance to be brought down.
   */
  void bringInstanceDown(String instance) {
    downInstances.add(instance);
    upInstances.remove(instance);
    triggerLiveInstanceChangeNotification();
  }

  /**
   * Trigger a live instance change notification.
   */
  private void triggerLiveInstanceChangeNotification() {
    for (MockHelixManager helixManager : helixManagersForThisAdmin) {
      helixManager.triggerLiveInstanceNotification();
    }
  }

  /**
   * @return a list of all partitions registered via this admin.
   */
  Set<String> getPartitions() {
    return partitionToInstances.keySet();
  }

  /**
   * @return all partitions registered via this Helix admin.
   */
  Set<String> getUpPartitions() {
    Set<String> upPartitions = new HashSet<>();
    for (Map.Entry<String, Set<String>> entry : partitionToInstances.entrySet()) {
      boolean up = true;
      for (String instance : entry.getValue()) {
        if (!getUpInstances().contains(instance)) {
          up = false;
          break;
        }
      }
      if (up) {
        upPartitions.add(entry.getKey());
      }
    }
    return upPartitions;
  }

  /**
   * @return the count of disks registered via this admin.
   */
  long getTotalDiskCount() {
    return totalDiskCount;
  }

  /**
   * @param instanceName the instance on which this operation is being done.
   * @return the count of disks registered for the given node via this admin.
   */
  long getDiskCountOnNode(String instanceName) {
    InstanceConfig instanceConfig = instanceNameToinstanceConfigs.get(instanceName);
    return instanceConfig.getRecord().getMapFields().size();
  }

  /**
   * @return the total capacity across all disks on all nodes registered via this admin.
   */
  long getTotalDiskCapacity() {
    return totalDiskCapacity;
  }

  // ***************************************
  // Not implemented. Implement as required.
  // ***************************************

  @Override
  public List<String> getResourcesInClusterWithTag(String clusterName, String tag) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public boolean addCluster(String clusterName, boolean recreateIfExists) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addClusterToGrandCluster(String clusterName, String grandCluster) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef,
      String rebalancerMode) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef,
      String rebalancerMode, String rebalanceStrategy) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef,
      String rebalancerMode, int bucketSize) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef,
      String rebalancerMode, int bucketSize, int maxPartitionsPerInstance) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addResource(String clusterName, String resourceName, int numPartitions, String stateModelRef,
      String rebalancerMode, String rebalanceStrategy, int bucketSize, int maxPartitionsPerInstance) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void dropInstance(String clusterName, InstanceConfig instanceConfig) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void setResourceIdealState(String clusterName, String resourceName, IdealState idealState) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void enableInstance(String clusterName, String instanceName, boolean enabled) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void enableResource(String clusterName, String resourceName, boolean enabled) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void enablePartition(boolean enabled, String clusterName, String instanceName, String resourceName,
      List<String> partitionNames) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void enableCluster(String clusterName, boolean enabled) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void resetPartition(String clusterName, String instanceName, String resourceName,
      List<String> partitionNames) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void resetInstance(String clusterName, List<String> instanceNames) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void resetResource(String clusterName, List<String> resourceNames) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addStateModelDef(String clusterName, String stateModelDef, StateModelDefinition record,
      boolean recreateIfExists) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void dropResource(String clusterName, String resourceName) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public List<String> getStateModelDefs(String clusterName) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public StateModelDefinition getStateModelDef(String clusterName, String stateModelName) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public ExternalView getResourceExternalView(String clusterName, String resourceName) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void dropCluster(String clusterName) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void setConfig(HelixConfigScope scope, Map<String, String> properties) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void removeConfig(HelixConfigScope scope, List<String> keys) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public Map<String, String> getConfig(HelixConfigScope scope, List<String> keys) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public List<String> getConfigKeys(HelixConfigScope scope) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void rebalance(String clusterName, String resourceName, int replica) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addIdealState(String clusterName, String resourceName, String idealStateFile) throws IOException {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addStateModelDef(String clusterName, String stateModelDefName, String stateModelDefFile)
      throws IOException {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void setConstraint(String clusterName, ClusterConstraints.ConstraintType constraintType, String constraintId,
      ConstraintItem constraintItem) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void removeConstraint(String clusterName, ClusterConstraints.ConstraintType constraintType,
      String constraintId) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public ClusterConstraints getConstraints(String clusterName, ClusterConstraints.ConstraintType constraintType) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void rebalance(String clusterName, IdealState currentIdealState, List<String> instanceNames) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void rebalance(String clusterName, String resourceName, int replica, List<String> instances) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void rebalance(String clusterName, String resourceName, int replica, String keyPrefix, String group) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public List<String> getInstancesInClusterWithTag(String clusterName, String tag) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void addInstanceTag(String clusterName, String instanceName, String tag) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void removeInstanceTag(String clusterName, String instanceName, String tag) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void setInstanceZoneId(String clusterName, String instanceName, String zoneId) {
    throw new IllegalStateException("Not implemented");
  }

  @Override
  public void close() {
    // no-op.
  }
}

class MockHelixAdminFactory extends HelixAdminFactory {
  Map<String, MockHelixAdmin> helixAdminMap = new HashMap<>();

  @Override
  MockHelixAdmin getHelixAdmin(String zkAddr) {
    if (helixAdminMap.containsKey(zkAddr)) {
      return helixAdminMap.get(zkAddr);
    } else {
      MockHelixAdmin helixAdmin = new MockHelixAdmin();
      helixAdminMap.put(zkAddr, helixAdmin);
      return helixAdmin;
    }
  }

  Map<String, MockHelixAdmin> getAllHelixAdmins() {
    return helixAdminMap;
  }
}

