/*
 * 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.admin.jmx.internal;

import static org.apache.geode.cache.Region.SEPARATOR;
import static org.apache.geode.distributed.ConfigurationProperties.ENABLE_TIME_STATISTICS;
import static org.apache.geode.distributed.ConfigurationProperties.STATISTIC_SAMPLING_ENABLED;

import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;

import javax.management.InstanceNotFoundException;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.ReflectionException;

import mx4j.AbstractDynamicMBean;
import org.apache.logging.log4j.Logger;

import org.apache.geode.admin.AdminDistributedSystem;
import org.apache.geode.admin.AdminException;
import org.apache.geode.admin.CacheVm;
import org.apache.geode.admin.ConfigurationParameter;
import org.apache.geode.admin.GemFireMemberStatus;
import org.apache.geode.admin.RegionSubRegionSnapshot;
import org.apache.geode.admin.StatisticResource;
import org.apache.geode.admin.SystemMember;
import org.apache.geode.admin.SystemMemberCacheServer;
import org.apache.geode.admin.jmx.Agent;
import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.cache.InterestPolicy;
import org.apache.geode.cache.SubscriptionAttributes;
import org.apache.geode.internal.GemFireVersion;
import org.apache.geode.internal.admin.remote.ClientHealthStats;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.util.internal.GeodeGlossary;

/**
 * This class uses the JMX Attributes/Operations that use (return/throw) GemFire types. This is the
 * single MBean accessible with ObjectName string {@link MemberInfoWithStatsMBean#MBEAN_NAME}}. This
 * MBean can be used to retrieve the all member details as plain java types.
 *
 * This MBean also acts as a Notification Hub for all the Notifications that are defined for Admin
 * Distributed System.
 *
 *
 * @since GemFire 6.5
 */
@Deprecated
public class MemberInfoWithStatsMBean extends AbstractDynamicMBean implements NotificationEmitter {
  private static final Logger logger = LogService.getLogger();

  /* constants defining max no of attributes/operations/notifications */
  private static final int MAX_ATTRIBUTES_COUNT = 3;
  private static final int MAX_OPERATIONS_COUNT = 3;
  private static final int MAX_NOTIFICATIONS_COUNT = 9;

  private static final String NOT_AVAILABLE_STR = "N/A";
  private static final String NOT_AVAILABLE = null;
  private static final Number NOT_AVAILABLE_NUMBER = null;

  /*
   * String constant used for a region that is used on admin side just as a root for rootRegions
   * defined on the member
   */
  private static final String PLACE_HOLDER_ROOT_REGION = SEPARATOR + "Root" + SEPARATOR;

  /* String that are used to form QueryExp/ObjectName for querying MBeanServer */
  private static final String REGION_QUERY_EXPRESSION = "*GemFire.Cache*:*,owner={0},type=Region";
  private static final String STATS_QUERY_EXPRESSION = "*GemFire.Statistic*:*,source={0},name={1}";

  /** mbean name string for this MBean */
  /* default */static final String MBEAN_NAME = "GemFire:type=MemberInfoWithStatsMBean";

  /** ObjectName handle for this MBean */
  private final ObjectName objectName;

  /** version of the GemFire Enterprise system that is running */
  private final String version;
  private int refreshInterval;
  private String id;

  private final Agent agent;
  private AdminDistributedSystemJmxImpl adminDSJmx;

  private final NotificationForwarder forwarder;
  private boolean isInitialized;// needs synchronization?

  /**
   * Default Constructor
   *
   * @param agent Admin Agent instance
   * @throws OperationsException if ObjectName can't be formed for this MBean
   */
  MemberInfoWithStatsMBean(Agent agent)
      throws OperationsException, MBeanRegistrationException, AdminException {
    this.agent = agent;
    objectName = ObjectName.getInstance(MBEAN_NAME);
    version = GemFireVersion.getGemFireVersion();
    refreshInterval = -1;
    id = NOT_AVAILABLE_STR;
    forwarder = new NotificationForwarder(agent.getMBeanServer());
  }

