/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.datakernel.async;

import io.datakernel.jmx.ConcurrentJmxMBean;
import io.datakernel.jmx.JmxAttribute;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static io.datakernel.jmx.MBeanFormat.formatDuration;

public final class CallbackRegistry {
	private static final ConcurrentHashMap<Object, CallbackMetaData> REGISTRY = new ConcurrentHashMap<>();

	private static volatile boolean storeStackTrace = false;

	// region public api
	public static void register(Object callback) {
		assert registerIfAssertionsEnabled(callback) : "Callback " + callback + " has already been registered";

	}

	public static void complete(Object callback) {
		assert REGISTRY.remove(callback) != null : "Callback " + callback + " has already been completed";
	}

	public static boolean getStoreStackTrace() {
		return storeStackTrace;
	}

	public static void setStoreStackTrace(boolean store) {
		storeStackTrace = store;
	}
	// endregion

	// region implementation
	private static boolean registerIfAssertionsEnabled(Object callback) {
		long timestamp = System.currentTimeMillis();

		StackTraceElement[] stackTrace = null;
		if (storeStackTrace) {
			// TODO(vmykhalko): maybe use new Exception().getStackTrace instead ? according to performance issues
			StackTraceElement[] fullStackTrace = Thread.currentThread().getStackTrace();
			// remove stack trace lines that stand for registration method calls
			stackTrace = Arrays.copyOfRange(fullStackTrace, 3, fullStackTrace.length);
		}

		return REGISTRY.put(callback, new CallbackMetaData(timestamp, stackTrace)) == null;
	}

	private static final class CallbackMetaData {
		private final long timestamp;
		private final StackTraceElement[] stackTrace;

		public CallbackMetaData(long timestamp, StackTraceElement[] stackTrace) {
			this.timestamp = timestamp;
			this.stackTrace = stackTrace;
		}

		public long getTimestamp() {
			return timestamp;
		}

		public StackTraceElement[] getStackTrace() {
			return stackTrace;
		}
	}
	// endregion

	// region jmx
	public static final class CallbackRegistryStats implements ConcurrentJmxMBean {
		private volatile int maxCallbacksToShow = 100;

		@JmxAttribute
		public List<String> getCurrentCallbacksSummary() {
			List<CallbackDetails> callbacksDetails = getCurrentCallbacksDetails();

			List<String> lines = new ArrayList<>();
			lines.add("Duration       Callback");
			for (CallbackDetails details : callbacksDetails) {
				lines.add(String.format("%s   %s: %s",
						details.getDuration(), details.getClassName(), details.getStringRepresentation()));
			}

			return lines;
		}

		@JmxAttribute
		public List<CallbackDetails> getCurrentCallbacksDetails() {
			List<Map.Entry<Object, CallbackMetaData>> sortedEntries = sortByTimestamp(REGISTRY);

			List<CallbackDetails> detailsList = new ArrayList<>();
			long currentTime = System.currentTimeMillis();
			int maxCallbacksToShowCached = maxCallbacksToShow;
			for (Map.Entry<Object, CallbackMetaData> entry : sortedEntries) {
				if (detailsList.size() == maxCallbacksToShowCached) {
					break;
				}

				Object callback = entry.getKey();
				CallbackMetaData metaData = entry.getValue();
				String formattedDuration = formatDuration(currentTime - metaData.getTimestamp());
				String className = callback.getClass().getName();
				String callbackString = callback.toString();

				List<String> stackTraceLines = new ArrayList<>();
				StackTraceElement[] stackTrace = metaData.getStackTrace();
				if (stackTrace != null) {
					for (StackTraceElement stackTraceElement : stackTrace) {
						stackTraceLines.add(stackTraceElement.toString());
					}
				}

				detailsList.add(new CallbackDetails(className, callbackString, formattedDuration, stackTraceLines));
			}

			return detailsList;
		}

		@JmxAttribute
		public boolean getStoreStackTrace() {
			return CallbackRegistry.getStoreStackTrace();
		}

		@JmxAttribute
		public void setStoreStackTrace(boolean store) {
			CallbackRegistry.setStoreStackTrace(store);
		}

		@JmxAttribute
		public int getMaxCallbacksToShow() {
			return maxCallbacksToShow;
		}

		@JmxAttribute
		public void setMaxCallbacksToShow(int maxCallbacksToShow) {
			if (maxCallbacksToShow < 0) {
				throw new IllegalArgumentException("argument must be non-negative");
			}
			this.maxCallbacksToShow = maxCallbacksToShow;
		}

		@JmxAttribute
		public int totalActiveCallbacks() {
			return CallbackRegistry.REGISTRY.size();
		}

		private static List<Map.Entry<Object, CallbackMetaData>> sortByTimestamp(Map<Object, CallbackMetaData> map) {
			List<Map.Entry<Object, CallbackMetaData>> sortedEntries = new ArrayList<>(map.entrySet());
			Collections.sort(sortedEntries, new Comparator<Map.Entry<Object, CallbackMetaData>>() {
				@Override
				public int compare(Map.Entry<Object, CallbackMetaData> o1, Map.Entry<Object, CallbackMetaData> o2) {
					long ts1 = o1.getValue().getTimestamp();
					long ts2 = o2.getValue().getTimestamp();
					return Long.compare(ts1, ts2);
				}
			});
			return sortedEntries;
		}

		public static final class CallbackDetails {
			private final String className;
			private final String stringRepresentation;
			private final String duration;
			private final List<String> stackTrace;

			public CallbackDetails(String className, String stringRepresentation,
			                       String duration, List<String> stackTrace) {
				this.className = className;
				this.stringRepresentation = stringRepresentation;
				this.duration = duration;
				this.stackTrace = stackTrace;
			}

			@JmxAttribute
			public String getClassName() {
				return className;
			}

			@JmxAttribute
			public String getStringRepresentation() {
				return stringRepresentation;
			}

			@JmxAttribute
			public String getDuration() {
				return duration;
			}

			@JmxAttribute
			public List<String> getStackTrace() {
				return stackTrace;
			}
		}
	}
	// endregion
}

@@ -4962,17 +4962,20 @@
 lic int 
-t
+getT
 otalActi

fix jmx attribute in CallbackRegistry