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

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

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

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

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

/* Структура экрана:                                                        отметки
 * +--------------------------------+     +--------------------------------+ -- p0
 * | Заголовок (title)              |     | Заголовок (title)              |
 * +--------------------------------+     +--------------------------------+ -- p1
 * | Клиентская часть (client)      |     | Бегущая строка (ticker)        |
 * |                                |     +--------------------------------+ -- p2
 * |                                |     | Клиентская часть (client)      |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                | ИЛИ |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * |                                |     |                                |
 * +----------+----------+----------+     +----------+----------+----------+ -- p3
 * |    К1    |    К2    |    К3    |     |    К1    |    К2    |    К3    |
 * +----------+----------+----------+     +----------+----------+----------+ -- p4
 *      Режим без бегущей строки               Режим с бегущей строкой
 *           (по умолчанию)                  (если задана бегущая строка)
 *        (MODE_WITHOUT_TICKER)                   (MODE_WITH_TICKER)
 * В полноэкранном режиме (MODE_FULL_SCREEN) отображается только клиентская
 * часть, простирающаяся от отметки p0 до отметки p4, остальные элементы
 * (даже если они заданы) на экране отсутствуют.
 * К1 - команда левой программной клавиши,
 * К2 - команда центральной клавиши выбора,
 * К3 - команда правой программной клавиши.
 * Команда К2 - основная команда выбора, выделяется от остальных двух
 * команд (default command).
 * Команды К1 и К3 - сторонние команды, выглядят одинаково (side commands).
 */

package javax.microedition.lcdui;

import java.io.*;
import java.util.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;
import malik.emulator.application.*;
import malik.emulator.media.graphics.*;

public class Display extends Object
{
	public static final int LIST_ELEMENT = 1;
	public static final int CHOICE_GROUP_ELEMENT = 2;
	public static final int ALERT = 3;
	public static final int COLOR_BACKGROUND = 0;
	public static final int COLOR_FOREGROUND = 1;
	public static final int COLOR_HIGHLIGHTED_BACKGROUND = 2;
	public static final int COLOR_HIGHLIGHTED_FOREGROUND = 3;
	public static final int COLOR_BORDER = 4;
	public static final int COLOR_HIGHLIGHTED_BORDER = 5;

	static final int EVENT_KEY_PRESSED = (char) 0x0001;
	static final int EVENT_KEY_REPEATED = (char) 0x0002;
	static final int EVENT_KEY_RELEASED = (char) 0x0003;
	static final int EVENT_POINTER_PRESSED = (char) 0x0005;
	static final int EVENT_POINTER_DRAGGED = (char) 0x0006;
	static final int EVENT_POINTER_RELEASED = (char) 0x0007;
	static final int EVENT_WINDOW_SHOW = (char) 0x000a;
	static final int EVENT_WINDOW_HIDE = (char) 0x000b;
	private static final int EVENT_EMPTY = (char) 0x0000;
	private static final int EVENT_CUSTOM = (char) 0x000d;
	private static final int EVENT_PAINT = (char) 0x000e;

	private static final int MIDLET_EVENT_QUEUE_INITIAL_SIZE = 0x21;

	private static boolean SERIALIZE_REPAINT;
	private static int MAXIMUM_FREQUENCY;
	private static int MINIMUM_PERIOD;

	static
	{
		setSerializeRepaint(true);
		setMaximumFrequency(25);
	}

	public static void setSerializeRepaint(boolean serializeRepaint)
	{
		SERIALIZE_REPAINT = serializeRepaint;
	}

	public static void setMaximumFrequency(int maximumFrequency)
	{
		MAXIMUM_FREQUENCY = maximumFrequency = maximumFrequency < 1 ? 1 :
				maximumFrequency > 100 ? 100 : maximumFrequency;
		MINIMUM_PERIOD = 1000 / maximumFrequency + ((1000 % maximumFrequency) > 0 ? 1 : 0);
	}

	public static boolean isSerializeRepaint()
	{
		return SERIALIZE_REPAINT;
	}

	public static int getMaximumFrequency()
	{
		return MAXIMUM_FREQUENCY;
	}

	public static int getMinimumPeriod()
	{
		return MINIMUM_PERIOD;
	}

	public static Display getDisplay(MIDlet midlet)
	{
		if(midlet == null)
		{
			throw new NullPointerException("Display.getDisplay: " +
					"параметр midlet равен нулевой ссылке.");
		}
		return MIDletProxy.getInstance().getEmulatorScreen();
	}