  /**
   * Returns attributes defined for this MBean as an array of MBeanAttributeInfo objects.
   *
   * @return attributes defined as an array of MBeanAttributeInfo objects.
   */
  @Override
  protected MBeanAttributeInfo[] createMBeanAttributeInfo() {
    MBeanAttributeInfo[] attributesInfo = new MBeanAttributeInfo[MAX_ATTRIBUTES_COUNT];

    /*
     * First letter in attribute name has to be 'V' so that getVersion is called. With 'v' it looks
     * for getversion, same for others
     */
    attributesInfo[0] = new MBeanAttributeInfo("Version", String.class.getName(),
        "GemFire Enterprise Version", true, /* readable */
        false, /* writable */
        false);/* has getter with name like 'is****' */

    attributesInfo[1] = new MBeanAttributeInfo("RefreshInterval", String.class.getName(),
        "The interval (in seconds) between auto-polling for updating member & statistics resources. If this is '-1', it means the this MBean has not yet been initialized. First call to getMembers operation will initialize this MBean.",
        true, /* readable */
        false, /* writable */
        false);/* has getter with name like 'is****' */

    attributesInfo[2] = new MBeanAttributeInfo("Id", String.class.getName(),
        "Identifier of the GemFire Enterprise. If this is 'N/A', it means the this MBean has not yet been initialized. First call to getMembers operation will initialize this MBean.",
        true, /* readable */
        false, /* writable */
        false);/* has getter with name like 'is****' */


    return attributesInfo;
  }

  /**
   * Returns operations defined for this MBean as an array of MBeanOperationInfo objects.
   *
   * @return operations defined as an array of MBeanOperationInfo objects.
   */
  @Override
  protected MBeanOperationInfo[] createMBeanOperationInfo() {
    MBeanOperationInfo[] operationsInfo = new MBeanOperationInfo[MAX_OPERATIONS_COUNT];

    operationsInfo[0] = new MBeanOperationInfo("getMembers",
        "Returns ids as strings for all the members - Application Peers & Cache Servers.",
        new MBeanParameterInfo[] {}, String[].class.getName(), MBeanOperationInfo.ACTION_INFO);

    MBeanParameterInfo[] getMemberDetailsArgs = new MBeanParameterInfo[1];
    getMemberDetailsArgs[0] = new MBeanParameterInfo("memberId", String.class.getName(),
        "Id of the member for all the details are to be retrieved.");
    operationsInfo[1] =
        new MBeanOperationInfo("getMemberDetails", "Returns details for a given member",
            getMemberDetailsArgs, Map.class.getName(), MBeanOperationInfo.ACTION_INFO);

    /*
     * For retrieving ObjectNames of existing Region MBeans, MBeanServerConnection.queryMBeans(),
     * could be called
     */
    MBeanParameterInfo[] getRegionSnapArgs = new MBeanParameterInfo[1];
    getRegionSnapArgs[0] = new MBeanParameterInfo("memberId", String.class.getName(),
        "Id of the member on which we want to discover all the region MBean.");
    operationsInfo[2] = new MBeanOperationInfo("getRegions",
        "Returns a java.util.Map of details of regions on a member", getRegionSnapArgs,
        Map.class.getName(), MBeanOperationInfo.ACTION_INFO);


    return operationsInfo;
  }

  /**
   * Returns notifications defined for this MBean as an array of MBeanNotificationInfo objects.
   *
   * @return notification definitions as an array of MBeanNotificationInfo objects.
   */
  @Override
  protected MBeanNotificationInfo[] createMBeanNotificationInfo() {
    MBeanNotificationInfo[] notificationsInfo = new MBeanNotificationInfo[MAX_NOTIFICATIONS_COUNT];

    String[] notificationTypes = new String[] {AdminDistributedSystemJmxImpl.NOTIF_MEMBER_JOINED};
    notificationsInfo[0] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A GemFire manager, cache, or other member has joined this distributed system.");

    notificationTypes = new String[] {AdminDistributedSystemJmxImpl.NOTIF_MEMBER_LEFT};
    notificationsInfo[1] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A GemFire manager, cache, or other member has left the distributed system.");

    notificationTypes = new String[] {AdminDistributedSystemJmxImpl.NOTIF_MEMBER_CRASHED};
    notificationsInfo[2] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A member of this distributed system has crashed instead of leaving cleanly.");

    notificationTypes = new String[] {AdminDistributedSystemJmxImpl.NOTIF_ALERT};
    notificationsInfo[3] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A member of this distributed system has generated an alert.");

    notificationTypes = new String[] {AdminDistributedSystemJmxImpl.NOTIF_ADMIN_SYSTEM_DISCONNECT};
    notificationsInfo[4] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A GemFire manager, cache, or other member has joined this distributed system.");

    notificationTypes = new String[] {SystemMemberJmx.NOTIF_CACHE_CREATED};
    notificationsInfo[5] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A cache got created on a member of this distributed system.");

    notificationTypes = new String[] {SystemMemberJmx.NOTIF_CACHE_CLOSED};
    notificationsInfo[6] = new MBeanNotificationInfo(notificationTypes,
        Notification.class.getName(), "A cache is closed on a member of this distributed system.");

    notificationTypes = new String[] {SystemMemberJmx.NOTIF_REGION_CREATED};
    notificationsInfo[7] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A region is created in a cache on a member of this distributed system.");

    notificationTypes = new String[] {SystemMemberJmx.NOTIF_REGION_LOST};
    notificationsInfo[8] =
        new MBeanNotificationInfo(notificationTypes, Notification.class.getName(),
            "A region was removed from a cache on a member of this distributed system.");

    return notificationsInfo;
  }

