/*
	Реализация спецификаций 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.application.*;

public final class Scheduler extends Object
{
	public static abstract class Task extends Object implements Runnable
	{
		boolean periodic;
		int mode;
		long period;
		long runningTime;
		long scheduledTime;

		protected Task()
		{
			this.scheduledTime = Long.MIN_VALUE;
		}

		public abstract void run();

		public void cancel()
		{
			Scheduler.cancel(this);
		}

		public final long scheduledTime()
		{
			return scheduledTime;
		}

		protected void cancelled()
		{
		}
	}

	public static final int ACTION = 0;
	public static final int THREAD = 1;
	public static final int MONOPOLY = 2;

	private static int COUNT;
	private static Task[] QUEUE;

	static
	{
		COUNT = 0;
		QUEUE = new Task[0x10];
		MalikInterrupt.register(0x18, new TimerInterruptHandler());
	}

	public static void schedule(Task task, long delay, int mode)
	{
		boolean status;
		if(task == null)
		{
			throw new NullPointerException("Scheduler.schedule: аргумент task равен нулевой ссылке.");
		}
		if(delay <= 0L)
		{
			throw new IllegalArgumentException("Scheduler.schedule: аргумент delay может быть только положительным.");
		}
		if(mode < ACTION || mode > MONOPOLY)
		{
			throw new IllegalArgumentException("Scheduler.schedule: недопустимое значение аргумента mode.");
		}
		status = MalikSystem.enterMonopolyAccess();
		try
		{
			if(indexOf(task) < 0)
			{
				task.periodic = false;
				task.mode = mode;
				task.runningTime = System.currentTimeMillis() + delay;
				push(task);
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
	}

	public static void schedule(Task task, long delay, long period, int mode)
	{
		boolean status;
		if(task == null)
		{
			throw new NullPointerException("Scheduler.schedule: аргумент task равен нулевой ссылке.");
		}
		if(delay <= 0L)
		{
			throw new IllegalArgumentException("Scheduler.schedule: аргумент delay может быть только положительным.");
		}
		if(period <= 0L)
		{
			throw new IllegalArgumentException("Scheduler.schedule: аргумент period может быть только положительным.");
		}
		if(mode < ACTION || mode > MONOPOLY)
		{
			throw new IllegalArgumentException("Scheduler.schedule: недопустимое значение аргумента mode.");
		}
		status = MalikSystem.enterMonopolyAccess();
		try
		{
			if(indexOf(task) < 0)
			{
				task.periodic = true;
				task.mode = mode;
				task.period = period;
				task.runningTime = System.currentTimeMillis() + delay;
				push(task);
			}
		}
		finally
		{
			MalikSystem.leaveMonopolyAccess(status);
		}
	}

	static void cancel(Task task)
	{
		try
		{
			boolean cancel = false;
			boolean status = MalikSystem.enterMonopolyAccess();
			try
			{
				int index;
				if((index = indexOf(task)) >= 0)
				{
					if(index < COUNT - 1)
					{
						MalikSystem.arraycopyf_object(QUEUE, index + 1, QUEUE, index, COUNT - index - 1);
					}
					QUEUE[--COUNT] = null;
					updateTimer();
					if(task.mode != MONOPOLY)
					{
						cancel = true;
					} else
					{
						task.cancelled();
					}
				}
			}
			finally
			{
				MalikSystem.leaveMonopolyAccess(status);
			}
			if(cancel)
			{
				task.cancelled();
			}
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
	}

	static void push(Task task)
	{
		int index;
		long runningTime;
		if(COUNT == QUEUE.length)
		{
			MalikSystem.arraycopyf_object(QUEUE, 0, QUEUE = new Task[COUNT << 1], 0, COUNT);
		}
		index = COUNT;
		runningTime = task.runningTime;
		for(int i = 0; i < index; i++)
		{
			if(QUEUE[i].runningTime <= runningTime)
			{
				MalikSystem.arraycopyb_object(QUEUE, COUNT, QUEUE, COUNT + 1, COUNT - (index = i));
				break;
			}
		}
		COUNT++;
		QUEUE[index] = task;
		updateTimer();
	}

	static Task pop()
	{
		long remainingTime;
		Task result;
		if(COUNT == 0)
		{
			return null;
		}
		if((remainingTime = (result = QUEUE[COUNT - 1]).runningTime - System.currentTimeMillis()) > 0L)
		{
			MalikSystem.syscall(remainingTime, 0x0040);
			return null;
		}
		QUEUE[--COUNT] = null;
		updateTimer();
		return result;
	}

	private static void updateTimer()
	{
		long remainingTime;
		if(COUNT > 0)
		{
			if((remainingTime = QUEUE[COUNT - 1].runningTime - System.currentTimeMillis()) <= 0L)
			{
				remainingTime = 1L;
			}
			MalikSystem.syscall(remainingTime, 0x0040);
			return;
		}
		MalikSystem.syscall(0L, 0x0040);
	}

	private static int indexOf(Task task)
	{
		return COUNT > 0 ? MalikSystem.arrayfindb_object(QUEUE, COUNT - 1, task) : -1;
	}


	private Scheduler()
	{
	}
}

final class TimerInterruptHandler extends InterruptHandler implements InterruptLong
{
	public TimerInterruptHandler()
	{
	}

	public void interrupt(long argument)
	{
		MalikSystem.disableThreadSwitching();
		try
		{
			Scheduler.Task task;
			if((task = Scheduler.pop()) != null)
			{
				try
				{
					task.scheduledTime = task.runningTime;
					switch(task.mode)
					{
					case Scheduler.THREAD:
						(new Thread(task, "Задача планировщика задач")).start();
						break;
					case Scheduler.ACTION:
						Run.getInstance().setEvent(task);
						break;
					case Scheduler.MONOPOLY:
						task.run();
						break;
					}
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				if(task.periodic)
				{
					task.runningTime = System.currentTimeMillis() + task.period;
					Scheduler.push(task);
				}
			}
		}
		finally
		{
			MalikSystem.enableThreadSwitching();
		}
	}
}
