/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.sys;

import org.apache.log4j.Logger;

import javax.management.ListenerNotFoundException;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MemoryMonitor {
    private final Collection<Listener> listeners = new ArrayList<Listener>();
    private static final Logger LOG = Logger.getLogger(MemoryMonitor.class);
    private String springContextId;

    public interface Listener {
        public void memoryUsageLow(String springContextId, Map<String, String> memoryUsageStatistics, String deadlockedThreadIds);
    }

    NotificationListener lowMemoryListener;

    public MemoryMonitor() {
        LOG.info("initializing");
        this.springContextId = "Unknown";
        ManagementFactory.getThreadMXBean().setThreadContentionMonitoringEnabled(true);
        ManagementFactory.getThreadMXBean().setThreadCpuTimeEnabled(true);
        lowMemoryListener = new NotificationListener() {
            public void handleNotification(Notification n, Object hb) {
                if (n.getType().equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
                    Map<String, String> memoryUsageStatistics = new HashMap<String, String>();
                    memoryUsageStatistics.put("MemoryMXBean: " + MemoryType.HEAP, ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().toString());
                    memoryUsageStatistics.put("MemoryMXBean:" + MemoryType.NON_HEAP, ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().toString());
                    for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
                        memoryUsageStatistics.put("MemoryPoolMXBean: " + pool.getType(), pool.getUsage().toString());
                    }
                    for (Listener listener : listeners) {
                        listener.memoryUsageLow(springContextId, memoryUsageStatistics, Arrays.toString(ManagementFactory.getThreadMXBean().findMonitorDeadlockedThreads()));
                    }
                }
            }
        };
        ((NotificationEmitter) ManagementFactory.getMemoryMXBean()).addNotificationListener(lowMemoryListener, null, null);
    }

    public void stop() {
        try {
            removeAllListeners();
            ((NotificationEmitter) ManagementFactory.getMemoryMXBean()).removeNotificationListener(lowMemoryListener);
        } catch (ListenerNotFoundException ex) {
            LOG.error("Unable to unregister mbean listener", ex);
        }
    }

    public void removeAllListeners() {
        listeners.clear();
    }

    public MemoryMonitor(String springContextId) {
        this();
        this.springContextId = springContextId;
    }

    public boolean addListener(Listener listener) {
        return listeners.add(listener);
    }

    public boolean removeListener(Listener listener) {
        return listeners.remove(listener);
    }

    public static void setPercentageUsageThreshold(double percentage) {
        for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (pool.getType() == MemoryType.HEAP && pool.isUsageThresholdSupported()) {
                if (percentage <= 0.0 || percentage > 1.0) {
                    throw new IllegalArgumentException("percentage not in range");
                }
                long warningThreshold = (long) (pool.getUsage().getMax() * percentage);
                pool.setUsageThreshold(warningThreshold);
            }
        }
    }
}