  /**
   *
   * @return ObjectName of this MBean
   */
  /* default */ ObjectName getObjectName() {
    return objectName;
  }

  /**
   * Returns the version of the GemFire Enterprise instance as a string.
   *
   * @return GemFire Enterprise version string derived from {@link GemFireVersion}
   */
  /* getter for attribute - Version */
  public String getVersion() {
    return version;
  }

  /**
   * @return the refreshInterval
   */
  public int getRefreshInterval() {
    return refreshInterval;
  }

  /**
   * @return the id
   */
  public String getId() {
    return id;
  }

  /**
   * Connects the Admin Agent in the DS
   *
   * @return AdminDistributedSystem MBean ObjectName
   * @throws OperationsException if connection to the DS fails
   * @throws AdminException if connection to the DS fails
   */
  private ObjectName connectToSystem() throws OperationsException, AdminException {
    ObjectName adminDsObjName = agent.connectToSystem();

    AdminDistributedSystem adminDS = agent.getDistributedSystem();
    if (adminDSJmx == null && adminDS instanceof AdminDistributedSystemJmxImpl) {// instanceof
                                                                                 // checks for null
      adminDSJmx = (AdminDistributedSystemJmxImpl) adminDS;
      refreshInterval = adminDSJmx.getRefreshInterval();
      id = adminDSJmx.getId();
      forwarder.registerNotificationListener(adminDSJmx.getObjectName());
    }

    return adminDsObjName;
  }

  /**
   *
   * @return SystemMemberJmx instance for given memberId
   */
  private SystemMemberJmx findMember(String memberId) throws AdminException {
    SystemMemberJmx foundMember = null;

    if (agent.isConnected()) {
      SystemMember[] members = adminDSJmx.getSystemMemberApplications();
      for (SystemMember app : members) {
        if (app.getId().equals(memberId)) {
          foundMember = (SystemMemberJmx) app;
          break;
        }
      }

      if (foundMember == null) {
        members = adminDSJmx.getCacheVms();
        for (SystemMember cacheVm : members) {
          if (cacheVm.getId().equals(memberId)) {
            foundMember = (SystemMemberJmx) cacheVm;
            break;
          }
        }
      }
    }

    return foundMember;
  }

  /**
   * Return ObjectNames for all the Member MBeans in the DS.
   *
   * @return Array of ObjectNames of all Member MBeans
   * @throws OperationsException if (1)agent could not connect in the DS OR (2)Notification Listener
   *         could not be registered for the Admin DS MBean OR (3)fails to retrieve information from
   *         Admin DS
   */
  public String[] getMembers() throws OperationsException {
    String[] members = new String[0];

    try {
      if (!isInitialized) {
        initializeAll(); // initialize if not yet
      }

      if (adminDSJmx != null) {
        CacheVm[] cacheVms = adminDSJmx.getCacheVms();
        SystemMember[] appVms = adminDSJmx.getSystemMemberApplications();

        List<String> membersList = new ArrayList<>();
        if (cacheVms != null && cacheVms.length != 0) {
          for (SystemMember cacheVm : cacheVms) {
            membersList.add(cacheVm.getId());
          }
        }
        if (appVms != null && appVms.length != 0) {
          for (SystemMember appVm : appVms) {
            membersList.add(appVm.getId());
          }
        }
        members = new String[membersList.size()];
        members = membersList.toArray(members);
      }
    } catch (AdminException e) {
      logger.warn("Exception occurred for operation: getMembers",
          e);
      throw new OperationsException(e.getMessage());
    } catch (Exception e) {
      logger.warn(
          "Exception occurred for operation: getMembers",
          e);
      throw new OperationsException(e.getMessage());
    }

    return members;
  }

