/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package java.lang;

import malik.emulator.util.*;

public class Thread extends Object implements Runnable
{
	static final class Monitor extends Object
	{
		private static final String NOT_OWNING = ": текущий поток исполнения не владеет этим блокирующим монитором в настоящий момент.";
		static final String MESSAGE_NOT_OWNING_WAIT = "Object.wait" + NOT_OWNING;
		static final String MESSAGE_NOT_OWNING_NOTIFY = "Object.notify" + NOT_OWNING;
		static final String MESSAGE_NOT_OWNING_NOTIFY_ALL = "Object.notifyAll" + NOT_OWNING;
		static final String MESSAGE_NOT_OWNING_MONITOR_EXIT = "Инструкция monitorexit" + NOT_OWNING;


		private int head;
		private int tail;
		private long ecount;
		private long notifies;
		private long[] ecounts;
		private Thread[] threads;
		private Thread owner;

		public Monitor()
		{
		}

		public void enter()
		{
			boolean status = MalikSystem.enterMonopolyAccess();
			try
			{
				Thread owner = this.owner;
				Thread current = Thread.currentThread();
				if(owner == null)
				{
					this.owner = current;
					this.ecount = 1L;
				}
				else if(owner == current)
				{
					this.ecount++;
				}
				else
				{
					push(current, 1L);
					current.suspend(false);
				}
			}
			finally
			{
				MalikSystem.leaveMonopolyAccess(status);
			}
		}

		public void wakeup()
		{
			if(owner == null) pop();
		}

		public void notifyWaiting(boolean all) throws IllegalMonitorStateException
		{
			boolean status;
			int error = 0;
			status = MalikSystem.enterMonopolyAccess();
			try
			{
				label0:
				{
					boolean notifying;
					Thread[] tq;
					if(owner != Thread.currentThread())
					{
						error = 1;
						break label0;
					}
					notifying = false;
					for(int capacity = (tq = threads) != null ? tq.length : 0, h = head, i = tail; i != h; i = (i + 1) % capacity)
					{
						Thread thread;
						Scheduler.Task task;
						if((thread = tq[i]).state != WAITING) continue;
						thread.suspend(false);
						if((task = thread.suspendTask) != null)
						{
							thread.suspendTask = null;
							task.cancel();
							task = null;
						}
						if(!all)
						{
							notifying = true;
							break;
						}
					}
					if(!all && !notifying)
					{
						notifies++;
					}
				}
			}
			finally
			{
				MalikSystem.leaveMonopolyAccess(status);
			}
			if(error == 1)
			{
				throw new IllegalMonitorStateException(all ? MESSAGE_NOT_OWNING_NOTIFY_ALL : MESSAGE_NOT_OWNING_NOTIFY, true);
			}
		}

		public void waitNotifying(long millis) throws IllegalMonitorStateException, InterruptedException
		{
			boolean flag;
			int error = 0;
			flag = MalikSystem.enterMonopolyAccess();
			try
			{
				label0:
				{
					long ecount;
					Thread owner;
					if((owner = this.owner) != Thread.currentThread())
					{
						error = 1;
						break label0;
					}
					if(owner.interrupted)
					{
						owner.interrupted = false;
						error = 2;
						break label0;
					}
					if((ecount = notifies) != 0L)
					{
						notifies = ecount - 1L;
						break label0;
					}
					if(millis > 0L)
					{
						Scheduler.Task task;
						owner.suspendTask = task = owner.new SuspendTask(this);
						Scheduler.schedule(task, millis, Scheduler.MONOPOLY);
						task = null;
					}
					ecount = this.ecount;
					pop();
					push(owner, ecount);
					owner.blockedBy = this;
					owner.suspend(true);
					owner.blockedBy = null;
					if(owner.interrupted)
					{
						owner.interrupted = false;
						error = 2;
						break label0;
					}
				}
			}
			finally
			{
				MalikSystem.leaveMonopolyAccess(flag);
			}
			switch(error)
			{
			case 1:
				throw new IllegalMonitorStateException(MESSAGE_NOT_OWNING_WAIT, true);
			case 2:
				throw new InterruptedException("Object.wait: поток исполнения был прерван.", true);
			}
		}

		public void exit() throws IllegalMonitorStateException
		{
			boolean flag;
			int error = 0;
			flag = MalikSystem.enterMonopolyAccess();
			try
			{
				label0:
				{
					if(owner != Thread.currentThread())
					{
						error = 1;
						break label0;
					}
					if(--ecount == 0L)
					{
						pop();
					}
				}
			}
			finally
			{
				MalikSystem.leaveMonopolyAccess(flag);
			}
			if(error == 1)
			{
				throw new IllegalMonitorStateException(MESSAGE_NOT_OWNING_MONITOR_EXIT, true);
			}
		}

