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

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

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

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

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


package javax.microedition.lcdui;

import malik.emulator.media.graphics.*;

public abstract class Displayable extends Object
{
	private static class TickerScrolling extends Object
			implements Runnable
	{
		private static class Event extends Object
				implements Runnable
		{
			private boolean queued;
			private Displayable screen;

			public Event()
			{
			}

			public void run()
			{
				Displayable screen;
				Ticker ticker = (screen = this.screen) != null ? screen.ticker : null;
				if(ticker != null && screen.isReallyShown() && screen.getMode() == MODE_WITH_TICKER)
				{
					ticker.scroll();
					screen.callSeriallyPaintScreen(TICKER);
				}
				this.queued = false;
				this.screen = null;
			}

			public void set(Displayable screen)
			{
				if(queued) return;
				this.queued = true;
				this.screen = screen;
				screen.callSerially(this);
			}
		}


		private boolean terminated;

		public TickerScrolling()
		{
		}

		public void run()
		{
			Event tickerScrollingEvent = new Event();
			Display display = MIDletProxy.getInstance().getEmulatorScreen();
			for(Displayable screen; ; )
			{
				waitEvent();
				if(terminated) break;
				if((screen = display.getShownScreen()) == null) continue;
				while(display.isShown(screen) && screen.ticker != null)
				{
					tickerScrollingEvent.set(screen);
					try
					{
						Thread.sleep((long) Display.getMinimumPeriod());
					}
					catch(InterruptedException e)
					{
						e.printRealStackTrace();
					}
				}
			}
		}

		public synchronized void update()
		{
			notify();
		}

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

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

	static final int ALL = -1;
	static final int TITLE = 0x01;
	static final int TICKER = 0x02;
	static final int CLIENT = 0x04;
	static final int COMMANDS = 0x08;

	static final int GUI_ELEMENT_TITLE = 0;
	static final int GUI_ELEMENT_TICKER = 1;
	static final int GUI_ELEMENT_CLIENT = 2;
	static final int GUI_ELEMENT_COMMANDS = 3;

	static final int MODE_WITHOUT_TICKER = 0;
	static final int MODE_WITH_TICKER = 1;
	static final int MODE_FULL_SCREEN = 2;

	private static final int MENU_SPIN_WIDTH = 16;
	private static final int MENU_SPIN_HEIGHT = 16;
	private static final int MENU_MAX_ELEMENTS = 7;
	private static final int MENU_OUTER_MARGIN = 4;
	private static final int MENU_INNER_MARGIN = 6;
	private static final int[] DEVICE_KEYS_FOR_COMMANDS;
	private static final int[] COMMAND_TYPES_FOR_SOFT2;
	private static final Command[] MENU_PANEL_COMMANDS;
	private static final Command MENU;
	private static final Command BACK;
	private static final Command SELECT;
	private static final TickerScrolling TICKER_SCROLLING;

	static
	{
		Command menu = new Command("Меню", Command.SCREEN, 0);
		Command back = new Command("Назад", Command.BACK, 0);
		Command select = new Command("Выбрать", Command.OK, 0);
		DEVICE_KEYS_FOR_COMMANDS = new int[] {
				MIDletProxy.DEVICE_KEY_SOFT1,
				MIDletProxy.DEVICE_KEY_SELECT,
				MIDletProxy.DEVICE_KEY_SOFT2
		};
		COMMAND_TYPES_FOR_SOFT2 = new int[] {
				Command.BACK, Command.CANCEL,
				Command.STOP, Command.EXIT
		};
		MENU_PANEL_COMMANDS = new Command[] {
				null, select, back
		};
		MENU = menu;
		BACK = back;
		SELECT = select;
		TICKER_SCROLLING = new TickerScrolling();
	}

	static void tickerStart()
	{
		(new Thread(TICKER_SCROLLING, "Прокрутка бегущих строк")).start();
	}

	static void tickerStop()
	{
		TICKER_SCROLLING.terminate();
	}

	static void tickerUpdate()
	{
		TICKER_SCROLLING.update();
	}

	static Command[] addCommand(Command[] list, int count, Command command)
	{
		int c;
		if(command == null)
		{
			return null;
		}
		if(list == null || (c = list.length) == 0)
		{
			count = 0;
			list = new Command[3];
		} else
		{
			for(int i = count; i-- > 0; )
			{
				if(list[i] == command)
				{
					return null;
				}
			}
			if(count == c)
			{
				Array.copy(list, 0, list = new Command[(c << 1) + 1], 0, c);
			}
		}
		list[count] = command;
		return list;
	}

	static Command[] removeCommand(Command[] list, int count, Command command)
	{
		int i;
		int c;
		if(command == null)
		{
			return null;
		}
		for(i = list == null || list.length <= 0 ? 0 : count; i-- > 0; )
		{
			if(list[i] != command)
			{
				continue;
			}
			if((c = --count - i) > 0)
			{
				Array.copy(list, i + 1, list, i, c);
			}
			list[count] = null;
			return list;
		}
		return null;
	}

	static Command[] concatCommands(Command[] list1, Command[] list2)
	{
		int len1 = list1 == null ? 0 : list1.length;
		int len2 = list2 == null ? 0 : list2.length;
		Command[] result;
		if(len1 <= 0)
		{
			return list2;
		}
		if(len2 <= 0)
		{
			return list1;
		}
		result = new Command[len1 + len2];
		Array.copy(list1, 0, result, 0, len1);
		Array.copy(list2, 0, result, len1, len2);
		return result;
	}

	static String truncate(String text, Font font, int width)
	{
		int i;
		int len;
		int pos;
		char[] c;
		if(text == null || (len = text.length()) <= 0)
		{
			return "";
		}
		if(width < 0)
		{
			return text;
		}
		if(font == null)
		{
			font = Font.getDefaultFont();
		}
		for(pos = len, i = 0; i < len; i++)
		{
			if(text.charAt(i) < 32)
			{
				pos = i;
				break;
			}
		}
		if(pos < len)
		{
			text.getChars(0, pos, c = new char[len = pos + 1], 0);
			c[pos] = '…';
		}
		else if(font.stringWidth(text) > width)
		{
			(c = text.toCharArray())[len - 1] = '…';
		}
		else
		{
			return text;
		}
		while(len > 1 && font.charsWidth(c, 0, len) > width)
		{
			c[--len - 1] = '…';
		}
		return (new String(c, 0, len)).intern();
	}

	private static Command findCommand(CommandEnumeration enumeration, int type, Command ignore)
	{
		Command result = null;
		for(int priority = Integer.MAX_VALUE, i = enumeration.getCommandsCount(); i-- > 0; )
		{
			int p;
			Command command;
			if((command = enumeration.getCommand(i)) != null && command != ignore &&
					command.getRealType() == type && (p = command.getRealPriority()) <= priority)
			{
				result = command;
				priority = p;
			}
		}
		return result;
	}


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

		public void run()
		{
			Displayable screen;
			(screen = Displayable.this).staySizeChanged = false;
			if(!screen.isReallyShown())
			{
				screen.needSizeChanged = true;
				return;
			}
			screen.needSizeChanged = false;
			screen.onSizeChanged(screen.getClientWidth(), screen.getClientHeight());
		}
	}