  /**
   * Returns information including ObjectNames for all regions on a member with given member id.
   *
   * @param memberId member identifier as a String
   * @return Map of details of all regions on a member with given id
   * @throws OperationsException if fails to retrieve the regions information
   */
  public Map<String, Map<String, ?>> getRegions(String memberId) throws OperationsException {
    Map<String, Map<String, ?>> regionsInfo = new LinkedHashMap<>();

    if (memberId != null) {
      try {
        SystemMemberJmx foundMember = findMember(memberId);
        if (foundMember != null) {
          SystemMemberCacheJmxImpl cache = (SystemMemberCacheJmxImpl) foundMember.getCache();
          if (cache != null) {
            Map<String, ObjectName> existingRegionMbeans =
                getExistingRegionMbeansFullPaths(memberId);
            // TODO: this is in-efficient
            // Can a region.create JMX notification be used?
            regionsInfo = getAllRegionsDetails(cache, existingRegionMbeans);
            existingRegionMbeans.clear();
          }
        }
      } catch (AdminException e) {
        logger.warn(String.format("Exception occurred for operation: %s for member: %s",
            "getRegions", memberId),
            e);
        throw new OperationsException(e.getMessage());
      } catch (Exception e) {
        logger.warn(String.format("Exception occurred for operation: %s for member: %s",
            "getRegions", memberId),
            e);
        throw new OperationsException(e.getMessage());
      }
    }

    return regionsInfo;
  }

  /* **************************************************************************/
  /* ************* INITIALIZE THE ENTIRE ADMIN DS AT A TIME *******************/
  /* **************************************************************************/
  /**
   * Initializes all the possible MBeans for all the members.
   *
   */
  private void initializeAll() throws OperationsException {
    try {
      connectToSystem();
      if (adminDSJmx != null) {
        // Members are already inited after connectToSystem. Now init Cache, Region & Stats MBeans
        SystemMember[] cacheVms = adminDSJmx.getCacheVms();
        for (final SystemMember cacheVm : cacheVms) {
          try {
            initializeCacheRegionsAndStats((SystemMemberJmx) cacheVm);
          } catch (AdminException e) {
            logger.info(String.format(
                "Exception occurred while intializing : %s. Contiuning with next  ...",
                cacheVm.getId()),
                e);
          }
        }
        SystemMember[] appVms = adminDSJmx.getSystemMemberApplications();
        for (final SystemMember appVm : appVms) {
          try {
            initializeCacheRegionsAndStats((SystemMemberJmx) appVm);
          } catch (AdminException e) {
            logger.info(String.format(
                "Exception occurred while intializing : %s. Contiuning with next  ...",
                appVm.getId()),
                e);
          }
        }
      }
    } catch (AdminException e) {
      logger.warn("Exception occurred while intializing.", e);
      throw new OperationsException(e.getMessage());
    } catch (Exception e) {
      logger.warn("Exception occurred while intializing.", e);
      throw new OperationsException(e.getMessage());
    }

    isInitialized = true;
  }

  /**
   * Initializes Cache, Regions & Statistics Types MBeans for the given Member.
   *
   * @param memberJmx Member Mbean instance
   * @throws OperationsException if fails to initialize required MBeans
   * @throws AdminException if fails to initialize required MBeans
   */
  private void initializeCacheRegionsAndStats(SystemMemberJmx memberJmx)
      throws OperationsException, AdminException {
    if (memberJmx != null) {
      SystemMemberCacheJmxImpl cache = (SystemMemberCacheJmxImpl) memberJmx.getCache();
      if (cache != null) {
        RegionSubRegionSnapshot regionSnapshot = cache.getRegionSnapshot();
        initializeRegionSubRegions(cache, regionSnapshot);
      }
      initStats(memberJmx);
    }
  }

