package org.apereo.cas.monitor;

import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.hazelcast.monitor.LocalMapStats;
import org.apereo.cas.configuration.model.support.hazelcast.HazelcastProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * This is {@link HazelcastMonitor}.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
public class HazelcastMonitor extends AbstractCacheMonitor {
    private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastMonitor.class);

    public HazelcastMonitor() {
        super(HazelcastMonitor.class.getSimpleName());
    }

    @Override
    protected CacheStatistics[] getStatistics() {
        final List<CacheStatistics> statsList = new ArrayList<>();
        final HazelcastProperties hz = casProperties.getTicket().getRegistry().getHazelcast();
        LOGGER.debug("Locating hazelcast instance [{}]...", hz.getCluster().getInstanceName());
        final HazelcastInstance instance = Hazelcast.getHazelcastInstanceByName(hz.getCluster().getInstanceName());

        instance.getConfig().getMapConfigs().keySet().forEach(key -> {
            final IMap map = instance.getMap(key);
            LOGGER.debug("Starting to collect hazelcast statistics for map [{}] identified by key [{}]...", map, key);
            statsList.add(new HazelcastStatistics(map, hz.getCluster().getMembers().size()));
        });
        return statsList.toArray(new CacheStatistics[statsList.size()]);
    }

    /**
     * The type Hazelcast statistics.
     */
    public static class HazelcastStatistics implements CacheStatistics {
        private static final int PERCENTAGE_VALUE = 100;

        private IMap map;
        private int clusterSize;

        protected HazelcastStatistics(final IMap map, final int clusterSize) {
            this.map = map;
            this.clusterSize = clusterSize;
        }

        @Override
        public long getSize() {
            return this.map.size();
        }

        @Override
        public long getCapacity() {
            return this.map.getLocalMapStats() != null ? this.map.getLocalMapStats().total() : 0;
        }

        @Override
        public long getEvictions() {
            if (this.map.getLocalMapStats() != null && this.map.getLocalMapStats().getNearCacheStats() != null) {
                return this.map.getLocalMapStats().getNearCacheStats().getMisses();
            }
            return 0;
        }

        @Override
        public String getName() {
            return this.map.getName();
        }

        @Override
        public int getPercentFree() {
            final long capacity = getCapacity();
            if (capacity == 0) {
                return 0;
            }
            return (int) ((capacity - getSize()) * PERCENTAGE_VALUE / capacity);
        }

        @Override
        public void toString(final StringBuilder builder) {
            final LocalMapStats localMapStats = map.getLocalMapStats();

            builder.append("Creation time: ")
                    .append(localMapStats.getCreationTime())
                    .append(", ")
                    .append("Cluster size: ")
                    .append(clusterSize)
                    .append(", ")
                    .append("Owned entry count: ")
                    .append(localMapStats.getOwnedEntryCount())
                    .append(", ")
                    .append("Backup entry count: ")
                    .append(localMapStats.getBackupEntryCount())
                    .append(", ")
                    .append("Backup count: ")
                    .append(localMapStats.getBackupCount())
                    .append(", ")
                    .append("Hits count: ")
                    .append(localMapStats.getHits())
                    .append(", ")
                    .append("Last update time: ")
                    .append(localMapStats.getLastUpdateTime())
                    .append(", ")
                    .append("Last access time: ")
                    .append(localMapStats.getLastAccessTime())
                    .append(", ")
                    .append("Locked entry count: ")
                    .append(localMapStats.getLockedEntryCount())
                    .append(", ")
                    .append("Dirty entry count: ")
                    .append(localMapStats.getDirtyEntryCount())
                    .append(", ")
                    .append("Total get latency: ")
                    .append(localMapStats.getMaxGetLatency())
                    .append(", ")
                    .append("Total put latency: ")
                    .append(localMapStats.getTotalPutLatency())
                    .append(", ")
                    .append("Total remove latency: ")
                    .append(localMapStats.getTotalRemoveLatency())
                    .append(", ")
                    .append("Heap cost: ")
                    .append(localMapStats.getHeapCost());

            if (localMapStats.getNearCacheStats() != null) {
                builder.append(", Misses: ")
                        .append(localMapStats.getNearCacheStats().getMisses());
            }
        }

        @Override
        public String toString() {
            final StringBuilder builder = new StringBuilder();
            this.toString(builder);
            return builder.toString();
        }
    }
}