		private void push(Thread thread, long ecount)
		{
			int qhead;
			int qtail;
			int ocapa;
			long[] oeq;
			Thread[] otq;
			if((oeq = ecounts) == null)
			{
				oeq = ecounts = new long[3];
				otq = threads = new Thread[3];
			} else
			{
				otq = threads;
			}
			if(((qhead = head) + 1) % (ocapa = oeq.length) == (qtail = tail))
			{
				int count;
				int ncapa;
				long[] neq = new long[ncapa = (ocapa << 1) - 1];
				Thread[] ntq = new Thread[ncapa];
				if(qhead < qtail)
				{
					int quantity = ocapa - qtail;
					count = qhead + quantity;
					if(quantity > 0)
					{
						MalikSystem.arraycopyf_long(oeq, qtail, neq, 0, quantity);
						MalikSystem.arraycopyf_object(otq, qtail, ntq, 0, quantity);
					}
					if(qhead > 0)
					{
						MalikSystem.arraycopyf_long(oeq, 0, neq, quantity, qhead);
						MalikSystem.arraycopyf_object(otq, 0, ntq, quantity, qhead);
					}
				} else
				{
					if((count = qhead - qtail) > 0)
					{
						MalikSystem.arraycopyf_long(oeq, qtail, neq, 0, count);
						MalikSystem.arraycopyf_object(otq, qtail, ntq, 0, count);
					}
				}
				ecounts = oeq = neq;
				threads = otq = ntq;
				qhead = count;
				ocapa = ncapa;
				tail = 0;
			}
			oeq[qhead] = ecount;
			otq[qhead] = thread;
			head = (qhead + 1) % ocapa;
		}

		private void pop()
		{
			int h;
			int qhead;
			int qtail;
			long ecount = 0L;
			Thread thread = null;
			if((h = qhead = head) != (qtail = tail))
			{
				int state;
				int capacity;
				long[] eq = ecounts;
				Thread[] tq = threads;
				capacity = eq.length;
				do
				{
					ecount = eq[qtail];
					thread = tq[qtail];
					eq[qtail] = 0L;
					tq[qtail] = null;
					qtail = (qtail + 1) % capacity;
					if((state = thread.state) != WAITING) break;
					eq[qhead] = ecount;
					tq[qhead] = thread;
					qhead = (qhead + 1) % capacity;
				} while(qtail != h);
				head = qhead;
				tail = qtail;
				if(state == WAITING)
				{
					ecount = 0L;
					thread = null;
				}
			}
			this.owner = thread;
			this.ecount = ecount;
			if(thread != null) thread.resume();
		}
	}

	static final int CREATED = 0;
	static final int RUNNING = 1;
	static final int WAITING = 2;
	static final int SUSPENDED = 3;
	static final int TERMINATED = 4;
	public static final int MIN_PRIORITY = 1;
	public static final int NORM_PRIORITY = 5;
	public static final int MAX_PRIORITY = 10;
	private static final int STACK_RESERVED = 0x0800;

	private static int LAST_NUM;
	static final int MAIN_THREAD_ID;
	private static final Thread[] THREADS;

	static
	{
		MAIN_THREAD_ID = MalikSystem.getCurrentThreadID();
		THREADS = new Thread[getMaximumThreads()];
	}

	public static void yield()
	{
		/* Потоки исполнения переключаются автоматически. За время выполнения этого метода произойдёт хотя бы одно переключение. */
		int i = 0;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i++;
		i = i + 1;
	}