  /**
   * Initializes statistics for a member with the given mbean.
   *
   * @param memberJmx Member Mbean instance
   * @throws AdminException if fails to initialize required statistic MBeans
   */
  private void initStats(SystemMemberJmx memberJmx) throws AdminException {
    StatisticResource[] statResources = memberJmx.getStats();
    for (StatisticResource statResource : statResources) {
      statResource.getStatistics();
    }
  }

  /**
   * Initializes all regions & its subregions using the Cache MBean and the RegionSubRegionSnapshot
   * for this cache MBean.
   *
   * @param cache Cache MBean resource
   * @param regionSnapshot RegionSubRegionSnapshot instance for the cache
   * @throws MalformedObjectNameException if fails to initialize the region MBean
   * @throws AdminException if fails to initialize the region MBean
   */
  @SuppressWarnings("rawtypes")
  private void initializeRegionSubRegions(SystemMemberCacheJmxImpl cache,
      RegionSubRegionSnapshot regionSnapshot) throws MalformedObjectNameException, AdminException {
    String fullPath = regionSnapshot.getFullPath();
    if (!fullPath.equals(PLACE_HOLDER_ROOT_REGION)) {
      fullPath = fullPath.substring(PLACE_HOLDER_ROOT_REGION.length() - 1);

      cache.manageRegion(fullPath);
    }

    Set subRegionSnapshots = regionSnapshot.getSubRegionSnapshots();

    for (final Object subRegionSnapshot : subRegionSnapshots) {
      RegionSubRegionSnapshot subRegion = (RegionSubRegionSnapshot) subRegionSnapshot;
      try {
        initializeRegionSubRegions(cache, subRegion);
      } catch (AdminException e) {
        logger.info(
            String.format("Exception occurred while intializing : %s. Contiuning with next  ...",
                subRegion.getFullPath()),
            e);
      }
    }
  }


  /* **************************************************************************/
  /* ********************** EVERYTHING HYPERIC NEEDS **************************/
  /* **************************************************************************/

  /* constants defined that could be used simply retrieve needed info from Map */
  private static final String TYPE_NAME_CACHESERVER = "Cache Server";
  private static final String TYPE_NAME_APPLICATION = "Application Peer";
  /*
   * NOTE - (My Understanding about the followings - abhishek) 1. CacheVM - a VM started using Cache
   * Server Launcher. This is considered to be a dedicated cache VM because there is only GemFire
   * Cache code running here. 2. ApplicationVM - a VM started with a written code using APIs and we
   * can not guarantee that there will be ONLY GemFire code running in this VM. 3. Cache Server -
   * Responsible for serving requests from the clients. There could be multiple of these per Cache
   * and hence per VM - one of 1 or 2 above. These could be specified by <cache-server> (or
   * deprecated <bridge-server>) element(s) in the cache-xml file or using an API
   * Cache.addCacheServer().
   */

  private static final String MEMBER_ID = GeodeGlossary.GEMFIRE_PREFIX + "member.id.string";
  private static final String MEMBER_NAME =
      GeodeGlossary.GEMFIRE_PREFIX + "member.name.string";
  private static final String MEMBER_HOST =
      GeodeGlossary.GEMFIRE_PREFIX + "member.host.string";
  private static final String MEMBER_PORT = GeodeGlossary.GEMFIRE_PREFIX + "member.port.int";
  private static final String MEMBER_UPTIME =
      GeodeGlossary.GEMFIRE_PREFIX + "member.uptime.long";
  private static final String MEMBER_CLIENTS =
      GeodeGlossary.GEMFIRE_PREFIX + "member.clients.map";
  private static final String MEMBER_REGIONS =
      GeodeGlossary.GEMFIRE_PREFIX + "member.regions.map";
  private static final String MEMBER_TYPE =
      GeodeGlossary.GEMFIRE_PREFIX + "member.type.string";
  private static final String IS_SERVER =
      GeodeGlossary.GEMFIRE_PREFIX + "member.isserver.boolean";
  private static final String IS_GATEWAY =
      GeodeGlossary.GEMFIRE_PREFIX + "member.isgateway.boolean";