	private class CommandActionEvent extends Object
			implements Runnable
	{
		private Command command;

		public CommandActionEvent(Command command)
		{
			this.command = command;
		}

		public void run()
		{
			(Displayable.this).onCommandAction(command);
		}
	}

	private final class CommandEnumerator extends Object
			implements CommandEnumeration
	{
		public CommandEnumerator()
		{
		}

		public int getCommandsCount()
		{
			return (Displayable.this).commandsCount;
		}

		public Command getCommand(int index)
		{
			Displayable screen;
			if(index < 0 || index >= (screen = Displayable.this).commandsCount)
			{
				throw new IndexOutOfBoundsException("Displayable.getCommands: " +
						"индекс выходит из диапазона.");
			}
			return screen.commandsInScreen[index];
		}

		public Command getDefaultCommand()
		{
			return (Displayable.this).defaultCommand;
		}

		public Object getMonitor()
		{
			return (Displayable.this).getCommandsMonitor();
		}
	}

	private boolean system;
	private boolean needSizeChanged;
	private boolean staySizeChanged;
	private boolean menuOpened;
	private int menuFirstIndex;
	private int menuSelectedIndex;
	private int mode;
	private int repaintElements;
	private int commandFocused;
	private int commandPressed;
	private int commandsCount;
	private Command[] commandsInScreen;
	private Command[] commandsOnPanel;
	private Command[] commandsOnMenu;
	private Command defaultCommand;
	private CommandListener listener;
	private CommandEnumeration commandsEnumeration;
	private Ticker ticker;
	private String title;
	private Object lock;

	Displayable(boolean system, boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command defaultCommand, CommandListener listener)
	{
		int len;
		Command[] c;
		if((len = commands == (c = null) ? 0 : commands.length) > 0)
		{
			Array.copy(commands, 0, c = new Command[len], 0, len);
			for(int i = defaultCommand != null ? -1 : 0; i < len; i++)
			{
				Command cmd;
				if((cmd = i < 0 ? defaultCommand : c[i]) == null && i >= 0)
				{
					throw new NullPointerException("Displayable: " +
							"один из элементов аргумента commands равен нулевой ссылке.");
				}
				for(int j = i + 1; j < len; j++)
				{
					if(c[j] == cmd)
					{
						throw new IllegalArgumentException("Displayable: " +
								"аргумент commands содержит равные элементы или элементы, " +
								"равные аргументу defaultCommand.");
					}
				}
			}
		}
		this.system = system;
		this.needSizeChanged = true;
		this.mode = fullScreenMode && listener == null ? MODE_FULL_SCREEN :
				ticker != null ? MODE_WITH_TICKER : MODE_WITHOUT_TICKER;
		this.commandFocused = -1;
		this.commandPressed = -1;
		this.commandsCount = len;
		this.commandsInScreen = c;
		this.commandsOnPanel = new Command[3];
		this.defaultCommand = defaultCommand;
		this.listener = listener;
		this.commandsEnumeration = this.new CommandEnumerator();
		this.ticker = ticker;
		this.title = title;
		this.lock = new Object();
		placeCommandsNeedRepaint();
	}

	public void addCommand(Command command)
	{
		boolean changed;
		if(command == null)
		{
			throw new NullPointerException("Displayable.addCommand: " +
					"аргумент command равен нулевой ссылке.");
		}
		changed = false;
		synchronized(lock)
		{
			int len;
			Command[] c;
			if(command != defaultCommand && (c = addCommand(commandsInScreen,
					len = commandsCount, command)) != null)
			{
				commandsCount = len + 1;
				commandsInScreen = c;
				changed = true;
			}
		}
		if(changed)
		{
			updateCommands();
		}
	}

	public void removeCommand(Command command)
	{
		boolean changed;
		if(command == null)
		{
			return;
		}
		changed = false;
		synchronized(lock)
		{
			int len;
			Command[] c;
			if(command == defaultCommand)
			{
				defaultCommand = null;
				changed = true;
			}
			else if((c = removeCommand(commandsInScreen,
					len = commandsCount, command)) != null)
			{
				commandsCount = len - 1;
				commandsInScreen = c;
				changed = true;
			}
		}
		if(changed)
		{
			updateCommands();
		}
	}