	private class SystemScreenChangeEvent extends Object
			implements Runnable
	{
		public SystemScreenChangeEvent()
		{
		}

		public void run()
		{
			/* код этого метода выполняется только в главном потоке */
			Display display = Display.this;
			Displayable curr = display.shownSystemScreen;
			Displayable next = display.currentSystemScreen;
			MIDletEventStream events = display.clientEvents;
			if(display.applicationInBackground || curr == next)
			{
				display.shownSystemScreen = next;
				return;
			}
			if(curr != null)
			{
				display.shownSystemScreen = null;
				try
				{
					curr.eventHide();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
			}
			if(events != null)
			{
				if(curr == null)
				{
					events.setEvent(EVENT_WINDOW_HIDE);
				}
				if(next == null)
				{
					events.setEvent(EVENT_WINDOW_SHOW);
				}
			}
			if(next != null)
			{
				try
				{
					next.eventShow();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				display.shownSystemScreen = next;
				Displayable.tickerUpdate();
				display.callSeriallyPaint(next);
			}
		}
	}

	private class SystemScreenRepaintEvent extends Object
			implements Runnable, Rectangle, ImmediatelyRunnable
	{
		protected final Displayable screen;

		public SystemScreenRepaintEvent(Displayable screen)
		{
			this.screen = screen;
		}

		public void run()
		{
			(Display.this).handlePaint(screen);
		}

		public int getLeft()
		{
			return 0;
		}

		public int getTop()
		{
			return 0;
		}

		public int getRight()
		{
			Displayable screen;
			return (screen = this.screen) == null ? (Display.this).getWidth() : screen.getClientWidth();
		}

		public int getBottom()
		{
			Displayable screen;
			return (screen = this.screen) == null ? (Display.this).getHeight() : screen.getClientHeight();
		}

		public Object argument()
		{
			return screen;
		}
	}

	private class SystemScreenRepaintRegionEvent extends SystemScreenRepaintEvent
	{
		private int left;
		private int top;
		private int right;
		private int bottom;

		public SystemScreenRepaintRegionEvent(Displayable screen, int left, int top, int right, int bottom)
		{
			super(screen);
			this.left = left;
			this.top = top;
			this.right = right;
			this.bottom = bottom;
		}

		public void run()
		{
			int l;
			int t;
			(Display.this).handlePaint(screen, l = left, t = top, right - l, bottom - t);
		}

		public int getLeft()
		{
			return left;
		}

		public int getTop()
		{
			return top;
		}

		public int getRight()
		{
			return right;
		}

		public int getBottom()
		{
			return bottom;
		}
	}

	private class ClientScreenChangeEvent extends Object
			implements Runnable
	{
		public ClientScreenChangeEvent()
		{
		}

		public void run()
		{
			/* код этого метода выполняется только в потоке-обработчике событий мидлета */
			Display display = Display.this;
			Displayable curr = display.shownClientScreen;
			Displayable next = display.currentClientScreen;
			if(display.applicationInBackground || display.shownSystemScreen != null ||
					display.currentSystemScreen != null || curr == next)
			{
				display.shownClientScreen = next;
				return;
			}
			if(curr != null)
			{
				display.shownClientScreen = null;
				try
				{
					curr.eventHide();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
			}
			if(next != null)
			{
				try
				{
					next.eventShow();
				}
				catch(RuntimeException e)
				{
					e.printRealStackTrace();
				}
				display.shownClientScreen = next;
				Displayable.tickerUpdate();
				display.callSeriallyPaint(next);
			}
		}
	}

	private class ClientScreenRepaintRegionEvent extends Object
			implements Runnable, Rectangle, ImmediatelyRunnable
	{
		private int left;
		private int top;
		private int right;
		private int bottom;

		private Displayable screen;

		public ClientScreenRepaintRegionEvent(Displayable screen, int left, int top, int right, int bottom)
		{
			this.left = left;
			this.top = top;
			this.right = right;
			this.bottom = bottom;
			this.screen = screen;
		}

		public void run()
		{
			int l;
			int t;
			(Display.this).handlePaint(screen, l = left, t = top, right - l, bottom - t);
		}

		public int getLeft()
		{
			return left;
		}

		public int getTop()
		{
			return top;
		}

		public int getRight()
		{
			return right;
		}

		public int getBottom()
		{
			return bottom;
		}

		public Object argument()
		{
			return screen;
		}
	}

	private class MIDletTerminationEvent extends Object
			implements Runnable
	{
		public MIDletTerminationEvent()
		{
		}

		public void run()
		{
			/* код этого метода выполняется только в потоке-обработчике событий мидлета */
			Display display;
			(display = Display.this).dispatcher.stopMIDlet();
			display.terminateEventStreams();
		}
	}

	private class MIDletEventStream extends Queue
			implements Runnable
	{
		private boolean terminated;
		private long[] eventsFromUser;
		private Runnable[] eventsFromApp;
		private Displayable[] eventsScreens;

		public MIDletEventStream()
		{
			super(MIDLET_EVENT_QUEUE_INITIAL_SIZE);
			this.eventsFromUser = new long[MIDLET_EVENT_QUEUE_INITIAL_SIZE];
			this.eventsFromApp = new Runnable[MIDLET_EVENT_QUEUE_INITIAL_SIZE];
			this.eventsScreens = new Displayable[MIDLET_EVENT_QUEUE_INITIAL_SIZE];
		}

		public void removeTailElement()
		{
			int t = tail;
			eventsFromUser[t] = 0L;
			eventsFromApp[t] = null;
			eventsScreens[t] = null;
			advanceTail();
		}

		public void run()
		{
			long fromUser;
			Runnable fromApp;
			Displayable screen;
			Display display = Display.this;
			do
			{
				waitEvent();
				if(terminated) break;
				if(isEmpty()) continue;
				synchronized(this)
				{
					fromUser = peekEventFromUser();
					fromApp = peekEventFromApp();
					screen = peekEventScreen();
					removeTailElement();
				}
				if(screen == null && ((short) fromUser) != EVENT_CUSTOM)
				{
					screen = display.getCurrent();
				}
				switch((short) fromUser)
				{
				case EVENT_EMPTY:
					break;
				case EVENT_PAINT:
					display.handlePaint(screen);
					break;
				case EVENT_CUSTOM:
					display.handleEvent(fromApp);
					break;
				default:
					display.handleEvent(screen,
							(short) fromUser, (short) (fromUser >> 16),
							(short) (fromUser >> 32), (short) (fromUser >> 48));
					break;
				}
			} while(true);
		}

		public void serviceRepaints(Displayable argument)
		{
			boolean init = false;
			int left;
			int top;
			int right;
			int bottom;
			left = top = right = bottom = 0;
			for(int i; ; )
			{
				long fromUser = 0L;
				Runnable fromApp = null;
				Displayable screen = null;
				synchronized(this)
				{
					if((i = findImmediatelyRunnable(argument)) >= 0)
					{
						long[] u;
						Runnable[] a;
						Displayable[] s;
						fromUser = (u = eventsFromUser)[i];
						fromApp = (a = eventsFromApp)[i];
						screen = (s = eventsScreens)[i];
						u[i] = (long) EVENT_EMPTY;
						a[i] = null;
						s[i] = null;
					}
				}
				if(i < 0) break;
				if((char) fromUser == EVENT_PAINT)
				{
					left = init ? Math.min(left, 0) : 0;
					top = init ? Math.min(top, 0) : 0;
					right = init ? Math.max(right, screen.getClientWidth()) : screen.getClientWidth();
					bottom = init ? Math.max(bottom, screen.getClientHeight()) : screen.getClientHeight();
					init = true;
					continue;
				}
				if(fromApp instanceof Rectangle)
				{
					Rectangle rect = (Rectangle) fromApp;
					left = init ? Math.min(left, rect.getLeft()) : rect.getLeft();
					top = init ? Math.min(top, rect.getTop()) : rect.getTop();
					right = init ? Math.max(right, rect.getRight()) : rect.getRight();
					bottom = init ? Math.max(bottom, rect.getBottom()) : rect.getBottom();
					init = true;
					continue;
				}
			}
			if(init) (Display.this).handlePaint(argument, left, top, right - left, bottom - top);
		}

		public synchronized void setEvent(int event)
		{
			push((long) (event & 0xffff), null, null);
			notify();
		}

		public synchronized void setEvent(int event, int param1, int param2, int param3)
		{
			push((long) (event & 0xffff) | (long) (param1 & 0xffff) << 16 |
					(long) (param2 & 0xffff) << 32 | (long) (param3 & 0xffff) << 48, null, null);
			notify();
		}

		public synchronized void setEvent(Runnable event)
		{
			push((long) EVENT_CUSTOM, event, null);
			notify();
		}

		public synchronized void setPaint(Displayable screen)
		{
			push((long) EVENT_PAINT, null, screen);
			notify();
		}

		public synchronized void terminate()
		{
			terminated = true;
			notify();
		}

		private synchronized void waitEvent()
		{
			do
			{
				try
				{
					wait();
					break;
				}
				catch(InterruptedException e)
				{
					e.printRealStackTrace();
				}
			} while(true);
		}

		private void push(long eventFromUser, Runnable eventFromApp, Displayable eventScreen)
		{
			int h;
			int nc;
			long[] lq = eventsFromUser;
			Runnable[] rq = eventsFromApp;
			Displayable[] dq = eventsScreens;
			if(getFreeElements() == 0)
			{
				nc = (capacity << 1) - 1;
				lq = eventsFromUser = expand(lq, nc);
				rq = eventsFromApp = (Runnable[]) expand(rq, nc);
				dq = eventsScreens = (Displayable[]) expand(dq, nc);
				reset(nc);
			}
			h = head;
			lq[h] = eventFromUser;
			rq[h] = eventFromApp;
			dq[h] = eventScreen;
			advanceHead();
		}

		private int findImmediatelyRunnable(Object argument)
		{
			int t = tail;
			int c = capacity;
			long[] u = eventsFromUser;
			Runnable[] e = eventsFromApp;
			Displayable[] s = eventsScreens;
			for(int i = head; i != t; )
			{
				Runnable r;
				i = (c + i - 1) % c;
				if((char) u[i] == EVENT_PAINT && s[i] == argument ||
						(r = e[i]) instanceof ImmediatelyRunnable &&
						((ImmediatelyRunnable) r).argument() == argument)
				{
					return i;
				}
			}
			return -1;
		}

		private long peekEventFromUser()
		{
			return eventsFromUser[tail];
		}

		private Runnable peekEventFromApp()
		{
			return eventsFromApp[tail];
		}

		private Displayable peekEventScreen()
		{
			return eventsScreens[tail];
		}
	}

	private final class MIDletExecution extends MIDletEventStream
	{
		public MIDletExecution()
		{
		}

		public void run()
		{
			Display display = Display.this;
			PrintStream printer = System.out;
			MIDletProxy proxy = MIDletProxy.getInstance();
			MIDletDispatcher dispatcher = display.dispatcher;

			/* вывод сообщения о запуске мидлета */
			printer.println("Запуск приложения…");
			printer.println("Класс мидлета: ".concat(
					dispatcher.getMIDletClass().getCanonicalName()));

			/* запуск мидлета */
			dispatcher.startMIDlet();

			/* оповещение о начале работы мидлета */
			proxy.notifyMIDletStarted();

			if(dispatcher.mustDestroyed())
			{
				/* неудачная попытка запуска? */
				printer.println("Неудачная попытка запуска – завершение работы приложения.");
				display = null;

				/* остановка мидлета */
				dispatcher.stopMIDlet();
				dispatcher = null;

				/* оповещение о завершении работы мидлета */
				proxy.notifyMIDletTerminated();
				return;
			}

			/* мидлет удачно запущен */
			printer.println("Приложение запущено и работает.");
			dispatcher = null;

			/* скрытие системного экрана */
			display.hideSystemScreen();
			display = null;

			/* обработка событий мидлета */
			super.run();

			/* закрываем все хранилища записей */
			RecordStore.closeAllRecordStores();

			/* вывод сообщения о завершении мидлета */
			printer.println("Приложение завершило работу.");

			/* оповещение о завершении работы мидлета */
			proxy.notifyMIDletTerminated();
		}
	}

	private boolean applicationInBackground;
	private int width;
	private int height;
	private long lastRepaintTimestamp;
	private int[] guiElementsMetrics;
	private Font[] guiElementsFonts;
	private Graphics[] graphicsForModesOfSystemScreen; // можно использовать в качестве монитора для визуализации системных экранов
	private Graphics[] graphicsForModesOfClientScreen; // можно использовать в качестве монитора для визуализации клиентских экранов
	private Font defaultCommandFont;
	private Graphics graphicsForSystemScreen;
	private Graphics graphicsForClientScreen;
	private RasterCanvas screenCanvas;
	private Displayable shownSystemScreen;
	private Displayable shownClientScreen;
	private Displayable currentSystemScreen;
	private Displayable currentClientScreen;
	private Displayable currentClientAfterAlertScreen;
	private MIDletDispatcher dispatcher;
	private MIDletEventStream paintEvents;
	private MIDletEventStream clientEvents;

	protected Display(RasterCanvas screenCanvas)
	{
		int a;
		int w;
		int h;
		int p0;
		int p1;
		int p2;
		int p3;
		int p4;
		Font titleFont;
		Font tickerFont;
		Font sideCommandFont;
		Font defaultCommandFont;
		RasterCanvas systemCanvas;
		RasterCanvas clientCanvas;
		Graphics systemGraphics;
		Graphics clientGraphics;
		if(screenCanvas == null)
		{
			throw new NullPointerException("Display: аргумент screenCanvas равен нулевой ссылке.");
		}
		if(!screenCanvas.isOpaque())
		{
			throw new IllegalStateException("Display: аргумент screenCanvas должен ссылаться на непрозрачную канву.");
		}
		titleFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
		tickerFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
		sideCommandFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
		defaultCommandFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
		w = screenCanvas.getWidth();
		h = screenCanvas.getHeight();
		a = w * h;
		systemCanvas = new RasterCanvas(new GraphicBuffer(new int[a], 0, w, w, h), true);
		clientCanvas = new RasterCanvas(new GraphicBuffer(new int[a], 0, w, w, h), true);
		p0 = 0;
		p1 = p0 + (titleFont.getHeight() + 4);
		p2 = p1 + (tickerFont.getHeight() + 4);
		p3 = h - (Math.max(defaultCommandFont.getHeight(), sideCommandFont.getHeight()) + 4);
		p4 = h;
		this.width = w;
		this.height = h;
		this.guiElementsMetrics = new int[] {
				/*			|         MODE_WITHOUT_TICKER|                   MODE_WITH_TICKER| MODE_FULL_SCREEN|*/
				/*			|  TITLE|--|  CLIENT|COMMANDS|   TITLE|  TICKER|  CLIENT|COMMANDS|--|--|  CLIENT|--|*/
				/*  LEFT*/	       0, 0,       0,       0,       0,       0,       0,       0, 0, 0,       0, 0,
				/*   TOP*/	      p0, 0,      p1,      p3,      p0,      p1,      p2,      p3, 0, 0,      p0, 0,
				/* WIDTH*/	       w, 0,       w,       w,       w,       w,       w,       w, 0, 0,       w, 0,
				/*HEIGHT*/	 p1 - p0, 0, p3 - p1, p4 - p3, p1 - p0, p2 - p1, p3 - p2, p4 - p3, 0, 0, p4 - p0, 0
		};
		this.guiElementsFonts = new Font[] {
				titleFont, tickerFont, Font.getDefaultFont(), sideCommandFont
		};
		this.graphicsForModesOfSystemScreen = new Graphics[] {
				new GraphicsClipRestricted(systemCanvas.subcanvas(0, p1, w, p3 - p1)),
				new GraphicsClipRestricted(systemCanvas.subcanvas(0, p2, w, p3 - p2)),
				systemGraphics = new Graphics(systemCanvas)
		};
		this.graphicsForModesOfClientScreen = new Graphics[] {
				new GraphicsClipRestricted(clientCanvas.subcanvas(0, p1, w, p3 - p1)),
				new GraphicsClipRestricted(clientCanvas.subcanvas(0, p2, w, p3 - p2)),
				clientGraphics = new Graphics(clientCanvas)
		};
		this.defaultCommandFont = defaultCommandFont;
		this.graphicsForSystemScreen = systemGraphics;
		this.graphicsForClientScreen = clientGraphics;
		this.screenCanvas = screenCanvas;
	}

	public void callSerially(Runnable event)
	{
		if(event != null)
		{
			clientEvents.setEvent(event);
		}
	}

	public void setCurrent(Displayable next)
	{
		if(next == null)
		{
			return;
		}
		if(next.isSystem())
		{
			currentSystemScreen = next;
			Run.getInstance().setEvent(this.new SystemScreenChangeEvent());
			return;
		}
		currentClientAfterAlertScreen = next instanceof Alert ? currentClientScreen : null;
		currentClientScreen = next;
		clientEvents.setEvent(this.new ClientScreenChangeEvent());
	}

	public void setCurrent(Alert next, Displayable after)
	{
		if(next == null)
		{
			throw new NullPointerException("Display.setCurrent: " +
					"параметр next равен нулевой ссылке.");
		}
		if(after == null)
		{
			throw new NullPointerException("Display.setCurrent: " +
					"параметр after равен нулевой ссылке.");
		}
		if(next.isSystem() || after.isSystem())
		{
			throw new IllegalArgumentException("Display.setCurrent(Alert, Displayable): " +
					"для системных экранов следует использовать метод setCurrent(Displayable).");
		}
		if(after instanceof Alert)
		{
			throw new IllegalArgumentException("Display.setCurrent(Alert, Displayable): " +
					"параметр after не может быть типа Alert.");
		}
		currentClientAfterAlertScreen = after;
		currentClientScreen = next;
		clientEvents.setEvent(this.new ClientScreenChangeEvent());
	}

	public void setCurrentItem(Item item)
	{
		Container owner;
		if(item == null)
		{
			throw new NullPointerException("Display.setCurrentItem: " +
					"параметр item равен нулевой ссылке.");
		}
		if((owner = item.getOwner()) == null)
		{
			throw new IllegalStateException("Display.setCurrentItem: " +
					"элемент item не содержится в каком-либо контейнере.");
		}
		owner.setCurrent(item, this);
	}

	public void hideSystemScreen()
	{
		currentSystemScreen = null;
		Run.getInstance().setEvent(this.new SystemScreenChangeEvent());
	}

	public boolean flashBacklight(int duration)
	{
		if(duration < 0)
		{
			throw new IllegalArgumentException("Display.flashBacklight: " +
					"параметр duration не может быть отрицательным.");
		}
		return isClientInForeground();
	}

	public boolean vibrate(int duration)
	{
		if(duration < 0)
		{
			throw new IllegalArgumentException("Display.vibrate: " +
					"параметр duration не может быть отрицательным.");
		}
		return isClientInForeground();
	}

	public boolean isColor()
	{
		return true;
	}

	public int numColors()
	{
		return 0x01000000;
	}

	public int numAlphaLevels()
	{
		return 0x00000100;
	}

	public int getBestImageWidth(int type)
	{
		switch(type)
		{
		default:
			throw new IllegalArgumentException("Display.getBestImageWidth: " +
					"недопустимое значение параметра type.");
		case CHOICE_GROUP_ELEMENT:
			return 24;
		case LIST_ELEMENT:
			return 32;
		case ALERT:
			return 48;
		}
	}

	public int getBestImageHeight(int type)
	{
		switch(type)
		{
		default:
			throw new IllegalArgumentException("Display.getBestImageHeight: " +
					"недопустимое значение параметра type.");
		case CHOICE_GROUP_ELEMENT:
			return 24;
		case LIST_ELEMENT:
			return 32;
		case ALERT:
			return 48;
		}
	}

	public int getBorderStyle(boolean highlighted)
	{
		return Graphics.SOLID;
	}

	public int getColor(int colorSpecifier)
	{
		switch(colorSpecifier)
		{
		default:
			throw new IllegalArgumentException("Display.getColor: " +
					"недопустимое значение аргумента colorSpecifier.");
		case COLOR_FOREGROUND:
			return RasterCanvas.getSystemColor(0x20);
		case COLOR_HIGHLIGHTED_BACKGROUND:
			return RasterCanvas.getSystemColor(0x19);
		case COLOR_HIGHLIGHTED_FOREGROUND:
			return RasterCanvas.getSystemColor(0x21);
		case COLOR_BACKGROUND:
		case COLOR_BORDER:
		case COLOR_HIGHLIGHTED_BORDER:
			return RasterCanvas.getSystemColor(0x05);
		}
	}

	public Displayable getCurrent()
	{
		return currentClientScreen;
	}

	public final void flushGraphics(Canvas screen, Image image, int left, int top, int width, int height)
	{
		Graphics context;
		if(screen == null || !isShown(screen)) return;
		context = getClientGraphicsFor(screen);
		try
		{
			context.reset();
			context.setClip(left, top, width, height);
			context.drawImage(image, 0, 0, Graphics.LEFT | Graphics.TOP);
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		paintDisplay(screen, screen.isSystem() ? graphicsForSystemScreen : graphicsForClientScreen);
	}

	public final boolean isClientInForeground()
	{
		return !applicationInBackground && currentSystemScreen == null;
	}

	public final int getWidth()
	{
		return width;
	}

	public final int getHeight()
	{
		return height;
	}

	public final Displayable getActive()
	{
		Displayable systemScreen;
		return (systemScreen = currentSystemScreen) != null ? systemScreen : currentClientScreen;
	}

	final void startMIDlet(Class midletClass)
	{
		MIDletEventStream p;
		MIDletEventStream c;
		synchronized(guiElementsMetrics)
		{
			if(dispatcher == null)
			{
				dispatcher = new MIDletDispatcher(midletClass);
				paintEvents = p = this.new MIDletEventStream();
				clientEvents = c = this.new MIDletExecution();
				(new Thread(p, "Обработчик событий перерисовки экрана")).start();
				(new Thread(c, "Обработчик событий мидлета")).start();
			}
		}
	}

	final void stopMIDlet()
	{
		MIDletEventStream m;
		synchronized(guiElementsMetrics)
		{
			if((m = clientEvents) != null)
			{
				m.setEvent(this.new MIDletTerminationEvent());
			}
		}
	}

	final void terminateEventStreams()
	{
		paintEvents.terminate();
		clientEvents.terminate();
	}

	final void setEvent(int event, int param1, int param2, int param3)
	{
		Displayable screen;
		MIDletEventStream events;
		switch(event)
		{
		case EVENT_WINDOW_SHOW:
			applicationInBackground = false;
			Displayable.tickerUpdate();
			break;
		case EVENT_WINDOW_HIDE:
			applicationInBackground = true;
			Displayable.tickerUpdate();
			break;
		}
		if((screen = shownSystemScreen) == null) screen = shownClientScreen;
		if(screen == null) return;
		if(screen.isSystem())
		{
			handleEvent(screen, event, param1, param2, param3);
			return;
		}
		if((events = clientEvents) != null)
		{
			events.setEvent(event, param1, param2, param3);
		}
	}

	final void callSeriallyEvent(Displayable screen, Runnable event)
	{
		MIDletEventStream events;
		if(event == null) return;
		if(screen.isSystem())
		{
			Run.getInstance().setEvent(event);
			return;
		}
		if((events = clientEvents) != null)
		{
			events.setEvent(event);
		}
	}

	final void callSeriallyPaint(Displayable screen)
	{
		MIDletEventStream events;
		if(screen.isSystem())
		{
			Run.getInstance().setEvent(this.new SystemScreenRepaintEvent(screen));
			return;
		}
		if((events = SERIALIZE_REPAINT ? clientEvents : paintEvents) != null)
		{
			events.setPaint(screen);
		}
	}

	final void callSeriallyPaint(Displayable screen, int left, int top, int width, int height)
	{
		MIDletEventStream events;
		if(screen.isSystem())
		{
			Run.getInstance().setEvent(this.new SystemScreenRepaintRegionEvent(screen,
					left, top, left + width, top + height));
			return;
		}
		if((events = SERIALIZE_REPAINT ? clientEvents : paintEvents) != null)
		{
			events.setEvent(this.new ClientScreenRepaintRegionEvent(screen,
					left, top, left + width, top + height));
		}
	}

	final void serviceRepaints(Displayable screen)
	{
		MIDletEventStream eventstream;
		if(screen.isSystem())
		{
			boolean init = false;
			int left;
			int top;
			int right;
			int bottom;
			left = top = right = bottom = 0;
			Runnable[] events = Run.getInstance().getImmediatelyServices(screen);
			for(int len = events == null ? 0 : events.length, i = 0; i < len; i++)
			{
				Runnable event;
				if((event = events[i]) != null && event instanceof Rectangle)
				{
					Rectangle rect = (Rectangle) event;
					left = init ? Math.min(left, rect.getLeft()) : rect.getLeft();
					top = init ? Math.min(top, rect.getTop()) : rect.getTop();
					right = init ? Math.max(right, rect.getRight()) : rect.getRight();
					bottom = init ? Math.max(bottom, rect.getBottom()) : rect.getBottom();
					init = true;
				}
			}
			if(init) handlePaint(screen, left, top, right - left, bottom - top);
			return;
		}
		if((eventstream = SERIALIZE_REPAINT ? clientEvents : paintEvents) != null)
		{
			eventstream.serviceRepaints(screen);
		}
	}

	final boolean isShown(Displayable screen)
	{
		return !applicationInBackground && (screen.isSystem() ? shownSystemScreen == screen :
				shownSystemScreen == null && shownClientScreen == screen);
	}

	final int getGUIElementLeft(Displayable screen, int guiElement)
	{
		return getGUIElementMetric(screen.getMode(), guiElement, 0);
	}

	final int getGUIElementTop(Displayable screen, int guiElement)
	{
		return getGUIElementMetric(screen.getMode(), guiElement, 1);
	}

	final int getGUIElementWidth(Displayable screen, int guiElement)
	{
		return getGUIElementMetric(screen.getMode(), guiElement, 2);
	}

	final int getGUIElementHeight(Displayable screen, int guiElement)
	{
		return getGUIElementMetric(screen.getMode(), guiElement, 3);
	}

	final Font getGUIElementFont(int guiElement)
	{
		Font[] fontFor;
		if(guiElement < 0 || guiElement >= (fontFor = guiElementsFonts).length)
		{
			throw new IllegalArgumentException("Display.getGUIElementFont: " +
					"недопустимое значение параметра guiElement.");
		}
		return fontFor[guiElement];
	}

	final Font getDefaultCommandFont()
	{
		return defaultCommandFont;
	}

	final Graphics getClientGraphicsFor(Displayable screen)
	{
		return (screen.isSystem() ? graphicsForModesOfSystemScreen : graphicsForModesOfClientScreen)[screen.getMode()];
	}

	final Displayable getShownScreen()
	{
		Displayable screen;
		return applicationInBackground ? null : (screen = shownSystemScreen) != null ? screen : shownClientScreen;
	}

	final Displayable getScreenAfterAlert()
	{
		return currentClientAfterAlertScreen;
	}

	final Exception getMIDletStartingCause()
	{
		MIDletDispatcher d;
		return (d = dispatcher) != null ? d.getCause() : null;
	}

	private void handleEvent(Runnable event)
	{
		if(event == null) return;
		try
		{
			event.run();
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
	}

	private void handleEvent(Displayable screen, int event, int param1, int param2, int param3)
	{
		if(screen == null) return;
		try
		{
			switch(event)
			{
			case EVENT_WINDOW_SHOW:
				screen.eventShow();
				break;
			case EVENT_WINDOW_HIDE:
				screen.eventHide();
				break;
			case EVENT_KEY_PRESSED:
				screen.eventKeyPressed(param1, param2 & 0xffff | param3 << 16);
				break;
			case EVENT_KEY_REPEATED:
				screen.eventKeyRepeated(param1, param2 & 0xffff | param3 << 16);
				break;
			case EVENT_KEY_RELEASED:
				screen.eventKeyReleased(param1);
				break;
			case EVENT_POINTER_PRESSED:
				screen.eventPointerPressed(param1, param2, param3);
				break;
			case EVENT_POINTER_DRAGGED:
				screen.eventPointerDragged(param1, param2);
				break;
			case EVENT_POINTER_RELEASED:
				screen.eventPointerReleased(param1, param2, param3);
				break;
			}
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		if(event == EVENT_WINDOW_SHOW)
		{
			callSeriallyPaint(screen);
		}
	}

	private void handlePaint(Displayable screen)
	{
		int elements;
		Graphics context;
		if(screen == null || !isShown(screen) || (elements = screen.getRepaintElements()) == 0) return;
		context = screen.isSystem() ? graphicsForSystemScreen : graphicsForClientScreen;
		try
		{
			context.reset();
			screen.clearRepaintElements();
			screen.eventPaint(context, elements);
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		paintDisplay(screen, context);
	}

	private void handlePaint(Displayable screen, int left, int top, int width, int height)
	{
		int elements;
		Graphics context;
		if(screen == null || !isShown(screen) || (elements = screen.getRepaintElements()) == 0) return;
		context = screen.isSystem() ? graphicsForSystemScreen : graphicsForClientScreen;
		try
		{
			context.reset();
			screen.clearRepaintElements();
			screen.eventPaint(context, elements, left, top, width, height);
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
		paintDisplay(screen, context);
	}

	private void paintDisplay(Displayable screen, Graphics context)
	{
		long delay;
		Object monitor;
		if(!isShown(screen)) return;
		synchronized(monitor = guiElementsMetrics)
		{
			long period = (long) MINIMUM_PERIOD;
			long currTimestamp = System.currentTimeMillis();
			long mustTimestamp = lastRepaintTimestamp + period;
			lastRepaintTimestamp = currTimestamp + ((delay = mustTimestamp - currTimestamp) > period ?
					delay = period : delay > 0L ? delay : 0L);
		}
		if(delay > 0L)
		{
			try
			{
				Thread.sleep(delay);
			}
			catch(InterruptedException e)
			{
				e.printRealStackTrace();
			}
		}
		if(!isShown(screen)) return;
		synchronized(monitor)
		{
			int srcScanlength;
			int dstScanlength;
			GraphicBuffer src;
			GraphicBuffer dst;
			srcScanlength = (src = context.getBuffer()).getScanlength();
			dstScanlength = (dst = screenCanvas.getBuffer()).getScanlength();
			if(srcScanlength == dstScanlength)
			{
				Array.copy(src.getPixels(), src.getOffset(), dst.getPixels(), dst.getOffset(),
						dst.getWidth() + (dst.getHeight() - 1) * dstScanlength);
			} else
			{
				int width = dst.getWidth();
				int height = dst.getHeight();
				int srcOffset = src.getOffset() + srcScanlength * src.getHeight();
				int dstOffset = dst.getOffset() + dstScanlength * height;
				int[] srcPixels = src.getPixels();
				int[] dstPixels = dst.getPixels();
				for(int i = height; i-- > 0; )
				{
					Array.copy(srcPixels, srcOffset -= srcScanlength, dstPixels, dstOffset -= dstScanlength, width);
				}
			}
			RasterCanvas.getScreenCanvas().updateScreen();
		}
	}

	private int getGUIElementMetric(int mode, int guiElement, int metric)
	{
		if(guiElement < 0 || guiElement >= 4)
		{
			throw new IllegalArgumentException("Display.getGUIElementMetrics: " +
					"недопустимое значение параметра guiElement.");
		}
		return guiElementsMetrics[metric * 12 + mode * 4 + guiElement];
	}
}