  private static final String MEMBER_STATSAMPLING_ENABLED =
      GeodeGlossary.GEMFIRE_PREFIX + "member.config.statsamplingenabled.boolean";
  private static final String MEMBER_TIME_STATS_ENABLED =
      GeodeGlossary.GEMFIRE_PREFIX + "member.config.timestatsenabled.boolean";

  private static final String STATS_PROCESSCPUTIME =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.processcputime.long";
  private static final String STATS_CPUS =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.cpus.int";
  private static final String STATS_USEDMEMORY =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.usedmemory.long";
  private static final String STATS_MAXMEMORY =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.maxmemory.long";
  private static final String STATS_GETS =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.gets.int";
  private static final String STATS_GETTIME =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.gettime.long";
  private static final String STATS_PUTS =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.puts.int";
  private static final String STATS_PUTTIME =
      GeodeGlossary.GEMFIRE_PREFIX + "member.stat.puttime.long";

  private static final String REGION_NAME =
      GeodeGlossary.GEMFIRE_PREFIX + "region.name.string";
  private static final String REGION_PATH =
      GeodeGlossary.GEMFIRE_PREFIX + "region.path.string";
  private static final String REGION_SCOPE =
      GeodeGlossary.GEMFIRE_PREFIX + "region.scope.string";
  private static final String REGION_DATAPOLICY =
      GeodeGlossary.GEMFIRE_PREFIX + "region.datapolicy.string";
  private static final String REGION_INTERESTPOLICY =
      GeodeGlossary.GEMFIRE_PREFIX + "region.interestpolicy.string";
  private static final String REGION_ENTRYCOUNT =
      GeodeGlossary.GEMFIRE_PREFIX + "region.entrycount.int";
  private static final String REGION_DISKATTRS =
      GeodeGlossary.GEMFIRE_PREFIX + "region.diskattrs.string";

  private static final String CLIENT_ID = GeodeGlossary.GEMFIRE_PREFIX + "client.id.string";
  private static final String CLIENT_NAME =
      GeodeGlossary.GEMFIRE_PREFIX + "client.name.string";
  private static final String CLIENT_HOST =
      GeodeGlossary.GEMFIRE_PREFIX + "client.host.string";
  private static final String CLIENT_QUEUESIZE =
      GeodeGlossary.GEMFIRE_PREFIX + "client.queuesize.int";
  private static final String CLIENT_STATS_GETS =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.gets.int";
  private static final String CLIENT_STATS_PUTS =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.puts.int";
  private static final String CLIENT_STATS_CACHEMISSES =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.cachemisses.int";
  private static final String CLIENT_STATS_CPUUSAGE =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.cpuusage.long";
  private static final String CLIENT_STATS_CPUS =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.cpus.int";
  private static final String CLIENT_STATS_UPDATETIME =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.updatetime.long";
  private static final String CLIENT_STATS_THREADS =
      GeodeGlossary.GEMFIRE_PREFIX + "client.stats.threads.int";

