/*
 * Tigase Jabber/XMPP Server
 * Copyright (C) 2004-2012 "Artur Hefczyc" <[email protected]>
 * 
 * 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, version 3 of the License.
 * 
 * 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. Look for COPYING file in the top folder.
 * If not, see http://www.gnu.org/licenses/.
 * 
 * $Rev$
 * Last modified by $Author$
 * $Date$
 */

package tigase.server.sreceiver.sysmon;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryNotificationInfo;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.logging.Logger;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationListener;
import tigase.server.Packet;
import tigase.stats.StatisticsList;
import tigase.xmpp.JID;

/**
 * Created: Dec 10, 2008 1:23:17 PM
 *
 * @author <a href="mailto:[email protected]">Artur Hefczyc</a>
 * @version $Rev$
 */
public class MemMonitor extends AbstractMonitor
				implements NotificationListener {

	private static Logger log =
					Logger.getLogger("tigase.server.sreceiver.sysmon.MemMonitor");

	private MemoryMXBean memoryMXBean = null;

	@Override
	public void destroy() {
		memoryMXBean = ManagementFactory.getMemoryMXBean();
		NotificationEmitter emitter = (NotificationEmitter)memoryMXBean;
		List<MemoryPoolMXBean> memPools = ManagementFactory.getMemoryPoolMXBeans();
		for (MemoryPoolMXBean memoryPoolMXBean : memPools) {
			try {
				emitter.removeNotificationListener(this, null, memoryPoolMXBean);
			} catch (Exception e) {	}
		}
	}

	@Override
	public void init(JID jid, float treshold, SystemMonitorTask smTask) {
		super.init(jid, treshold, smTask);
		memoryMXBean = ManagementFactory.getMemoryMXBean();
		NotificationEmitter emitter = (NotificationEmitter)memoryMXBean;
		List<MemoryPoolMXBean> memPools = ManagementFactory.getMemoryPoolMXBeans();
		for (MemoryPoolMXBean memoryPoolMXBean : memPools) {
			try {
				emitter.removeNotificationListener(this, null, memoryPoolMXBean);
			} catch (Exception e) {	}
			MemoryUsage memUsage = memoryPoolMXBean.getUsage();
			if (memUsage != null) {
				if (memoryPoolMXBean.isUsageThresholdSupported()) {
					emitter.addNotificationListener(this, null, memoryPoolMXBean);
					long memUsageTreshold =
									new Double(new Long(memUsage.getMax()).doubleValue() *
									treshold).longValue();
					memoryPoolMXBean.setUsageThreshold(memUsageTreshold);
					log.config("Setting treshold: " + memUsageTreshold +
									" for memory pool: " + memoryPoolMXBean.getName() +
									", type: " + memoryPoolMXBean.getType().toString() +
									", memMax: " + memUsage.getMax() +
									", memUsed: " + memUsage.getUsed() +
									", config treeshold: " + treshold);
					if (memUsage.getUsed() > memUsageTreshold) {
						Notification not = new Notification(
										MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED,
										this, 1);
						handleNotification(not, memoryPoolMXBean);
					}
				} else {
					log.config("Memory pool name: " + memoryPoolMXBean.getName() +
									", type: " + memoryPoolMXBean.getType().toString() +
									" usage threshold is not supported.");
				}
			} else {
				log.config("Memory pool name: " + memoryPoolMXBean.getName() +
								", type: " + memoryPoolMXBean.getType().toString() +
								" is invalid.");
			}
		}
	}

	@Override
	public void handleNotification(Notification note, Object handback) {
		if (note.getType().equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
			log.info("Usage threshold exceeded, sending notification.");
			NumberFormat format = NumberFormat.getIntegerInstance();
			if (format instanceof DecimalFormat) {
				DecimalFormat decf = (DecimalFormat) format;
				decf.applyPattern(decf.toPattern() + " KB");
			}
			MemoryPoolMXBean memoryPoolMXBean = (MemoryPoolMXBean) handback;
			String message = "Threshold " +
							format.format(memoryPoolMXBean.getUsageThreshold() / 1024) +
							" for memory pool: " + memoryPoolMXBean.getName() +
							", type: " + memoryPoolMXBean.getType().toString() +
							" exceeded.";
			sendWarningOut(message, handback);
		}
	}

	@Override
	public String getState() {
		NumberFormat format = NumberFormat.getIntegerInstance();
		if (format instanceof DecimalFormat) {
			DecimalFormat decf = (DecimalFormat) format;
			decf.applyPattern(decf.toPattern() + " KB");
		}
		NumberFormat formp = NumberFormat.getPercentInstance();
		formp.setMaximumFractionDigits(2);
		StringBuilder sb = new StringBuilder();
		List<MemoryPoolMXBean> memPools = ManagementFactory.getMemoryPoolMXBeans();
		for (MemoryPoolMXBean memoryPoolMXBean : memPools) {
			MemoryUsage memUsage = memoryPoolMXBean.getUsage();
			if (memUsage != null) {
				sb.append("Memory pool: " + memoryPoolMXBean.getName() +
								", type: " + memoryPoolMXBean.getType().toString() +
								", usage: " + format.format(memUsage.getUsed()/1024) +
								" of " + format.format(memUsage.getMax()/1024) +
								" - " +
								formp.format(new Long(memUsage.getUsed()).doubleValue()/
								new Long(memUsage.getMax()).doubleValue()));
				if (memoryPoolMXBean.isUsageThresholdSupported()) {
					sb.append(", treshold: " +
									format.format(memoryPoolMXBean.getUsageThreshold() / 1024));
				}
				sb.append("\n");
			}
		}
		return sb.toString();
	}

	@Override
	public void getStatistics(StatisticsList list) {
    super.getStatistics(list);
	}

	private static int GC_INTERVAL = 40;
	private int gc_cnt = new Random(System.currentTimeMillis()).nextInt(GC_INTERVAL);
	@Override
	public void check1Min(Queue<Packet> results) {
		if (++gc_cnt >= GC_INTERVAL) {
			Runtime.getRuntime().gc();
			gc_cnt = 0;
		}
	}



}