/*
	Реализация спецификаций 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 java.lang.ref.*;
import malik.emulator.media.graphics.*;

public abstract class Item extends Object
{
	private static final class Helper extends WeakReference
			implements ItemCommandListener, CommandEnumerationWithCheck, CommandEnumeration
	{
		Helper(Item thisItem)
		{
			super(thisItem);
		}

		public void commandAction(Command command, Item item)
		{
			Object thisItem;
			if(!((thisItem = get()) instanceof ItemCommandListener))
			{
				return;
			}
			((ItemCommandListener) thisItem).commandAction(command, item);
		}

		public boolean hasCommand(Command command)
		{
			int i;
			Command[] c;
			Item thisItem;
			if((thisItem = (Item) get()) == null)
			{
				return false;
			}
			if(command == thisItem.defaultCommand)
			{
				return true;
			}
			for(c = thisItem.commandsInItem, i = thisItem.commandsCount; i-- > 0; )
			{
				if(c[i] == command)
				{
					return true;
				}
			}
			return false;
		}

		public int getCommandsCount()
		{
			Item thisItem;
			return (thisItem = (Item) get()) == null ? 0 : thisItem.commandsCount;
		}

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

		public Command getDefaultCommand()
		{
			Item thisItem;
			return (thisItem = (Item) get()) == null ? null : thisItem.defaultCommand;
		}

		public Object getMonitor()
		{
			Item thisItem;
			Container owner;
			return (thisItem = (Item) get()) == null ? this :
					((owner = thisItem.getOwner()) == null ? thisItem.getMonitor() :
					owner.getCommandsMonitor());
		}
	}

	private static final class ItemUpdateEventStream extends Object
			implements Runnable
	{
		private int count;
		private Item[] items;

		ItemUpdateEventStream()
		{
			this.items = new Item[8];
		}

		public synchronized void run()
		{
			int i;
			int count;
			Item[] items;
			Item item;
			do
			{
				waitEvent();
				count = this.count;
				items = this.items;
				for(i = count; i-- > 0; )
				{
					(item = items[i]).onUpdate();
					if(i == 0)
					{
						item.notifyPaint();
					}
				}
				item = null;
			} while(true);
		}

		public synchronized void add(Item item)
		{
			int i;
			int count = this.count;
			Item[] items = this.items;
			for(i = count; i-- > 0; )
			{
				if(items[i] == item)
				{
					return;
				}
			}
			if(count == items.length)
			{
				Array.copy(items, 0, items = this.items = new Item[count << 1], 0, count);
			}
			items[count++] = item;
			this.count = count;
			notify();
		}

		public synchronized void remove(Item item)
		{
			int i;
			int index;
			int count = this.count;
			Item[] items = this.items;
			label0:
			{
				for(i = count; i-- > 0; )
				{
					if(items[i] == item)
					{
						index = i;
						break label0;
					}
				}
				return;
			}
			if((i = count - index - 1) > 0)
			{
				Array.copy(items, index + 1, items, index, i);
			}
			items[--count] = null;
			this.count = count;
			notify();
		}

		private void waitEvent()
		{
			do
			{
				try
				{
					wait(count > 0 ? 100L : 0L);
					break;
				}
				catch(InterruptedException e)
				{
					e.printRealStackTrace();
				}
			} while(true);
		}
	}

	public static final int PLAIN = 0;
	public static final int HYPERLINK = 1;
	public static final int BUTTON = 2;
	public static final int LAYOUT_DEFAULT = 0x0000;
	public static final int LAYOUT_LEFT = 0x0001;
	public static final int LAYOUT_RIGHT = 0x0002;
	public static final int LAYOUT_CENTER = 0x0003;
	public static final int LAYOUT_TOP = 0x0010;
	public static final int LAYOUT_BOTTOM = 0x0020;
	public static final int LAYOUT_VCENTER = 0x0030;
	public static final int LAYOUT_NEWLINE_BEFORE = 0x0100;
	public static final int LAYOUT_NEWLINE_AFTER = 0x0200;
	public static final int LAYOUT_SHRINK = 0x0400;
	public static final int LAYOUT_EXPAND = 0x0800;
	public static final int LAYOUT_VSHRINK = 0x1000;
	public static final int LAYOUT_VEXPAND = 0x2000;
	public static final int LAYOUT_2 = 0x4000;
	static final int LEFT = 0;
	static final int TOP = 1;
	static final int WIDTH = 2;
	static final int HEIGHT = 3;
	private static final int LAYOUT_MASK = ~0x7f33;
	private static final ItemUpdateEventStream STREAM;

	static
	{
		ItemUpdateEventStream stream;
		STREAM = stream = new ItemUpdateEventStream();
		(new Thread(stream, "Поток, периодически вызывающий метод Item.onUpdate().")).start();
	}


	private boolean needRecompute;
	private boolean focusedClient;
	private boolean visible;
	private int layout;
	private int left;
	private int top;
	private int width;
	private int height;
	private int minWidth;
	private int minHeight;
	private int lockedWidth;
	private int lockedHeight;
	private int contentLeft;
	private int contentTop;
	private int contentWidth;
	private int contentHeight;
	private int commandsCount;
	private Command[] commandsInItem;
	private Command defaultCommand;
	private ItemCommandListener listener;
	private String label;
	private Reference owningReference;
	private TextOutputMultilined lines;
	private Helper helper;
	private Object lock;

	Item(int layout, int lockedWidth, int lockedHeight, String label,
			Command[] commands, Command defaultCommand, ItemCommandListener listener)
	{
		int i;
		int j;
		int len;
		Command[] c;
		Command cmd;
		if((layout & LAYOUT_MASK) != 0)
		{
			throw new IllegalArgumentException("Item: " +
					"недопустимое значение параметра layout.");
		}
		if(lockedWidth < -1 || lockedHeight < -1)
		{
			throw new IllegalArgumentException("Item: " +
					"предпочитаемые размеры не могут быть меньше -1.");
		}
		if((len = commands == (c = null) ? 0 : commands.length) > 0)
		{
			Array.copy(commands, 0, c = new Command[len], 0, len);
			for(i = defaultCommand != null ? -1 : 0; i < len; i++)
			{
				if((cmd = i < 0 ? defaultCommand : c[i]) == null && i >= 0)
				{
					throw new NullPointerException("Item: " +
							"один из элементов параметра commands равен нулевой ссылке.");
				}
				for(j = i + 1; j < len; j++)
				{
					if(c[j] == cmd)
					{
						throw new IllegalArgumentException("Item: " +
								"параметр commands содержит равные элементы или элементы, " +
								"равные параметру defaultCommand.");
					}
				}
			}
		}
		this.needRecompute = true;
		this.layout = layout;
		this.lockedWidth = lockedWidth;
		this.lockedHeight = lockedHeight;
		this.commandsCount = len;
		this.commandsInItem = c;
		this.defaultCommand = defaultCommand;
		this.listener = listener;
		this.label = label;
		this.lines = new TextOutputMultilined(0);
		this.helper = new Helper(this);
		this.lock = new Object();
		updateLines(label);
	}

	public void notifyStateChanged()
	{
		Container owner;
		if(!((owner = getOwner()) instanceof Form))
		{
			throw new IllegalStateException("Item.notifyStateChanged: " +
					"невозможно оповестить об изменении содержимого, " +
					"если элемент находится не на форме.");
		}
		((Form) owner).notifyItemStateChanged(this);
	}

	public void addCommand(Command command)
	{
		boolean commandsChanged;
		int len;
		Command[] c;
		Container owner;
		if((owner = getOwner()) instanceof Alert)
		{
			throw new IllegalStateException("Item.addCommand: " +
					"невозможно добавить команду, " +
					"если элемент находится на диалоговом экране.");
		}
		if(command == null)
		{
			throw new NullPointerException("Item.addCommand: " +
					"параметр command равен нулевой ссылке.");
		}
		synchronized(getCommands().getMonitor())
		{
			commandsChanged = false;
			if(command != defaultCommand && (c = Displayable.addCommand(commandsInItem,
					len = commandsCount, command)) != null)
			{
				commandsCount = len + 1;
				commandsInItem = c;
				commandsChanged = true;
			}
		}
		if((!commandsChanged) || owner == null || (!owner.isFocused(this)))
		{
			return;
		}
		owner.updateCommands();
		owner.callSeriallyPaintScreen(Displayable.CLIENT);
	}

	public void removeCommand(Command command)
	{
		boolean commandsChanged;
		int len;
		Command[] c;
		Container owner;
		synchronized(getCommands().getMonitor())
		{
			label0:
			{
				commandsChanged = false;
				if(command != null && command == defaultCommand)
				{
					defaultCommand = null;
					commandsChanged = true;
					break label0;
				}
				if((c = Displayable.removeCommand(commandsInItem,
						len = commandsCount, command)) != null)
				{
					commandsCount = len - 1;
					commandsInItem = c;
					commandsChanged = true;
				}
			}
		}
		if((!commandsChanged) || (owner = getOwner()) == null || (!owner.isFocused(this)))
		{
			return;
		}
		owner.updateCommands();
		owner.callSeriallyPaintScreen(Displayable.CLIENT);
	}

	public void setDefaultCommand(Command command)
	{
		boolean commandsChanged;
		int len;
		int count;
		Command[] c;
		Command[] inItem;
		Command currentDefault;
		Container owner;
		if((owner = getOwner()) instanceof Alert)
		{
			throw new IllegalStateException("Item.setDefaultCommand: " +
					"невозможно изменить основную команду, " +
					"если элемент находится на диалоговом экране.");
		}
		synchronized(getCommands().getMonitor())
		{
			if(commandsChanged = (currentDefault = defaultCommand) != command)
			{
				count = len = commandsCount;
				inItem = commandsInItem;
				if(command != null && (c = Displayable.removeCommand(inItem,
						len, command)) != null)
				{
					count = --len;
					inItem = c;
				}
				if(currentDefault != null && (c = Displayable.addCommand(inItem,
						len, currentDefault)) != null)
				{
					count = len + 1;
					inItem = c;
				}
				commandsCount = count;
				commandsInItem = inItem;
				defaultCommand = command;
			}
		}
		if((!commandsChanged) || owner == null || (!owner.isFocused(this)))
		{
			return;
		}
		owner.updateCommands();
		owner.callSeriallyPaintScreen(Displayable.CLIENT);
	}

	public void setItemCommandListener(ItemCommandListener listener)
	{
		this.listener = listener == this ? helper : listener;
		notifyPaint();
	}

	public void setPreferredSize(int lockedWidth, int lockedHeight)
	{
		if(getOwner() instanceof Alert)
		{
			throw new IllegalStateException("Item.setPreferredSize: " +
					"невозможно изменить предпочитаемые размеры, " +
					"если элемент находится на диалоговом экране.");
		}
		if(lockedWidth < -1 || lockedHeight < -1)
		{
			throw new IllegalArgumentException("Item.setPreferredSize: " +
					"предпочитаемые размеры не могут быть меньше -1.");
		}
		synchronized(lock)
		{
			this.lockedWidth = lockedWidth;
			this.lockedHeight = lockedHeight;
		}
		notifyUpdate();
	}

	public void setLayout(int layout)
	{
		boolean layoutChanged;
		if(getOwner() instanceof Alert)
		{
			throw new IllegalStateException("Item.setLayout: " +
					"невозможно изменить расположение элемента, " +
					"если элемент находится на диалоговом экране.");
		}
		if((layout & LAYOUT_MASK) != 0)
		{
			throw new IllegalArgumentException("Item.setLayout: " +
					"недопустимое значение параметра layout.");
		}
		synchronized(lock)
		{
			if(layoutChanged = this.layout != layout)
			{
				this.layout = layout;
			}
		}
		if(!layoutChanged)
		{
			return;
		}
		notifyUpdate();
	}

	public void setLabel(String label)
	{
		boolean labelChanged;
		String oldLabel;
		if(getOwner() instanceof Alert)
		{
			throw new IllegalStateException("Item.setLabel: " +
					"невозможно изменить метку элемента, " +
					"если элемент находится на диалоговом экране.");
		}
		synchronized(lock)
		{
			if(labelChanged = (oldLabel = this.label) == null ?
					label != null : (!oldLabel.equals(label)))
			{
				this.label = label;
				updateLines(label);
			}
		}
		if(!labelChanged)
		{
			return;
		}
		notifyUpdate();
	}

	public int getMinimumWidth()
	{
		return minWidth;
	}

	public int getMinimumHeight()
	{
		return minHeight;
	}

	public int getPreferredWidth()
	{
		return width;
	}

	public int getPreferredHeight()
	{
		return height;
	}

	public int getLayout()
	{
		return layout;
	}

	public String getLabel()
	{
		return label;
	}

	abstract void paintContent(Graphics render, int contentWidth, int contentHeight);

	abstract int getPreferredContentWidth(int contentHeight, int containerClientWidth);

	abstract int getPreferredContentHeight(int contentWidth);

	void onUpdate()
	{
	}

	void onShow()
	{
	}

	void onHide()
	{
	}

	void onSizeChanged(int contentWidth, int contentHeight)
	{
	}

	void onKeyPressed(int key, int charCode)
	{
	}

	void onKeyRepeated(int key, int charCode)
	{
	}

	void onKeyReleased(int key)
	{
	}

	void onContentPointerPressed(int x, int y, int button)
	{
	}

	void onContentPointerDragged(int x, int y)
	{
	}

	void onContentPointerReleased(int x, int y, int button)
	{
	}

	void onCommandAction(Command command)
	{
		ItemCommandListener listener;
		if((listener = this.listener) == null)
		{
			return;
		}
		listener.commandAction(command, this);
	}

	void onTraverseOut()
	{
	}

	boolean onTraverseNeedStayOn(int direction, int viewportWidth, int viewportHeight,
			int[] visibleRectangle)
	{
		return false;
	}

	boolean keyHandling(int key)
	{
		return false;
	}

	boolean isMatchForAlert()
	{
		CommandEnumeration e;
		return getOwner() == null &&
				(e = getCommands()).getCommandsCount() == 0 && e.getDefaultCommand() == null &&
				lockedWidth == -1 && lockedHeight == -1 && layout == LAYOUT_DEFAULT &&
				listener == null && label == null;
	}

	boolean isMatchForButton()
	{
		return getCommands().getDefaultCommand() != null && listener != null;
	}

	boolean isMatchForHyperlink()
	{
		return getCommands().getDefaultCommand() != null && listener != null;
	}

	int getMinimumContentWidth(int containerClientWidth)
	{
		return getPreferredContentWidth(-1, containerClientWidth);
	}

	int getMinimumContentHeight()
	{
		return getPreferredContentHeight(-1);
	}

	CommandEnumerationWithCheck getCommands()
	{
		return helper;
	}

	final void onPointerPressed(int x, int y, int button)
	{
		int l;
		int t;
		if(x >= (l = contentLeft) && y >= (t = contentTop) &&
				x < l + contentWidth && y < t + contentHeight)
		{
			focusedClient = true;
			onContentPointerPressed(x - l, y - t, button);
		}
	}

	final void onPointerDragged(int x, int y)
	{
		if(focusedClient)
		{
			onContentPointerDragged(x - contentLeft, y - contentTop);
		}
	}

	final void onPointerReleased(int x, int y, int button)
	{
		if(focusedClient)
		{
			focusedClient = false;
			onContentPointerReleased(x - contentLeft, y - contentTop, button);
		}
	}

	final void onPaint(GraphicsClipRestricted render)
	{
		int w;
		int h;
		int t;
		int s;
		int ct;
		int cb;
		int i;
		int len;
		char[] labelChars;
		TextOutputMultilined labelLines;
		Font labelFont;
		String labelText;
		/* рисуем метку */
		labelFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
		render.setFont(labelFont);
		render.setColor(RasterCanvas.getSystemColor(0x28));
		synchronized(lock)
		{
			if((labelText = label) != null && labelText.length() > 0)
			{
				s = labelFont.getHeight();
				cb = (ct = render.getClipY()) + render.getClipHeight();
				len = (labelLines = lines).getLinesCount();
				labelChars = labelLines.getChars();
				for(t = 0, i = 0; i < len; t += s, i++)
				{
					if(t < cb && t + s > ct)
					{
						render.drawChars(labelChars,
								labelLines.getLineStart(i), labelLines.getLineLength(i),
								0, t, 0);
					}
				}
			}
		}
		/* рисуем содержимое */
		if((w = contentWidth) <= 0 || (h = contentHeight) <= 0)
		{
			return;
		}
		render.setColor(0x000000);
		render.setFont(Font.getDefaultFont());
		render.translate(contentLeft, contentTop);
		render.restrictClipRect(0, 0, w, h);
		if(render.getClipWidth() <= 0 || render.getClipHeight() <= 0)
		{
			return;
		}
		paintContent(render, w, h);
	}

	final void notifyCommandAction(Command command)
	{
		Displayable screen;
		if(command == null || (screen = getOwner()) == null)
		{
			return;
		}
		screen.callSeriallyCommandAction(command);
	}

	final void notifyPaint()
	{
		Displayable owner;
		if((owner = getOwner()) == null)
		{
			return;
		}
		owner.callSeriallyPaintScreen(Displayable.CLIENT);
	}

	final void notifyUpdate()
	{
		Container owner;
		if(needRecompute || (!(needRecompute = true)) || (owner = getOwner()) == null)
		{
			return;
		}
		owner.notifyPlaceItems();
	}

	final void computeSizes(int containerClientWidth)
	{
		boolean needInvokeSizeChanged;
		int i;
		int len;
		int tmp;
		int pch1;
		int pch2;
		int minContentWidth;
		int minContentHeight;
		int currContentWidth;
		int currContentHeight;
		int lockedWidth;
		int lockedHeight;
		int labelWidth;
		int labelHeight;
		char[] labelChars;
		TextOutputMultilined labelLines;
		Font labelFont;
		String labelText;
		/* проверка необходимости пересчёта размеров */
		if(!needRecompute)
		{
			return;
		}
		needRecompute = false;
		/* расчёт размера метки */
		labelWidth = 0;
		labelHeight = 0;
		synchronized(lock)
		{
			labelFont = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_SMALL);
			(labelLines = lines).split(labelFont, containerClientWidth);
			if((labelText = label) != null && labelText.length() > 0)
			{
				labelHeight = labelFont.getHeight() * (len = labelLines.getLinesCount());
				labelChars = labelLines.getChars();
				for(i = 0; i < len; i++)
				{
					if(labelWidth < (tmp = labelFont.charsWidth(labelChars,
							labelLines.getLineStart(i), labelLines.getLineLength(i))))
					{
						labelWidth = tmp;
					}
				}
			}
			lockedWidth = this.lockedWidth;
			lockedHeight = this.lockedHeight;
		}
		try
		{
			/* расчёт размеров содержимого и самого элемента */
			minContentWidth = Math.max(getMinimumContentWidth(containerClientWidth), 0);
			minContentHeight = Math.max(getMinimumContentHeight(), 0);
			if(lockedWidth < 0)
			{
				if(lockedHeight < 0)
				{
					if(containerClientWidth > minContentWidth)
					{
						pch1 = Math.max(getPreferredContentHeight(containerClientWidth), 0);
						pch2 = Math.max(getPreferredContentHeight(minContentWidth), 0);
						if(pch1 < pch2)
						{
							currContentWidth = containerClientWidth;
							currContentHeight = Math.max(pch1, minContentHeight);
						} else
						{
							currContentWidth = minContentWidth;
							currContentHeight = Math.max(pch2, minContentHeight);
						}
					} else
					{
						currContentWidth = minContentWidth;
						currContentHeight = Math.max(getPreferredContentHeight(currContentWidth),
								minContentHeight);
					}
				} else
				{
					currContentHeight = Math.max(minContentHeight, lockedHeight - labelHeight);
					currContentWidth = Math.max(getPreferredContentWidth(currContentHeight,
							containerClientWidth), minContentWidth);
				}
			} else
			{
				currContentWidth = Math.max(minContentWidth, Math.max(lockedWidth, labelWidth));
				currContentHeight = Math.max(getPreferredContentHeight(currContentWidth),
						lockedHeight < 0 ? minContentHeight :
						Math.max(minContentHeight, lockedHeight - labelHeight));
			}
			currContentWidth = Math.max(labelWidth, currContentWidth);
			minContentWidth = Math.max(labelWidth, minContentWidth);
			needInvokeSizeChanged =
					contentWidth != currContentWidth ||
					contentHeight != currContentHeight;
			width = currContentWidth;
			height = labelHeight + currContentHeight;
			minWidth = minContentWidth;
			minHeight = labelHeight + minContentHeight;
			contentLeft = 0;
			contentTop = labelHeight;
			contentWidth = currContentWidth;
			contentHeight = currContentHeight;
			/* вызываем onSizeChanged если размеры изменились */
			if(needInvokeSizeChanged)
			{
				onSizeChanged(currContentWidth, currContentHeight);
			}
		}
		catch(RuntimeException e)
		{
			e.printRealStackTrace();
		}
	}

	final void startUpdating()
	{
		STREAM.add(this);
	}

	final void stopUpdating()
	{
		STREAM.remove(this);
	}

	final void setVisible(boolean visible)
	{
		this.visible = visible;
	}

	final void setPosition(int left, int top)
	{
		this.left = left;
		this.top = top;
	}

	final void setOwner(Container owner)
	{
		this.owningReference = owner == null ? null : owner.getOwningReference();
	}

	final boolean isVisible()
	{
		return visible;
	}

	final int getLeft()
	{
		return left;
	}

	final int getTop()
	{
		return top;
	}

	final int getWidth()
	{
		return width;
	}

	final int getHeight()
	{
		return height;
	}

	final int getRealLayout()
	{
		return layout;
	}

	final Object getMonitor()
	{
		return lock;
	}

	final Container getOwner()
	{
		Reference ref;
		return (ref = owningReference) == null ? null : (Container) ref.get();
	}

	private void updateLines(String label)
	{
		int len;
		TextOutputMultilined labelLines;
		(labelLines = lines).setText(label);
		if(label == null || (len = label.length()) <= 0 || label.charAt(len - 1) == ':')
		{
			return;
		}
		labelLines.append(":");
	}
}