	public void setCommandListener(CommandListener listener)
	{
		boolean changedmode;
		this.listener = listener;
		if(listener == null) return;
		synchronized(lock)
		{
			changedmode = mode != (mode = ticker != null ? MODE_WITH_TICKER : MODE_WITHOUT_TICKER);
		}
		if(changedmode)
		{
			tickerUpdate();
			callSeriallySizeChanged();
			callSeriallyMenuClose();
		}
	}

	public void setTitle(String title)
	{
		boolean changed = false;
		synchronized(lock)
		{
			String current = this.title;
			if(title == null || title.length() <= 0 ? current != null && current.length() > 0 :
					current == null || current.length() <= 0 || !current.equals(title))
			{
				this.title = title;
				changed = true;
			}
		}
		if(changed)
		{
			callSeriallyPaintScreen(TITLE);
		}
	}

	public void setTicker(Ticker ticker)
	{
		boolean changed = false;
		boolean changedmode = false;
		synchronized(lock)
		{
			if(ticker != this.ticker)
			{
				int currmode;
				this.ticker = ticker;
				changedmode = (currmode = mode) != MODE_FULL_SCREEN && currmode != (mode =
						ticker != null ? MODE_WITH_TICKER : MODE_WITHOUT_TICKER);
				changed = true;
			}
		}
		if(changed)
		{
			tickerUpdate();
			if(changedmode)
			{
				callSeriallySizeChanged();
			}
			callSeriallyPaintScreen(ticker != null ? changedmode ? TICKER | CLIENT : TICKER : CLIENT);
		}
	}