	public static void sleep(long millis) throws InterruptedException
	{
		boolean status;
		int error = 0;
		status = MalikSystem.enterMonopolyAccess();
		try
		{
			label0:
			{
				Thread thread;
				if((thread = Thread.currentThread()).interrupted)
				{
					thread.interrupted = false;
					error = 1;
					break label0;
				}
				if(millis <= 0L)
				{
					break label0;
				}
				Scheduler.Task task;
				thread.resumeTask = task = thread.new ResumeTask();
				Scheduler.schedule(task, millis, Scheduler.MONOPOLY);
				task = null;
				thread.suspend(false);
				if(thread.interrupted)
				{
					thread.interrupted = false;
					error = 1;
					break label0;
				}
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
		if(error == 1)
		{
			throw new InterruptedException("Thread.sleep: поток исполнения был прерван.");
		}
	}

	public static int activeCount()
	{
		int result = 0;
		for(int i = THREADS.length; i-- > 0; )
		{
			if(THREADS[i] != null) result++;
		}
		return result;
	}

	public static Thread currentThread()
	{
		return THREADS[MalikSystem.getCurrentThreadID()];
	}

	static void completeInit()
	{
		THREADS[MAIN_THREAD_ID] = new Thread(NORM_PRIORITY, MAIN_THREAD_ID);
	}

	static void waitForInterrupt() throws InterruptedException
	{
		Thread thread;
		if((thread = Thread.currentThread()).interrupted)
		{
			thread.interrupted = false;
			throw new InterruptedException("Thread.waitForInterrupt: поток исполнения был прерван.");
		}
		MalikSystem.syscall(0L, 0x000e);
		if(thread.interrupted)
		{
			thread.interrupted = false;
			throw new InterruptedException("Thread.waitForInterrupt: поток исполнения был прерван.");
		}
	}

	private static void writeStackLimit(int address)
	{
		MalikSystem.setIntAt(address + 0x0c, MalikSystem.STACKITEM_OVERFLOW);
	}

	private static int nextThreadNumber()
	{
		int result;
		synchronized(THREADS)
		{
			result = ++LAST_NUM;
		}
		return result;
	}

	private static int getMaximumThreads()
	{
		return ((int) (MalikSystem.syscall(0L, 0x000f) >> 32) & 0xff) + 1;
	}


	private class SuspendTask extends Scheduler.Task
	{
		private final Monitor monitor;

		public SuspendTask(Monitor monitor)
		{
			this.monitor = monitor;
		}

		public void run()
		{
			Thread owner;
			if((owner = Thread.this).state == WAITING)
			{
				owner.suspendTask = null;
				owner.suspend(false);
				monitor.wakeup();
			}
		}

		protected void cancelled()
		{
			monitor.wakeup();
		}
	}

	private class ResumeTask extends Scheduler.Task
	{
		public ResumeTask()
		{
		}

		public void run()
		{
			Thread owner;
			if((owner = Thread.this).state == SUSPENDED)
			{
				owner.resumeTask = null;
				owner.resume();
			}
		}
	}

	private boolean stackTrace;
	boolean interrupted;
	private int id;
	int state;
	private int priority;
	private int stackBottom;
	Scheduler.Task resumeTask;
	Scheduler.Task suspendTask;
	Monitor blockedBy;
	private final Runnable target;
	private final String name;
	private final Object monitor;

	public Thread()
	{
		this(null, null);
	}

	public Thread(String name)
	{
		this(null, name);
	}

	public Thread(Runnable target)
	{
		this(target, null);
	}

	public Thread(Runnable target, String name)
	{
		if(name == null || name.length() <= 0)
		{
			name = "Поток исполнения #".concat(Integer.toString(nextThreadNumber()));
		}
		this.id = -1;
		this.state = CREATED;
		this.priority = NORM_PRIORITY;
		this.stackBottom = 0;
		this.target = target;
		this.name = name;
		this.monitor = new Object();
	}

	private Thread(int priority, int id)
	{
		int address;
		long answer;
		if(priority < MIN_PRIORITY || priority > MAX_PRIORITY)
		{
			throw new IllegalArgumentException("Thread: аргумент priority выходит из диапазона.");
		}
		writeStackLimit((address = (int) (answer = MalikSystem.syscall(0L, 0x0006))) + STACK_RESERVED);
		this.id = id;
		this.state = RUNNING;
		this.priority = priority;
		this.stackBottom = address + (int) (answer >> 32) + 1;
		this.target = null;
		this.name = "Главный поток исполнения";
		this.monitor = new Object();
		MalikSystem.syscall(id, priority << 1, 0x0009);
	}

	public String toString()
	{
		return (new StringBuilder()).append("Поток исполнения[").append(name).append(", приоритет=").append(priority).append("]").toString();
	}

	public void run()
	{
	}

	public void start()
	{
		boolean flag;
		int error = 0;
		flag = MalikSystem.enterMonopolyAccess();
		try
		{
			label0:
			{
				int id;
				int address;
				long answer;
				if(state != CREATED)
				{
					error = 1;
					break label0;
				}
				if((answer = MalikSystem.syscall(MalikSystem.getMethodAddress("java/lang/Thread.execute()V"), MalikSystem.convertToReference(this), 0x0000)) == -1L)
				{
					error = 2;
					break label0;
				}
				writeStackLimit((address = (int) (answer >> 32)) + STACK_RESERVED);
				this.id = id = (int) answer & 0xff;
				this.state = RUNNING;
				this.stackBottom = address + ((int) answer & -0x0100) + 0x0100;
				MalikSystem.syscall(id, priority << 1, 0x0009);
				MalikSystem.syscall((long) id, 0x0003);
				Memory.setMultiThreaded();
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(flag);
		}
		switch(error)
		{
		case 1:
			throw new IllegalThreadStateException((new StringBuilder()).append("Thread.start: поток исполнения \"").append(name).append("\" уже был запущен раннее.").toString());
		case 2:
			throw new OutOfThreadsError("Невозможно создать новый поток исполнения: выполняется максимальное количество потоков исполнения.");
		}
	}

	public void interrupt()
	{
		boolean status = MalikSystem.enterMonopolyAccess();
		try
		{
			label0:
			{
				Monitor monitor;
				Scheduler.Task task;
				interrupted = true;
				if((task = resumeTask) != null)
				{
					/* Thread.sleep(long) */
					resumeTask = null;
					resume();
					task.cancel();
					break label0;
				}
				if((task = suspendTask) != null)
				{
					/* Object.wait(long) */
					/* Object.wait(long, int) */
					suspendTask = null;
					suspend(false);
					task.cancel();
					break label0;
				}
				if((monitor = blockedBy) != null)
				{
					/* Object.wait() */
					suspend(false);
					monitor.wakeup();
					break label0;
				}
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
	}

	public final void join() throws InterruptedException
	{
		Object monitor;
		synchronized(monitor = this.monitor)
		{
			for(; isAlive(); monitor.wait(1000L));
		}
	}

	public final void setPriority(int priority)
	{
		boolean status;
		if(priority < MIN_PRIORITY || priority > MAX_PRIORITY)
		{
			throw new IllegalArgumentException("Thread.setPriority: аргумент priority выходит из диапазона.");
		}
		status = MalikSystem.enterMonopolyAccess();
		try
		{
			int state;
			this.priority = priority;
			if((state = this.state) > CREATED && state < TERMINATED)
			{
				MalikSystem.syscall(id, priority << 1, 0x0009);
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
	}

	public final boolean isAlive()
	{
		int state;
		return (state = this.state) > CREATED && state < TERMINATED;
	}

	public final int getPriority()
	{
		return priority;
	}

	public final String getName()
	{
		return name;
	}

	final void execute()
	{
		int id;
		Throwable exitThrowable;
		THREADS[id = this.id] = this;
		exitThrowable = null;
		try
		{
			Runnable target;
			((target = this.target) == null ? this : target).run();
		}
		catch(Throwable e)
		{
			exitThrowable = e;
		}
		MalikInterrupt.disable();
		ThreadTerminationListenerCollection.instance.notifyListeners(this, exitThrowable);
		THREADS[id] = null;
		state = TERMINATED;
	}

	final void resume()
	{
		switch(state)
		{
		default:
			return;
		case SUSPENDED:
		case WAITING:
			state = RUNNING;
			if(!stackTrace)
			{
				MalikSystem.syscall((long) id, 0x0003);
			}
			return;
		}
	}

	final void suspend(boolean wait)
	{
		switch(state)
		{
		default:
			return;
		case RUNNING:
			state = wait ? WAITING : SUSPENDED;
			if(!stackTrace)
			{
				MalikSystem.syscall((long) id, 0x0002);
			}
			return;
		case SUSPENDED:
		case WAITING:
			state = wait ? WAITING : SUSPENDED;
			return;
		}
	}

	final void enterStackTrace()
	{
		boolean status;
		int error = 0;
		status = MalikSystem.enterMonopolyAccess();
		try
		{
			label0:
			{
				if(id != MalikSystem.getCurrentThreadID())
				{
					switch(state)
					{
					default:
						break;
					case CREATED:
						error = 1;
						break label0;
					case RUNNING:
						MalikSystem.syscall((long) id, 0x0002);
						break;
					case TERMINATED:
						error = 2;
						break label0;
					}
				}
				stackTrace = true;
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
		switch(error)
		{
		case 1:
			throw new IllegalThreadStateException("StackTracer.trace: нельзя выполнить трассировку стека для незапущенного потока исполнения.", true);
		case 2:
			throw new IllegalThreadStateException("StackTracer.trace: нельзя выполнить трассировку стека для завершившегося потока исполнения.", true);
		}
	}

	final void leaveStackTrace()
	{
		boolean flag = MalikSystem.enterMonopolyAccess();
		try
		{
			stackTrace = false;
			if(id != MalikSystem.getCurrentThreadID() && state == RUNNING)
			{
				MalikSystem.syscall((long) id, 0x0003);
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(flag);
		}
	}

	final int getStackBottom()
	{
		return stackBottom;
	}

	final int getID()
	{
		return id;
	}
}
