package net.lecousin.framework.concurrent;

import java.io.Closeable;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.ThreadFactory;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import net.lecousin.framework.application.LCCore;
import net.lecousin.framework.util.DebugUtil;

/** Monitoring of multi-threading system to avoid dead tasks. */
@SuppressFBWarnings({"MS_CANNOT_BE_FINAL", "MS_SHOULD_BE_FINAL" })
public final class TaskMonitoring {

	public static boolean checkLocksOfBlockingTasks = false;
	
	public static int MINUTES_BEFORE_TO_PUT_TASK_ASIDE = 5;
	public static int MINUTES_BEFORE_KILL_TASK = 10;
	
	private static TaskMonitor monitor;
	
	static void start(ThreadFactory threadFactory) {
		monitor = new TaskMonitor();
		threadFactory.newThread(monitor);
		LCCore.get().toClose(monitor);
	}
	
	/** Called when a TaskWorker is blocked, if checkLocksOfBlockingTasks is true. */
	static void checkNoLockForWorker() {
		ThreadMXBean bean = ManagementFactory.getThreadMXBean();
		if (bean == null) return;
		Thread t = Thread.currentThread();
		ThreadInfo info = bean.getThreadInfo(t.getId());
		if (info == null) return;
		MonitorInfo[] monitors = info.getLockedMonitors();
		LockInfo[] locks = info.getLockedSynchronizers();
		if (monitors.length == 0 && locks.length == 0) return;
		StringBuilder s = new StringBuilder(4096);
		s.append("TaskWorker is blocked while locking objects:\r\n");
		DebugUtil.createStackTrace(s, new Exception("Here"), false);
		if (monitors.length > 0) {
			s.append("\r\nLocked monitors:");
			for (int i = 0; i < monitors.length; ++i) {
				StackTraceElement trace = monitors[i].getLockedStackFrame();
				s.append("\r\n - ").append(trace.getClassName()).append('#')
					.append(trace.getMethodName()).append(':').append(trace.getLineNumber());
			}
		}
		if (locks.length > 0) {
			s.append("\r\nLocked synchronizers:");
			for (int i = 0; i < locks.length; ++i) {
				s.append("\r\n - ").append(locks[i].getClassName());
			}
		}
		Threading.logger.error(s.toString());
	}

	private static class TaskMonitor implements Runnable, Closeable {
		
		private Object lock = new Object();
		private boolean closed = false;
		
		@Override
		public void run() {
			while (!closed) {
				synchronized (lock) {
					try { lock.wait(60000); }
					catch (InterruptedException e) { break; }
					if (closed)
						break;
				}
				for (TaskManager manager : Threading.getAllTaskManagers())
					check(manager);
			}
		}
		
		@Override
		public void close() {
			closed = true;
			synchronized (lock) {
				lock.notifyAll();
			}
		}
	}
	
	private static void check(TaskManager manager) {
		for (TaskWorker worker : manager.getAllActiveWorkers())
			check(worker);
	}
	
	private static void check(TaskWorker worker) {
		if (worker == null) return;
		long now = System.nanoTime();
		Task<?,?> task = worker.currentTask;
		if (task == null) return;
		long start = worker.currentTaskStart;
		long minutes = (now - start) / (1000000L * 1000 * 60);
		if (minutes < MINUTES_BEFORE_TO_PUT_TASK_ASIDE) return;
		if (minutes < MINUTES_BEFORE_KILL_TASK) {
			Threading.logger.warn("Task " + task + " is running since more than 5 minutes !");
			worker.manager.putWorkerAside(worker);
			return;
		}
		Threading.logger.error("Task " + task + " is running since more than 10 minutes ! kill it.");
		worker.manager.killWorker(worker);
	}
	
}

@@ -713,37 +713,89 @@
 lic static int M
+ONITORING_
 IN
-U
 TE
+RVAL = 60 * 1000;%0A%09public static int SECOND
 S_BEFORE_TO_PUT_
@@ -808,16 +808,21 @@
 SIDE = 5
+ * 60
 ;%0A%09publi
@@ -834,22 +834,22 @@
 tic int 
-MINUTE
+SECOND
 S_BEFORE
@@ -863,16 +863,21 @@
 ASK = 10
+ * 60
 ;%0A%09%0A%09pri
@@ -2528,13 +2528,27 @@
 ait(
-60000
+MONITORING_INTERVAL
 ); %7D
@@ -3197,22 +3197,22 @@
 %0A%09%09long 
-minute
+second
 s = (now
@@ -3239,21 +3239,16 @@
 L * 1000
- * 60
 );%0A%09%09if 
@@ -3248,32 +3248,32 @@
 ;%0A%09%09if (
-minutes %3C MINUTE
+seconds %3C SECOND
 S_BEFORE
@@ -3310,24 +3310,24 @@
 if (
-minutes %3C MINUTE
+seconds %3C SECOND
 S_BE
@@ -3409,29 +3409,69 @@
 nce 
-more than 5 minutes !
+%22 + seconds + %22 seconds ! put it aside and start a new thread
 %22);%0A
@@ -3592,27 +3592,30 @@
 nce 
-more than 10 minute
+%22 + seconds + %22 second
 s ! 

make TaskMonitoring more configurable