  /**
   *
   * @return All the required details for a member with given memberId
   */
  public Map<String, Object> getMemberDetails(String memberId) throws OperationsException {
    Map<String, Object> allDetails = new TreeMap<>();

    if (memberId != null) {
      try {
        SystemMemberJmx member = findMember(memberId);
        if (member != null) {
          SystemMemberCacheJmxImpl cache = (SystemMemberCacheJmxImpl) member.getCache();
          GemFireMemberStatus snapshot = cache.getSnapshot();
          boolean isServer = snapshot.getIsServer();
          boolean isGatewayHub = snapshot.getIsGatewayHub();

          // 1. Member info
          allDetails.put(MEMBER_ID, member.getId());
          allDetails.put(MEMBER_NAME, member.getName());
          String host = member.getHost();// from of GemFireVM.getHost
          InetAddress hostAddr = member.getHostAddress();
          // possibility of null host address
          if (hostAddr != null) {
            host = hostAddr.getHostName();
          }
          allDetails.put(MEMBER_HOST, host);
          allDetails.put(MEMBER_UPTIME, snapshot.getUpTime());
          allDetails.put(IS_SERVER, isServer);
          allDetails.put(IS_GATEWAY, isGatewayHub);

          String memberType = "";
          if (member instanceof CacheServerJmxImpl) {
            memberType = TYPE_NAME_CACHESERVER;
          } else {// Mark it of Application type if neither a gateway hub nor a server
            memberType = TYPE_NAME_APPLICATION;
          }
          allDetails.put(MEMBER_TYPE, memberType);

          // 2. Region info
          Map<String, ObjectName> existingRegionMbeans = getExistingRegionMbeansFullPaths(memberId);
          allDetails.put(MEMBER_REGIONS, getAllRegionsDetails(cache, existingRegionMbeans));
          existingRegionMbeans.clear();

          // 3. Clients info
          allDetails.put(MEMBER_CLIENTS, getClientDetails(snapshot));

          boolean statSamplingEnabled = true;
          // assuming will never return as per current implementation
          ConfigurationParameter[] configParams = member.getConfiguration();
          for (ConfigurationParameter configParam : configParams) {
            if (STATISTIC_SAMPLING_ENABLED.equals(configParam.getName())) {
              allDetails.put(MEMBER_STATSAMPLING_ENABLED, configParam.getValue());
              statSamplingEnabled = Boolean.parseBoolean("" + configParam.getValue());
            } else if (ENABLE_TIME_STATISTICS.equals(configParam.getName())) {
              allDetails.put(MEMBER_TIME_STATS_ENABLED, configParam.getValue());
            }
          }

          // 5. Stats info
          allDetails.putAll(getRequiredStats(member, statSamplingEnabled));

          SystemMemberCacheServer[] cacheServers = cache.getCacheServers();
          // attempt refreshing the cache info once
          if (cacheServers.length == 0) {
            cache.refresh();
            cacheServers = cache.getCacheServers();
          }
          Integer memberCacheServerPort = 0;
          if (cacheServers.length != 0) {
            /*
             * Taking the first cache server port. We don't recommend multiple cache severs for a
             * cache.
             */
            memberCacheServerPort = cacheServers[0].getPort();
          }
          allDetails.put(MEMBER_PORT, memberCacheServerPort);
        }

      } catch (AdminException e) {
        logger.warn(String.format("Exception occurred for operation: %s for member: %s",
            "getMemberDetails", memberId),
            e);
        throw new OperationsException(e.getMessage());
      } catch (Exception e) {
        logger.warn(String.format("Exception occurred for operation: %s for member: %s",
            "getMemberDetails", memberId),
            e);
        throw new OperationsException(e.getMessage());
      }
    }

    return allDetails;
  }

  /**
   *
   * @return Map of client details
   */
  @SuppressWarnings("rawtypes")
  private Map<String, Map<String, ?>> getClientDetails(GemFireMemberStatus snapshot) {
    Map<String, Map<String, ?>> clientsInfo = new LinkedHashMap<>();

    Set connectedClients = snapshot.getConnectedClients();
    if (!connectedClients.isEmpty()) {
      Map clientHealthStatsMap = snapshot.getClientHealthStats();

      for (final Object connectedClient : connectedClients) {
        Map<String, Object> clientData = new HashMap<>();
        String clientId = (String) connectedClient;
        String host = snapshot.getClientHostName(clientId);
        clientData.put(CLIENT_ID, clientId);
        clientData.put(CLIENT_NAME, extractClientName(clientId, host));
        clientData.put(CLIENT_HOST, host);
        clientData.put(CLIENT_QUEUESIZE, snapshot.getClientQueueSize(clientId));

        ClientHealthStats clientHealthStats =
            (ClientHealthStats) clientHealthStatsMap.get(clientId);
        if (clientHealthStats != null) {
          clientData.put(CLIENT_STATS_GETS, clientHealthStats.getNumOfGets());
          clientData.put(CLIENT_STATS_PUTS, clientHealthStats.getNumOfPuts());
          clientData.put(CLIENT_STATS_CACHEMISSES, clientHealthStats.getNumOfMisses());
          clientData.put(CLIENT_STATS_CPUUSAGE, clientHealthStats.getProcessCpuTime());
          clientData.put(CLIENT_STATS_CPUS, clientHealthStats.getCpus());
          clientData.put(CLIENT_STATS_UPDATETIME, clientHealthStats.getUpdateTime().getTime());
          clientData.put(CLIENT_STATS_THREADS, clientHealthStats.getNumOfThreads());
        } else {
          clientData.put(CLIENT_STATS_GETS, 0);
          clientData.put(CLIENT_STATS_PUTS, 0);
