/*
 * 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.geode.management.internal.cli.domain;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionShortcut;
import org.apache.geode.cache.Scope;

/***
 * Data class which contains description of a region and provides the aggregated view of the region
 * Used by describe region command
 * 
 * 
 */
public class RegionDescription implements Serializable {

  private static final long serialVersionUID = 1L;
  private String name;
  private boolean isPartition;
  private boolean isPersistent;
  private boolean isReplicate;
  private boolean haslocalDataStorage;
  private boolean isLocal = false;
  private boolean isReplicatedProxy = false;;
  private boolean isAccessor = false;


  // Common Non Default Attributes
  private Map<String, String> cndRegionAttributes;
  private Map<String, String> cndPartitionAttributes;
  private Map<String, String> cndEvictionAttributes;

  private Map<String, RegionDescriptionPerMember> regionDescPerMemberMap = null;
  private Scope scope;
  private DataPolicy dataPolicy;

  public RegionDescription() {

  }

  public DataPolicy getDataPolicy() {
    return this.dataPolicy;
  }

  public Scope getScope() {
    return this.scope;
  }

  /**
   * Adds the RegionDescription per member to the aggregated view
   * 
   * @param regionDescPerMember
   * 
   */
  public boolean add(RegionDescriptionPerMember regionDescPerMember) {
    boolean isAdded = false;

    if (regionDescPerMemberMap == null) {
      regionDescPerMemberMap = new HashMap<String, RegionDescriptionPerMember>();
      regionDescPerMemberMap.put(regionDescPerMember.getHostingMember(), regionDescPerMember);
      this.scope = regionDescPerMember.getScope();
      this.dataPolicy = regionDescPerMember.getDataPolicy();
      this.name = regionDescPerMember.getName();
      isPartition = this.dataPolicy.withPartitioning();
      isPersistent = this.dataPolicy.withPersistence();
      isReplicate = this.dataPolicy.withReplication();
      haslocalDataStorage = this.dataPolicy.withStorage();
      isLocal = this.scope.isLocal();
      isAccessor = regionDescPerMember.isAccessor();
      // COPY
      this.cndRegionAttributes = new HashMap<String, String>();
      this.cndRegionAttributes.putAll(regionDescPerMember.getNonDefaultRegionAttributes());

      this.cndPartitionAttributes = new HashMap<String, String>();
      this.cndPartitionAttributes.putAll(regionDescPerMember.getNonDefaultPartitionAttributes());

      this.cndEvictionAttributes = new HashMap<String, String>();
      this.cndEvictionAttributes.putAll(regionDescPerMember.getNonDefaultEvictionAttributes());

      if (this.dataPolicy.equals(DataPolicy.EMPTY) && this.scope.equals(Scope.DISTRIBUTED_ACK)) {
        isReplicatedProxy = true;
      }

      // Don't have to show the scope for PR's


      isAdded = true;
    } else {
      if (this.scope.equals(regionDescPerMember.getScope())
          && this.name.equals(regionDescPerMember.getName())
          && this.dataPolicy.equals(regionDescPerMember.getDataPolicy())
          && this.isAccessor == regionDescPerMember.isAccessor()) {

        regionDescPerMemberMap.put(regionDescPerMember.getHostingMember(), regionDescPerMember);
        findCommon(cndRegionAttributes, regionDescPerMember.getNonDefaultRegionAttributes());
        findCommon(cndEvictionAttributes, regionDescPerMember.getNonDefaultEvictionAttributes());
        findCommon(cndPartitionAttributes, regionDescPerMember.getNonDefaultPartitionAttributes());

        isAdded = true;
      }
    }
    return isAdded;
  }

  private void findCommon(Map<String, String> commonNdMap, Map<String, String> incomingNdMap) {
    // First get the intersection of the both maps

    Set<String> commonNdKeySet = commonNdMap.keySet();
    Set<String> incomingNdKeySet = incomingNdMap.keySet();

    commonNdKeySet.retainAll(incomingNdKeySet);

    // Now compare the values
    // Take a copy of the set to avoid a CME
    Iterator<String> commonKeysIter = (new HashSet<String>(commonNdKeySet)).iterator();

    while (commonKeysIter.hasNext()) {
      String attribute = commonKeysIter.next();
      String commonNdValue = commonNdMap.get(attribute);
      String incomingNdValue = incomingNdMap.get(attribute);

      if (commonNdValue != null) {
        if (!commonNdValue.equals(incomingNdValue)) {
          // Remove it from the commonNdMa
          commonNdMap.remove(attribute);
        }
      } else {
        if (incomingNdValue != null) {
          commonNdMap.remove(attribute);
        }
      }
    }
  }


  @Override
  public boolean equals(Object obj) {
    if (obj instanceof RegionDescription) {
      RegionDescription regionDesc = (RegionDescription) obj;

      return this.getName().equals(regionDesc.getName()) && this.scope.equals(regionDesc.getScope())
          && this.dataPolicy.equals(regionDesc.getDataPolicy());
    }
    return true;
  }

  public int hashCode() {
    return 42; // any arbitrary constant will do

  }

  public Set<String> getHostingMembers() {
    return regionDescPerMemberMap.keySet();
  }

  public String getName() {
    return this.name;
  }

  public boolean isPersistent() {
    return this.isPersistent;
  }

  public boolean isPartition() {
    return this.isPartition;
  }

  public boolean isReplicate() {
    return this.isReplicate;
  }

  public boolean hasLocalStorage() {
    return this.haslocalDataStorage;
  }

  public boolean isLocal() {
    return this.isLocal;
  }

  public boolean isReplicatedProxy() {
    return this.isReplicatedProxy;
  }

  public boolean isAccessor() {
    return this.isAccessor;
  }


  /***
   * Get
   * 
   * @return Map containing attribute name and its associated value
   */
  public Map<String, String> getCndRegionAttributes() {
    return this.cndRegionAttributes;
  }

  /***
   * Gets the common non-default Eviction Attributes
   * 
   * @return Map containing attribute name and its associated value
   */
  public Map<String, String> getCndEvictionAttributes() {
    return this.cndEvictionAttributes;
  }

  /***
   * Gets the common non-default PartitionAttributes
   * 
   * @return Map containing attribute name and its associated value
   */
  public Map<String, String> getCndPartitionAttributes() {
    return this.cndPartitionAttributes;
  }

  public Map<String, RegionDescriptionPerMember> getRegionDescriptionPerMemberMap() {
    return this.regionDescPerMemberMap;
  }


  public String toString() {
    StringBuilder sb = new StringBuilder();

    return sb.toString();
  }

}
