/*******************************************************************************
 * Copyright (c) 1997, 2018 by ProSyst Software GmbH
 * http://www.prosyst.com
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    ProSyst Software GmbH - initial API and implementation
 *******************************************************************************/

package org.eclipse.equinox.internal.util.impl.tpt.threadpool;

import java.security.*;
import org.eclipse.equinox.internal.util.UtilActivator;
import org.eclipse.equinox.internal.util.impl.tpt.ServiceFactoryImpl;
import org.eclipse.equinox.internal.util.pool.ObjectPool;
import org.eclipse.equinox.internal.util.timer.TimerListener;

/**
 * @author Pavlin Dobrev
 * @version 1.0
 */

public class ThreadPoolManagerImpl extends ObjectPool implements TimerListener, PrivilegedAction<Executor> {

	static ThreadPoolManagerImpl threadPool = null;

	static ObjectPool jobPool;

	private int used = 0;
	static int tMaximum = 0;

	Job waiting = new Job();

	private static String pAutoMaximum = "equinox.util.threadpool.autoMaximum";

	private static String pMin = "equinox.util.threadpool.minThreads";

	private static String pMax = "equinox.util.threadpool.maxThreads";

	private static String pIgnoreMax = "equinox.util.threadpool.ignoreMaximum";

	private static int defMin = 1;

	private static int defMax = 48;

	private static int MAX_WAITING = 20;

	private static float MAX_OVERLOAD = 0.10F;

	private static boolean ignoreMax;
	private static boolean autoMax;

	private ThreadPoolManagerImpl(int i, int j, int m) {
		super((Class<?>) null, i, j, m);
		tMaximum = i * j;
		ignoreMax = UtilActivator.getBoolean(pIgnoreMax);
		autoMax = UtilActivator.getBoolean(pAutoMaximum);
		jobPool = new ObjectPool(waiting, 5, 8, 4);
	}

	public static ThreadPoolManagerImpl getThreadPool() {
		if (threadPool == null) {
			int intSize = UtilActivator.getInteger(pMin, defMin);
			int minFill = intSize;
			int factor = UtilActivator.getInteger(pMax, defMax);
			intSize = intSize < 2 ? 2 : intSize;
			if (intSize > factor) {
				factor = (int) (intSize * 1.5 + 0.5);
			}
			threadPool = new ThreadPoolManagerImpl(intSize, (factor / intSize), minFill);
		}
		return threadPool;
	}

	@Override
	public void clear() {
		shrink(-1);
		threadPool = null;
	}

	@Override
	public Object getInstance() throws Exception {
		if (ServiceFactoryImpl.privileged()) {
			return AccessController.doPrivileged(this);
		}

		return new Executor();
	}

	@Override
	public Executor run() {
		return new Executor();
	}

	@Override
	public Object getObject() {
		try {
			return super.getObject();
		} catch (Throwable tw) {
			if (ServiceFactoryImpl.log != null) {
				ServiceFactoryImpl.log.error("Unable to create more threads!\r\nActive Thread Pool tasks: " + used, tw);
			}
		}
		return null;
	}

	public Executor getExecutor() {
		synchronized (getSyncMonitor()) {
			if (used < tMaximum || ignoreMax) {
				Executor e = (Executor) getObject();
				if (e != null) {
					used++;
					return e;
				}
			}
		}
		return null;
	}

	@Override
	protected void shrink(int count) {
		synchronized (getSyncMonitor()) {
			dontExtend = true;
			int x, y;
			for (; nextFree > count; nextFree--) {
				x = nextFree / factor;
				y = nextFree % factor;
				Executor e = (Executor) buff[x][y];
				buff[x][y] = null;
				e.terminate();
			}
		}
	}

	@Override
	public void shrink() {
		shrink(minimumFill - 1);
		dontExtend = false;
	}

	@Override
	public boolean releaseObject(Object obj) {
		Job tmp = null;
		Executor x = (Executor) obj;

		synchronized (getSyncMonitor()) {
			x.factory.finished();

			if (used <= tMaximum || ignoreMax) {
				tmp = waiting.getJob();
			}
		}

		if (tmp == null) {
			used--;
			x.clear();
			x.setPriorityI(Thread.NORM_PRIORITY);
			return super.releaseObject(obj);
		}
		if (UtilActivator.LOG_DEBUG) {
			UtilActivator.log.debug(0x0100, 10005, tmp.name, null, false);
		}
		x.setPriorityI(tmp.priority);

		x.setRunnable(tmp.run, tmp.name, tmp.factory, tmp.acc);
		tmp.fullClear();
		jobPool.releaseObject(tmp);
		return true;
	}

	@Override
	public void timer(int event) {
		int count = 0;
		int all = 0;
		synchronized (getSyncMonitor()) {
			for (Object[] b : buff) {
				if (b != null) {
					for (Object item : b) {
						Executor e = (Executor) item;
						if (e != null) {
							all++;
							if (!e.accessed)
								count++;
							else
								e.accessed = false;
						}
					}
				}
			}
			if (count > 0 && all > minimumFill - 1 && all > count) {
				/*
				 * keep in mind current thread - shrinking one more, since the
				 * current thread will be back in pool
				 */
				if (count > minimumFill)
					shrink(count - 2);
				else
					shrink(minimumFill - 2);
				dontExtend = false;
			}
		}
	}

	public void execute(Runnable job, int priority, String name, ThreadPoolFactoryImpl factory, AccessControlContext acc) {
		Executor ex = null;
		synchronized (getSyncMonitor()) {
			if (used < tMaximum || ignoreMax) {
				ex = (Executor) getObject();
			}
			if (ex != null) {
				used++;
			} else {
				addInTasksQueue(job, name, priority, factory, acc);

				return;
			}
		}

		ex.setPriorityI(priority);
		ex.setRunnable(job, name, factory, acc);
	}

	private void addInTasksQueue(Runnable job, String name, int priority, ThreadPoolFactoryImpl factory, AccessControlContext acc) {

		waiting.addJob(job, name, priority, factory, acc);
		if (UtilActivator.LOG_DEBUG) {
			UtilActivator.log.debug("In Threadpool Queue: " + name + ", queue size:" + waiting.counter, null);
		}
		if (autoMax && waiting.counter > MAX_WAITING) {
			Executor ex = (Executor) getObject();
			if (ex != null) {
				tMaximum += MAX_WAITING;
				MAX_WAITING += (int) (MAX_WAITING * MAX_OVERLOAD);
				for (Job j = waiting.getJob(); j != null; j = waiting.getJob()) {
					if (ex == null) {
						ex = (Executor) getObject();
					}
					if (ex != null) {
						used++;
						ex.setPriorityI(j.priority);
						ex.setRunnable(j.run, j.name, factory, acc);
						ex = null;
					} else {
						waiting.addJob(j.run, j.name, j.priority, j.factory, acc);
						break;
					}
				}
			}
		}
	}

	public void reset() {
		shrink(-1);
		dontExtend = false;
	}

	public Object getSyncMonitor() {
		return buff;
	}

}