	public boolean isShown()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().isShown(this);
	}

	public int getWidth()
	{
		return getClientWidth();
	}

	public int getHeight()
	{
		return getClientHeight();
	}

	public String getTitle()
	{
		return title;
	}

	public Ticker getTicker()
	{
		return ticker;
	}

	public final boolean isSystem()
	{
		return system;
	}

	protected void sizeChanged(int width, int height)
	{
	}

	abstract void paint(Graphics render);

	void paintTitle(Graphics render, int width, int height, String title)
	{
		render.setColor(RasterCanvas.getSystemColor(0x09));
		render.drawElementOfGUI(6, 1, 0, 0, 0, width, height);
		render.drawString(title, 2, 2, 0);
	}

	void paintTicker(Graphics render, int width, int height, Ticker ticker)
	{
		render.setColor(RasterCanvas.getSystemColor(0x29));
		render.drawElementOfGUI(13, 0, 0, 0, 0, width, height);
		render.drawString(ticker.getDisplayedText(), ticker.getPosition(), 2, 0);
	}

	void paintClient(Graphics render, int width, int height,
			int clipLeft, int clipTop, int clipWidth, int clipHeight)
	{
		int x;
		int y;
		Display display;
		Graphics clientRender;
		x = (display = MIDletProxy.getInstance().getEmulatorScreen()).getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
		y = mode == MODE_FULL_SCREEN ?
				display.getGUIElementTop(this, GUI_ELEMENT_CLIENT) :
				display.getGUIElementTop(this, GUI_ELEMENT_TITLE) +
				display.getGUIElementHeight(this, GUI_ELEMENT_TITLE);
		render.drawElementOfGUI(6, 5, 0, x - render.getTranslateX(), y - render.getTranslateY(),
				display.getWidth() - x, display.getHeight() - y);
		(clientRender = getClientContext()).reset();
		clientRender.translate(getClientLeft(), getClientTop());
		clientRender.setClip(clipLeft, clipTop, clipWidth, clipHeight);
		paint(clientRender);
	}

	void paintCommands(Graphics render, int width, int height,
			Command[] commands, int pressedIndex, Font defaultCommandFont)
	{
		int x;
		int y;
		int q1;
		int q2;
		int q3;
		Display display;
		x = (display = MIDletProxy.getInstance().getEmulatorScreen()).getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
		y = mode == MODE_FULL_SCREEN ?
				display.getGUIElementTop(this, GUI_ELEMENT_CLIENT) :
				display.getGUIElementTop(this, GUI_ELEMENT_TITLE) +
				display.getGUIElementHeight(this, GUI_ELEMENT_TITLE);
		render.drawElementOfGUI(6, 5, 0, x - render.getTranslateX(), y - render.getTranslateY(),
				display.getWidth() - x, display.getHeight() - y);
		q2 = (q3 = width) - (q1 = width / 3);
		paintCommand(render, 0, 0, q1, height, commands[0], false, pressedIndex == 0);
		paintCommand(render, q2, 0, q3 - q2, height, commands[2], false, pressedIndex == 2);
		render.setFont(defaultCommandFont);
		paintCommand(render, q1, 0, q2 - q1, height, commands[1], true, pressedIndex == 1);
	}

	void paintCommand(Graphics render, int left, int top, int width, int height,
			Command command, boolean asDefault, boolean asPressed)
	{
		int ofs;
		if(command == null) return;
		render.drawElementOfGUI(4, ofs = asPressed ? 1 : asDefault ? 3 : 0, 0, left, top, width, height);
		render.setColor(RasterCanvas.getSystemColor(0x24 + ofs));
		render.drawString(command.getTruncatedLabel(render.getFont(), width - 4),
				left + (width / 2) + (asPressed ? 1 : 0),
				top + (asDefault ? -1 : 1) + (asPressed ? 3 : 2),
				Graphics.HCENTER | Graphics.TOP);
	}

	void onShow()
	{
	}

	void onHide()
	{
	}

	void onSizeChanged(int width, int height)
	{
		sizeChanged(width, height);
	}

	void onKeyPressed(int key, int charCode)
	{
		int i = getCommandUnderKey(key);
		Command[] c = commandsOnPanel;
		if(i < 0 || i >= c.length || c[i] == null) return;
		commandPressed = i;
		callSeriallyPaintScreen(COMMANDS);
	}

	void onKeyRepeated(int key, int charCode)
	{
	}

	void onKeyReleased(int key)
	{
		int i;
		Command command;
		if((i = commandPressed) < 0) return;
		commandPressed = -1;
		callSeriallyPaintScreen(COMMANDS);
		if(i != getCommandUnderKey(key) || (command = commandsOnPanel[i]) == null) return;
		callSeriallyCommandAction(command);
	}

	void onPointerPressed(int x, int y, int button)
	{
		int i;
		Command[] c;
		if(button != MIDletProxy.BUTTON_MAIN) return;
		i = getCommandUnderPointer(x, y);
		c = commandsOnPanel;
		if(i < 0 || i >= c.length || c[i] == null) return;
		commandPressed = commandFocused = i;
		callSeriallyPaintScreen(COMMANDS);
	}

	void onPointerDragged(int x, int y)
	{
		int f;
		if((f = commandFocused) < 0 ||
				commandPressed == (commandPressed = getCommandUnderPointer(x, y) != f ? -1 : f)) return;
		callSeriallyPaintScreen(COMMANDS);
	}

	void onPointerReleased(int x, int y, int button)
	{
		int i;
		Command command;
		if(button != MIDletProxy.BUTTON_MAIN) return;
		i = commandPressed;
		commandPressed = commandFocused = -1;
		if(i < 0) return;
		callSeriallyPaintScreen(COMMANDS);
		if((command = commandsOnPanel[i]) == null) return;
		callSeriallyCommandAction(command);
	}

	void onPaint(Graphics render, int guiElements, int clipLeft, int clipTop, int clipWidth, int clipHeight)
	{
		int mode;
		int width;
		int height;
		Ticker ticker;
		Display display = MIDletProxy.getInstance().getEmulatorScreen();
		if((mode = this.mode) != MODE_FULL_SCREEN)
		{
			if((guiElements & TITLE) != 0)
			{
				render.translate(
						display.getGUIElementLeft(this, GUI_ELEMENT_TITLE),
						display.getGUIElementTop(this, GUI_ELEMENT_TITLE));
				width = display.getGUIElementWidth(this, GUI_ELEMENT_TITLE);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_TITLE);
				render.setFont(getGUIElementFont(GUI_ELEMENT_TITLE));
				render.setClip(0, 0, width, height);
				paintTitle(render, width, height, getDisplayedTitle(title));
				render.reset();
			}
			if((guiElements & TICKER) != 0 && mode == MODE_WITH_TICKER && (ticker = this.ticker) != null)
			{
				render.translate(
						display.getGUIElementLeft(this, GUI_ELEMENT_TICKER),
						display.getGUIElementTop(this, GUI_ELEMENT_TICKER));
				width = display.getGUIElementWidth(this, GUI_ELEMENT_TICKER);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_TICKER);
				render.setFont(getGUIElementFont(GUI_ELEMENT_TICKER));
				render.setClip(0, 0, width, height);
				paintTicker(render, width, height, ticker);
				render.reset();
			}
			if((guiElements & COMMANDS) != 0)
			{
				render.translate(
						display.getGUIElementLeft(this, GUI_ELEMENT_COMMANDS),
						display.getGUIElementTop(this, GUI_ELEMENT_COMMANDS));
				width = display.getGUIElementWidth(this, GUI_ELEMENT_COMMANDS);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_COMMANDS);
				render.setFont(getGUIElementFont(GUI_ELEMENT_COMMANDS));
				render.setClip(0, 0, width, height);
				paintCommands(render, width, height, commandsOnPanel,
						commandPressed, getDefaultCommandFont());
				render.reset();
			}
		}
		if((guiElements & CLIENT) != 0)
		{
			render.translate(
					display.getGUIElementLeft(this, GUI_ELEMENT_CLIENT),
					display.getGUIElementTop(this, GUI_ELEMENT_CLIENT));
			width = display.getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
			height = display.getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
			render.setFont(getGUIElementFont(GUI_ELEMENT_CLIENT));
			render.setClip(0, 0, width, height);
			paintClient(render, width, height, clipLeft, clipTop, clipWidth, clipHeight);
		}
	}

	void onCommandAction(Command command)
	{
		CommandListener listener;
		if(command == MENU)
		{
			menuOpened = true;
			callSeriallyPaintScreen();
			return;
		}
		if(command == BACK)
		{
			menuOpened = false;
			callSeriallyPaintScreen();
			return;
		}
		if(command == SELECT)
		{
			int i;
			Command[] c;
			menuOpened = false;
			callSeriallyPaintScreen();
			if((c = commandsOnMenu) == null || (i = menuSelectedIndex) < 0 || i >= c.length ||
					(command = c[i]) == null) return;
			callSeriallyCommandAction(command);
			return;
		}
		if((listener = this.listener) != null)
		{
			listener.commandAction(command, this);
		}
	}

	void setDefaultCommand(Command command)
	{
		boolean changed = false;
		synchronized(lock)
		{
			if(command != this.defaultCommand)
			{
				this.defaultCommand = command;
				changed = true;
			}
		}
		if(changed)
		{
			updateCommands();
		}
	}

	void setFullScreenMode(boolean fullScreenMode)
	{
		boolean changedmode;
		synchronized(lock)
		{
			changedmode = mode != (mode = fullScreenMode && listener == null ? MODE_FULL_SCREEN :
					ticker != null ? MODE_WITH_TICKER : MODE_WITHOUT_TICKER);
		}
		if(changedmode)
		{
			tickerUpdate();
			callSeriallySizeChanged();
			callSeriallyMenuClose();
		}
	}

	boolean keyHandling(int key)
	{
		return getCommandUnderKey(key) >= 0;
	}

	boolean coordinateHandling(int x, int y)
	{
		return getCommandUnderPointer(x, y) >= 0;
	}

	int getPanelCommandIndex(int x, int y, int width, int height)
	{
		int q1;
		int q2 = width - (q1 = width / 3);
		return x >= q2 && x < width ? 2 : x >= q1 && x < q2 ? 1 : x >= 0 && x < q1 ? 0 : -1;
	}

	int getClientLeft()
	{
		return 0;
	}

	int getClientTop()
	{
		return 0;
	}

	int getClientWidth()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
	}

	int getClientHeight()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
	}

	Font getGUIElementFont(int guiElement)
	{
		return MIDletProxy.getInstance().getEmulatorScreen().getGUIElementFont(guiElement);
	}

	Font getDefaultCommandFont()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().getDefaultCommandFont();
	}

	CommandEnumeration getCommands()
	{
		return commandsEnumeration;
	}

	final void eventShow()
	{
		if(needSizeChanged)
		{
			callSeriallySizeChanged();
		}
		menuOpened = false;
		repaintElements = ALL;
		commandPressed = commandFocused = -1;
		onShow();
	}

	final void eventHide()
	{
		menuOpened = false;
		onHide();
	}

	final void eventKeyPressed(int key, int charCode)
	{
		if(menuOpened)
		{
			menuKeyPressed(key);
			return;
		}
		onKeyPressed(key, charCode);
	}

	final void eventKeyRepeated(int key, int charCode)
	{
		if(menuOpened)
		{
			menuKeyRepeated(key);
			return;
		}
		onKeyRepeated(key, charCode);
	}

	final void eventKeyReleased(int key)
	{
		if(menuOpened)
		{
			menuKeyReleased(key);
			return;
		}
		onKeyReleased(key);
	}

	final void eventPointerPressed(int x, int y, int button)
	{
		if(menuOpened)
		{
			menuPointerPressed(x, y, button);
			return;
		}
		onPointerPressed(x, y, button);
	}

	final void eventPointerDragged(int x, int y)
	{
		if(menuOpened)
		{
			menuPointerDragged(x, y);
			return;
		}
		onPointerDragged(x, y);
	}

	final void eventPointerReleased(int x, int y, int button)
	{
		if(menuOpened)
		{
			menuPointerReleased(x, y, button);
			return;
		}
		onPointerReleased(x, y, button);
	}

	final void eventPaint(Graphics render, int guiElements)
	{
		eventPaint(render, guiElements, 0, 0, getClientWidth(), getClientHeight());
	}

	final void eventPaint(Graphics render, int guiElements, int clipLeft, int clipTop, int clipWidth, int clipHeight)
	{
		if(menuOpened)
		{
			menuPaint(render, guiElements, clipLeft, clipTop, clipWidth, clipHeight);
			return;
		}
		onPaint(render, guiElements, clipLeft, clipTop, clipWidth, clipHeight);
	}

	final void callSerially(Runnable event)
	{
		MIDletProxy.getInstance().getEmulatorScreen().callSeriallyEvent(this, event);
	}

	final void callSeriallySizeChanged()
	{
		if(!staySizeChanged)
		{
			staySizeChanged = true;
			callSerially(this.new SizeChangedEvent());
		}
	}

	final void callSeriallyPaintScreen()
	{
		int rge;
		repaintElements = (rge = repaintElements) | ALL;
		if(rge == 0)
		{
			MIDletProxy.getInstance().getEmulatorScreen().callSeriallyPaint(this);
		}
	}

	final void callSeriallyPaintScreen(int guiElements)
	{
		int rge;
		repaintElements = (rge = repaintElements) | guiElements;
		if(rge == 0)
		{
			MIDletProxy.getInstance().getEmulatorScreen().callSeriallyPaint(this);
		}
	}

	final void callSeriallyPaintClient(int left, int top, int width, int height)
	{
		repaintElements |= CLIENT;
		MIDletProxy.getInstance().getEmulatorScreen().callSeriallyPaint(this, left, top, width, height);
	}

	final void callSeriallyCommandAction(Command command)
	{
		callSerially(this.new CommandActionEvent(command));
	}

	final void clearRepaintElements()
	{
		repaintElements = 0;
	}

	final void updateCommands()
	{
		if(placeCommandsNeedRepaint())
		{
			callSeriallyPaintScreen(menuOpened ? CLIENT : COMMANDS);
		}
	}

	final boolean isReallyShown()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().isShown(this);
	}

	final int getMode()
	{
		return mode;
	}

	final int getRepaintElements()
	{
		return repaintElements;
	}

	final Graphics getClientContext()
	{
		return MIDletProxy.getInstance().getEmulatorScreen().getClientGraphicsFor(this);
	}

	final Object getCommandsMonitor()
	{
		return lock;
	}

	private void menuKeyPressed(int key)
	{
		int i;
		Command[] c;
		if(menuMoveCursor(key)) return;
		i = getCommandUnderKey(key);
		c = MENU_PANEL_COMMANDS;
		if(i < 0 || i >= c.length || c[i] == null) return;
		commandPressed = i;
		callSeriallyPaintScreen(COMMANDS);
	}

	private void menuKeyRepeated(int key)
	{
		menuMoveCursor(key);
	}

	private void menuKeyReleased(int key)
	{
		int i;
		Command command;
		if((i = commandPressed) < 0) return;
		commandPressed = -1;
		callSeriallyPaintScreen(COMMANDS);
		if(i != getCommandUnderKey(key) || (command = MENU_PANEL_COMMANDS[i]) == null) return;
		callSeriallyCommandAction(command);
	}

	private void menuPointerPressed(int x, int y, int button)
	{
		int i;
		Command[] c;
		if(button != MIDletProxy.BUTTON_MAIN) return;
		if((i = getCommandUnderPointer(x, y)) < 0)
		{
			int l;
			int t;
			int w;
			int h;
			int s;
			int ofs;
			int len;
			int width;
			int height;
			int length;
			Display display = MIDletProxy.getInstance().getEmulatorScreen();
			s = Math.max(display.getGUIElementFont(GUI_ELEMENT_COMMANDS).getHeight(), 1);
			x -= display.getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
			y -= display.getGUIElementTop(this, GUI_ELEMENT_CLIENT);
			width = display.getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
			height = display.getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
			synchronized(getCommands().getMonitor())
			{
				ofs = menuFirstIndex;
				len = getMenuCapacity();
				length = (c = commandsOnMenu) == null ? 0 : c.length;
				l = width - (MENU_OUTER_MARGIN + MENU_INNER_MARGIN + MENU_SPIN_WIDTH);
				t = height - (MENU_OUTER_MARGIN + MENU_INNER_MARGIN + 2 * MENU_SPIN_HEIGHT);
				w = MENU_SPIN_WIDTH;
				h = MENU_SPIN_HEIGHT;
				label0:
				{
					if(ofs > 0 && x >= l && x < l + w && y >= t && y < t + h)
					{
						menuFirstIndex = ofs - 1;
						callSeriallyPaintScreen(CLIENT);
						break label0;
					}
					t += MENU_SPIN_HEIGHT;
					if(ofs < length - len && x >= l && x < l + w && y >= t && y < t + h)
					{
						menuFirstIndex = ofs + 1;
						callSeriallyPaintScreen(CLIENT);
						break label0;
					}
					l = MENU_OUTER_MARGIN;
					t = height - (h = s * len) - (MENU_OUTER_MARGIN + MENU_INNER_MARGIN);
					w = width - (2 * MENU_OUTER_MARGIN + 3 * MENU_INNER_MARGIN + MENU_SPIN_WIDTH);
					if(x >= l && x < l + w && y >= t && y < t + h)
					{
						commandPressed = -2;
						if(menuSelectedIndex != (menuSelectedIndex = ofs + (y - t) / s))
						{
							callSeriallyPaintScreen(CLIENT);
						}
					}
				}
			}
			return;
		}
		c = MENU_PANEL_COMMANDS;
		if(i >= c.length || c[i] == null) return;
		commandPressed = commandFocused = i;
		callSeriallyPaintScreen(COMMANDS);
	}

	private void menuPointerDragged(int x, int y)
	{
		int f;
		int p;
		if((p = commandPressed) == -2)
		{
			int l;
			int t;
			int w;
			int h;
			int s;
			int ofs;
			int len;
			int width;
			int height;
			Display display = MIDletProxy.getInstance().getEmulatorScreen();
			s = Math.max(display.getGUIElementFont(GUI_ELEMENT_COMMANDS).getHeight(), 1);
			x -= display.getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
			y -= display.getGUIElementTop(this, GUI_ELEMENT_CLIENT);
			width = display.getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
			height = display.getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
			synchronized(getCommands().getMonitor())
			{
				ofs = menuFirstIndex;
				len = getMenuCapacity();
				l = MENU_OUTER_MARGIN;
				t = height - (h = s * len) - (MENU_OUTER_MARGIN + MENU_INNER_MARGIN);
				w = width - (2 * MENU_OUTER_MARGIN + 3 * MENU_INNER_MARGIN + MENU_SPIN_WIDTH);
				if(x >= l && x < l + w && y >= t && y < t + h &&
						menuSelectedIndex != (menuSelectedIndex = ofs + (y - t) / s))
				{
					callSeriallyPaintScreen(CLIENT);
				}
			}
			return;
		}
		if((f = commandFocused) < 0 ||
				p == (commandPressed = getCommandUnderPointer(x, y) != f ? -1 : f)) return;
		callSeriallyPaintScreen(COMMANDS);
	}

	private void menuPointerReleased(int x, int y, int button)
	{
		int i;
		Command command;
		if(button != MIDletProxy.BUTTON_MAIN) return;
		if((i = commandPressed) == -1)
		{
			int l;
			int t;
			int w;
			int h;
			int s;
			Display display = MIDletProxy.getInstance().getEmulatorScreen();
			s = Math.max(display.getGUIElementFont(GUI_ELEMENT_COMMANDS).getHeight(), 1);
			l = display.getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
			t = display.getGUIElementTop(this, GUI_ELEMENT_CLIENT);
			w = display.getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
			h = display.getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
			synchronized(getCommands().getMonitor())
			{
				l += MENU_OUTER_MARGIN;
				t += h - (h = s * getMenuCapacity() + 2 * MENU_INNER_MARGIN) - MENU_OUTER_MARGIN;
				w -= 2 * MENU_OUTER_MARGIN;
			}
			if((x < l || x >= l + w || y < t || y >= t + h) && (
					x < (l = display.getGUIElementLeft(this, GUI_ELEMENT_COMMANDS)) ||
					x >= l + display.getGUIElementWidth(this, GUI_ELEMENT_COMMANDS) ||
					y < (t = display.getGUIElementTop(this, GUI_ELEMENT_COMMANDS)) ||
					y >= (t + display.getGUIElementHeight(this, GUI_ELEMENT_COMMANDS))))
			{
				menuOpened = false;
				callSeriallyPaintScreen();
			}
			return;
		}
		commandPressed = commandFocused = -1;
		if(i < 0) return;
		callSeriallyPaintScreen(COMMANDS);
		if((command = MENU_PANEL_COMMANDS[i]) == null) return;
		callSeriallyCommandAction(command);
	}

	private void menuPaint(Graphics render, int guiElements, int clipLeft, int clipTop, int clipWidth, int clipHeight)
	{
		int mode;
		int top;
		int left;
		int width;
		int height;
		RasterCanvas canvas = render.getCanvas();
		Display display = MIDletProxy.getInstance().getEmulatorScreen();
		onPaint(render, guiElements & ~COMMANDS, clipLeft, clipTop, clipWidth, clipHeight);
		if((mode = this.mode) != MODE_FULL_SCREEN)
		{
			if((guiElements & TITLE) != 0)
			{
				top = display.getGUIElementTop(this, GUI_ELEMENT_TITLE);
				left = display.getGUIElementLeft(this, GUI_ELEMENT_TITLE);
				width = display.getGUIElementWidth(this, GUI_ELEMENT_TITLE);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_TITLE);
				canvas.fillRect(left, top, width, height, left, top, width, height, 0x80000000, true);
			}
			if((guiElements & TICKER) != 0 && mode == MODE_WITH_TICKER && ticker != null)
			{
				top = display.getGUIElementTop(this, GUI_ELEMENT_TICKER);
				left = display.getGUIElementLeft(this, GUI_ELEMENT_TICKER);
				width = display.getGUIElementWidth(this, GUI_ELEMENT_TICKER);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_TICKER);
				canvas.fillRect(left, top, width, height, left, top, width, height, 0x80000000, true);
			}
			if((guiElements & COMMANDS) != 0)
			{
				render.reset();
				render.translate(
						display.getGUIElementLeft(this, GUI_ELEMENT_COMMANDS),
						display.getGUIElementTop(this, GUI_ELEMENT_COMMANDS));
				width = display.getGUIElementWidth(this, GUI_ELEMENT_COMMANDS);
				height = display.getGUIElementHeight(this, GUI_ELEMENT_COMMANDS);
				render.setFont(getGUIElementFont(GUI_ELEMENT_COMMANDS));
				render.setClip(0, 0, width, height);
				paintCommands(render, width, height, MENU_PANEL_COMMANDS,
						commandPressed, getDefaultCommandFont());
			}
		}
		if((guiElements & CLIENT) != 0)
		{
			Graphics clientRender;
			top = display.getGUIElementTop(this, GUI_ELEMENT_CLIENT);
			left = display.getGUIElementLeft(this, GUI_ELEMENT_CLIENT);
			width = display.getGUIElementWidth(this, GUI_ELEMENT_CLIENT);
			height = display.getGUIElementHeight(this, GUI_ELEMENT_CLIENT);
			canvas.fillRect(left, top, width, height, left, top, width, height, 0x80000000, true);
			(clientRender = getClientContext()).reset();
			clientRender.setFont(getGUIElementFont(GUI_ELEMENT_COMMANDS));
			menuPaint(clientRender, width, height);
		}
	}

	private void menuPaint(Graphics render, int width, int height)
	{
		int l = MENU_OUTER_MARGIN;
		int t = MENU_OUTER_MARGIN;
		int w = width - 2 * MENU_OUTER_MARGIN;
		int h = height - 2 * MENU_OUTER_MARGIN;
		int b;
		int s;
		int cap;
		int sel;
		int ofs;
		int len;
		int lim;
		int length;
		Command[] c;
		Command command;
		Font font;
		synchronized(getCommands().getMonitor())
		{
			sel = menuSelectedIndex;
			ofs = menuFirstIndex;
			cap = (h - 2 * MENU_INNER_MARGIN) / (s = Math.max((font = render.getFont()).getHeight(), 1));
			length = (c = commandsOnMenu) == null ? 0 : c.length;
			len = Math.min(Math.min(cap, length), MENU_MAX_ELEMENTS);
			if(ofs > (b = length - len))
			{
				menuFirstIndex = ofs = Math.max(b, 0);
			}
		}
		h = s * len + 2 * MENU_INNER_MARGIN;
		t = height - h - MENU_OUTER_MARGIN;
		render.setColor(RasterCanvas.getSystemColor(0x04));
		render.fillRect(l, t, w, h);
		l += MENU_INNER_MARGIN;
		t += MENU_INNER_MARGIN;
		w -= 3 * MENU_INNER_MARGIN + MENU_SPIN_WIDTH;
		h -= 2 * MENU_INNER_MARGIN;
		render.setColor(RasterCanvas.getSystemColor(0x07));
		for(int ti = t + s * len, i = lim = ofs + len; i-- > ofs; )
		{
			ti -= s;
			if(i == sel || (command = c[i]) == null) continue;
			render.drawString(command.getTruncatedLabel(font, w - 4), l + 2, ti, Graphics.LEFT | Graphics.TOP);
		}
		if(sel >= ofs && sel < lim && (command = c[sel]) != null)
		{
			int ti = t + s * (sel - ofs);
			render.setColor(RasterCanvas.getSystemColor(0x0d));
			render.fillRect(l, ti, w, s);
			render.setColor(RasterCanvas.getSystemColor(0x0e));
			render.drawString(command.getTruncatedLabel(font, w - 4), l + 2, ti, Graphics.LEFT | Graphics.TOP);
		}
		if(ofs > 0 || lim < length)
		{
			l += w + MENU_INNER_MARGIN;
			w = MENU_SPIN_WIDTH;
			render.drawElementOfGUI(14, 0, ofs > 0 ? 0 : 2,
					l, t + h - 2 * MENU_SPIN_HEIGHT, MENU_SPIN_WIDTH, MENU_SPIN_HEIGHT);
			render.drawElementOfGUI(14, 1, lim < length ? 0 : 2,
					l, t + h - MENU_SPIN_HEIGHT, MENU_SPIN_WIDTH, MENU_SPIN_HEIGHT);
		}
	}

	private void callSeriallyMenuClose()
	{
		callSerially(this.new CommandActionEvent(BACK));
	}

	private boolean placeCommandsNeedRepaint()
	{
		boolean result;
		CommandEnumeration enumeration;
		synchronized((enumeration = getCommands()).getMonitor())
		{
			boolean m;
			int i;
			int len;
			int placed = 1;
			Command[] b;
			Command[] c = null;
			Command left;
			Command right;
			Command center;
			if((center = enumeration.getDefaultCommand()) == null &&
					(center = findCommand(enumeration, Command.OK, null)) != null)
			{
				placed++;
			}
			right = null;
			for(len = COMMAND_TYPES_FOR_SOFT2.length, i = 0; i < len; i++)
			{
				if((right = findCommand(enumeration, COMMAND_TYPES_FOR_SOFT2[i], center)) == null) continue;
				placed++;
				break;
			}
			left = null;
			if((len = enumeration.getCommandsCount()) == placed)
			{
				for(i = len; i-- > 0; )
				{
					Command command;
					if((command = enumeration.getCommand(i)) != null && command != center && command != right)
					{
						left = command;
						break;
					}
				}
			}
			else if(len > placed)
			{
				int j;
				for(left = MENU, c = new Command[len - placed + 1], i = j = 0; i < len; i++)
				{
					Command command;
					if((command = enumeration.getCommand(i)) != null && command != center && command != right)
					{
						c[j++] = command;
					}
				}
			}
			len = c == null ? 0 : c.length;
			if(!(m = menuOpened))
			{
				result = (b = commandsOnPanel)[0] != left || b[1] != center || b[2] != right;
			}
			else if(!(result = ((b = commandsOnMenu) == null ? 0 : b.length) != len))
			{
				for(i = len; i-- > 0; )
				{
					if(b[i] != c[i])
					{
						result = true;
						break;
					}
				}
			}
			commandsOnMenu = c;
			(b = commandsOnPanel)[0] = left;
			b[1] = center;
			b[2] = right;
			if((i = menuSelectedIndex) >= len && i != (menuSelectedIndex = Math.max(0, len - 1)) && m)
			{
				result = true;
			}
		}
		return result;
	}

	private boolean menuMoveCursor(int key)
	{
		MIDletProxy proxy;
		if(key == (proxy = MIDletProxy.getInstance()).getKeyUsedAs(MIDletProxy.DEVICE_KEY_UP))
		{
			synchronized(getCommands().getMonitor())
			{
				int length;
				Command[] c;
				if((length = (c = commandsOnMenu) == null ? 0 : c.length) > 0)
				{
					int first = menuFirstIndex;
					int index = menuSelectedIndex = (menuSelectedIndex + length - 1) % length;
					if(first > index)
					{
						menuFirstIndex = index;
					}
					else if(first <= (index -= getMenuCapacity()))
					{
						menuFirstIndex = index + 1;
					}
					callSeriallyPaintScreen(CLIENT);
				}
			}
			return true;
		}
		if(key == proxy.getKeyUsedAs(MIDletProxy.DEVICE_KEY_DOWN))
		{
			synchronized(getCommands().getMonitor())
			{
				int length;
				Command[] c;
				if((length = (c = commandsOnMenu) == null ? 0 : c.length) > 0)
				{
					int first = menuFirstIndex;
					int index = menuSelectedIndex = (menuSelectedIndex + 1) % length;
					if(first > index)
					{
						menuFirstIndex = index;
					}
					else if(first <= (index -= getMenuCapacity()))
					{
						menuFirstIndex = index + 1;
					}
					callSeriallyPaintScreen(CLIENT);
				}
			}
			return true;
		}
		return false;
	}

	private int getMenuCapacity()
	{
		int h;
		int s;
		int cap;
		int length;
		Command[] c;
		Display display;
		h = (display = MIDletProxy.getInstance().getEmulatorScreen()).getGUIElementHeight(this, GUI_ELEMENT_CLIENT) -
				2 * MENU_OUTER_MARGIN;
		s = Math.max(display.getGUIElementFont(GUI_ELEMENT_COMMANDS).getHeight(), 1);
		cap = (h - 2 * MENU_INNER_MARGIN) / s;
		length = (c = commandsOnMenu) == null ? 0 : c.length;
		return Math.min(Math.min(cap, length), MENU_MAX_ELEMENTS);
	}

	private int getCommandUnderKey(int key)
	{
		int i;
		MIDletProxy proxy;
		if(mode == MODE_FULL_SCREEN) return -1;
		for(proxy = MIDletProxy.getInstance(), i = DEVICE_KEYS_FOR_COMMANDS.length; i-- > 0; )
		{
			if(key == proxy.getKeyUsedAs(DEVICE_KEYS_FOR_COMMANDS[i])) return i;
		}
		return -1;
	}

	private int getCommandUnderPointer(int x, int y)
	{
		int l;
		int t;
		int w;
		int h;
		Display display = MIDletProxy.getInstance().getEmulatorScreen();
		return mode == MODE_FULL_SCREEN ||
				x < (l = display.getGUIElementLeft(this, GUI_ELEMENT_COMMANDS)) ||
				x >= (w = display.getGUIElementWidth(this, GUI_ELEMENT_COMMANDS)) + l ||
				y < (t = display.getGUIElementTop(this, GUI_ELEMENT_COMMANDS)) ||
				y >= (h = display.getGUIElementHeight(this, GUI_ELEMENT_COMMANDS)) + t ? -1 :
				getPanelCommandIndex(x - l, y - t, w, h);
	}

	private String getDisplayedTitle(String title)
	{
		Display display = MIDletProxy.getInstance().getEmulatorScreen();
		return truncate(title, getGUIElementFont(GUI_ELEMENT_TITLE),
				display.getGUIElementWidth(this, GUI_ELEMENT_TITLE) - 4);
	}
}
