package org.infinispan.stats.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.factories.annotations.Inject;
import org.infinispan.factories.annotations.Start;
import org.infinispan.factories.scopes.Scope;
import org.infinispan.factories.scopes.Scopes;
import org.infinispan.jmx.annotations.DisplayType;
import org.infinispan.jmx.annotations.MBean;
import org.infinispan.jmx.annotations.ManagedAttribute;
import org.infinispan.jmx.annotations.MeasurementType;
import org.infinispan.manager.ClusterExecutor;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.stats.ClusterContainerStats;
import org.infinispan.util.concurrent.CompletableFutures;
import org.infinispan.util.logging.Log;
import org.infinispan.util.logging.LogFactory;

/**
 * @author Ryan Emerson
 * @since 9.0
 */
@Scope(Scopes.GLOBAL)
@MBean(objectName = "ClusterContainerStats", description = "General container statistics aggregated across the cluster.")
public class ClusterContainerStatsImpl extends AbstractClusterStats implements ClusterContainerStats {

   private static final Log log = LogFactory.getLog(ClusterContainerStatsImpl.class);

   // Memory
   private static final String MEMORY_AVAILABLE = "memoryAvailable";
   private static final String MEMORY_MAX = "memoryMax";
   private static final String MEMORY_TOTAL = "memoryTotal";
   private static final String MEMORY_USED = "memoryUsed";

   private static final String[] LONG_ATTRIBUTES = new String[]{MEMORY_AVAILABLE, MEMORY_MAX, MEMORY_TOTAL, MEMORY_USED};

   private ClusterExecutor clusterExecutor;
   private EmbeddedCacheManager cacheManager;

   public ClusterContainerStatsImpl() {
      super(log);
   }

   @Inject
   public void init(EmbeddedCacheManager cacheManager,
                    GlobalConfiguration configuration) {
      this.cacheManager = cacheManager;
      this.statisticsEnabled = configuration.globalJmxStatistics().enabled();
   }

   @Start
   public void start() {
      this.clusterExecutor = SecurityActions.getClusterExecutor(cacheManager);
   }

   @Override
   void updateStats() throws Exception {
      List<Map<String, Number>> memoryMap = getClusterStatMaps();
      for (String att : LONG_ATTRIBUTES)
         putLongAttributes(memoryMap, att);
   }

   private List<Map<String, Number>> getClusterStatMaps() throws Exception {
      final List<Map<String, Number>> successfulResponseMaps = new ArrayList<>();
      CompletableFutures.await(clusterExecutor.submit(() -> {
         Map<String, Number> map = new HashMap<>();
         long available = Runtime.getRuntime().freeMemory();
         long total = Runtime.getRuntime().totalMemory();
         map.put(MEMORY_AVAILABLE, available);
         map.put(MEMORY_MAX, Runtime.getRuntime().maxMemory());
         map.put(MEMORY_TOTAL, total);
         map.put(MEMORY_USED, total - available);
         successfulResponseMaps.add(map);
      }));
      return successfulResponseMaps;
   }

   @ManagedAttribute(description = "The maximum amount of free memory in bytes across the cluster JVMs",
         displayName = "Cluster wide available memory.",
         measurementType = MeasurementType.DYNAMIC,
         displayType = DisplayType.SUMMARY)
   @Override
   public long getMemoryAvailable() {
      return getStatAsLong(MEMORY_AVAILABLE);
   }

   @ManagedAttribute(description = "The maximum amount of memory that JVMs across the cluster will attempt to utilise in bytes",
         displayName = "Cluster wide max memory of JVMs",
         measurementType = MeasurementType.DYNAMIC,
         displayType = DisplayType.SUMMARY)
   @Override
   public long getMemoryMax() {
      return getStatAsLong(MEMORY_MAX);
   }

   @ManagedAttribute(description = "The total amount of memory in the JVMs across the cluster in bytes",
         displayName = "Cluster wide total memory",
         measurementType = MeasurementType.DYNAMIC,
         displayType = DisplayType.SUMMARY)
   @Override
   public long getMemoryTotal() {
      return getStatAsLong(MEMORY_TOTAL);
   }

   @ManagedAttribute(description = "The amount of memory used by JVMs across the cluster in bytes",
         displayName = "Cluster wide memory utilisation",
         measurementType = MeasurementType.DYNAMIC,
         displayType = DisplayType.SUMMARY)
   @Override
   public long getMemoryUsed() {
      return getStatAsLong(MEMORY_USED);
   }
}
