package com.thimbleware.jmemcached;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.jboss.netty.channel.ChannelHandlerContext;

import com.thimbleware.jmemcached.protocol.exceptions.DatabaseException;
/**
 * 在jmemcached的基础之上完善了stats信息
 * 
 * 
 */


/**
 * Abstract implementation of a cache handler for the memcache daemon; provides
 * some convenience methods and a general framework for implementation
 */
public abstract class AbstractCache<CACHE_ELEMENT extends CacheElement> implements Cache<CACHE_ELEMENT> {

	protected final AtomicLong started = new AtomicLong();

	protected final AtomicLong getCmds = new AtomicLong();
	protected final AtomicLong setCmds = new AtomicLong();
	protected final AtomicLong getHits = new AtomicLong();
	protected final AtomicLong getMisses = new AtomicLong();
	protected final AtomicLong casCounter = new AtomicLong(1);
	protected final OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();

	public AbstractCache() {
		initStats();
	}

	/** Get JVM CPU time in milliseconds */
	public long getJVMCpuTime() {
		if (!(bean instanceof com.sun.management.OperatingSystemMXBean))
			return 0L;
		return ((com.sun.management.OperatingSystemMXBean) bean).getProcessCpuTime();
	}

	public double getJVMLoad() {
		if (!(bean instanceof com.sun.management.OperatingSystemMXBean))
			return 0L;
		return ((com.sun.management.OperatingSystemMXBean) bean).getSystemLoadAverage();
	}

	public int getAllthreadsCount() {
		ThreadGroup group = Thread.currentThread().getThreadGroup();
		ThreadGroup topGroup = group;
		while (group != null) {
			topGroup = group;
			group = group.getParent();
		}
		return topGroup.activeCount();
	}

	/**
	 * @return the current time in seconds (from epoch), used for expiries, etc.
	 */
	public static int Now() {
		return (int) (System.currentTimeMillis() / 1000);
	}

	public abstract Set<String> keys();

	public abstract long getCurrentItems();

	public abstract long getLimitMaxBytes();

	public abstract long getCurrentBytes();

	public final long getGetCmds() {
		return getCmds.get();
	}

	public final long getSetCmds() {
		return setCmds.get();
	}

	public final long getGetHits() {
		return getHits.get();
	}

	public final long getGetMisses() {
		return getMisses.get();
	}

	/**
	 * Return runtime statistics
	 * 
	 * @param arg
	 *            additional arguments to the stats command
	 * @return the full command response
	 */
	public final Map<String, Set<String>> stat(String arg, ChannelHandlerContext channelHandlerContext) {
		Map<String, Set<String>> result = new HashMap<String, Set<String>>();
		// stats we know
		multiSet(result, "version", MemCacheDaemon.memcachedVersion);
		multiSet(result, "cmd_get", java.lang.String.valueOf(getGetCmds()));
		multiSet(result, "cmd_set", java.lang.String.valueOf(getSetCmds()));
		multiSet(result, "get_hits", java.lang.String.valueOf(getGetHits()));
		multiSet(result, "get_misses", java.lang.String.valueOf(getGetMisses()));
		multiSet(result, "time", java.lang.String.valueOf(java.lang.String.valueOf(Now())));
		multiSet(result, "uptime", java.lang.String.valueOf(Now() - this.started.longValue()));
		multiSet(result, "curr_items", java.lang.String.valueOf(this.getCurrentItems()));
		multiSet(result, "total_items", java.lang.String.valueOf(this.getCurrentItems()));
		multiSet(result, "limit_maxbytes", java.lang.String.valueOf(this.getLimitMaxBytes()));
		multiSet(result, "current_bytes", java.lang.String.valueOf(this.getCurrentBytes()));
		multiSet(result, "bytes", java.lang.String.valueOf(this.getCurrentBytes()));
		multiSet(result, "free_bytes", java.lang.String.valueOf(Runtime.getRuntime().freeMemory()));

		// Not really the same thing precisely, but meaningful nonetheless.
		// potentially this should be renamed
		multiSet(result, "pid", java.lang.String.valueOf(Thread.currentThread().getId()));

		// stuff we know nothing about; gets faked only because some clients
		// expect this
		multiSet(result, "rusage_user", String.valueOf(TimeUnit.NANOSECONDS.toSeconds(getJVMCpuTime())));
		multiSet(result, "rusage_system", "0.0");
		multiSet(result, "connection_structures", "0");
		multiSet(result, "curr_connections", String.valueOf(StatsCounter.curr_conns.longValue()));
		multiSet(result, "total_connections", String.valueOf(StatsCounter.total_conns.longValue()));

		// TODO we could collect these stats
		multiSet(result, "bytes_read", String.valueOf(StatsCounter.bytes_read.longValue()));
		multiSet(result, "bytes_written", String.valueOf(StatsCounter.bytes_written.longValue()));
		multiSet(result, "system_load", String.valueOf(getJVMLoad()));
		multiSet(result, "threads", String.valueOf(getAllthreadsCount()));
		return result;
	}

	private void multiSet(Map<String, Set<String>> map, String key, String val) {
		Set<String> cur = map.get(key);
		if (cur == null) {
			cur = new HashSet<String>();
		}
		cur.add(val);
		map.put(key, cur);
	}

	/**
	 * Initialize all statistic counters
	 */
	protected void initStats() {
		started.set(System.currentTimeMillis() / 1000);
		getCmds.set(0);
		setCmds.set(0);
		getHits.set(0);
		getMisses.set(0);

	}

	public abstract void asyncEventPing() throws DatabaseException, Exception;
}