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

public class TextBox extends ScrollingScreen
		implements Input
{
	private static final class Destination extends WeakReference
			implements CharDestination
	{
		Destination(TextBox thisScreen)
		{
			super(thisScreen);
		}

		public void insert(char src)
		{
			TextBox thisScreen;
			if((thisScreen = (TextBox) get()) == null)
			{
				return;
			}
			thisScreen.insert(src);
		}

		public void insert(String src)
		{
			TextBox thisScreen;
			if((thisScreen = (TextBox) get()) == null)
			{
				return;
			}
			thisScreen.insert(src);
		}
	}


	private Font font;
	private TextInput input;
	private ScrollBarStyle systemStyle;
	private CommandListener capabilities;

	public TextBox(String title, String text, int maxLength, int constraints)
	{
		this(false, false, title, null, null, null, null, text, maxLength, constraints);
	}

	public TextBox(boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command defaultCommand, CommandListener listener,
			String text, int maxLength, int constraints)
	{
		this(true, fullScreenMode, title, ticker, commands, defaultCommand, listener, text, maxLength, constraints);
	}

	private TextBox(boolean system, boolean fullScreenMode, String title, Ticker ticker,
			Command[] commands, Command defaultCommand, CommandListener listener,
			String text, int maxLength, int constraints)
	{
		super(system, fullScreenMode, title, ticker, concatCommands(commands,
				TextInput.getAdditionalCapabilitiesCommands(constraints, true)),
				defaultCommand, listener, VERTICAL);
		int charArrayLength = TextInput.DEFAULT_CHAR_ARRAY_LENGTH << 4;
		Font font;
		TextInput input;
		this.font = font = Font.getFont(Font.FONT_INPUT_TEXT);
		this.input = input = new TextInput(text, maxLength, constraints, charArrayLength);
		this.systemStyle = new ScrollBarSystem(font.getHeight());
		this.capabilities = input.new AdditionalCapabilities(new Destination(this));
		updateView();
	}

	public void insert(String src, int position)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.insert(src, position);
			if(src.length() > 0)
			{
				updateView();
			}
		}
	}

	public void insert(char[] src, int offset, int length, int position)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.insert(src, offset, length, position);
			if(length > 0)
			{
				updateView();
			}
		}
	}

	public void delete(int offset, int length)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.delete(offset, length);
			if(length > 0)
			{
				updateView();
			}
		}
	}

	public void setString(String src)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.setString(src);
			updateView();
		}
	}

	public void setChars(char[] src, int offset, int length)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.setChars(src, offset, length);
			updateView();
		}
	}

	public void setConstraints(int constraints)
	{
		int oldConstraints;
		TextInput input;
		synchronized(input = this.input)
		{
			if((oldConstraints = input.getConstraints()) != constraints)
			{
				input.setConstraints(constraints);
				removeCommands(TextInput.getAdditionalCapabilitiesCommands(oldConstraints, true));
				addCommands(TextInput.getAdditionalCapabilitiesCommands(constraints, true));
				updateView();
			}
		}
	}

	public void setInitialInputMode(String characterSubset)
	{
		input.setInitialInputMode(characterSubset);
	}

	public int setMaxSize(int maxLength)
	{
		int result;
		TextInput input;
		synchronized(input = this.input)
		{
			if(input.getMaxSize() != maxLength)
			{
				result = input.setMaxSize(maxLength);
				updateView();
			} else
			{
				result = maxLength;
			}
		}
		return result;
	}

	public int size()
	{
		return input.size();
	}

	public int getChars(char[] dst)
	{
		int result;
		TextInput input;
		synchronized(input = this.input)
		{
			result = input.getChars(dst);
		}
		return result;
	}

	public int getConstraints()
	{
		return input.getConstraints();
	}

	public int getCaretPosition()
	{
		return input.getCaretPosition();
	}

	public int getMaxSize()
	{
		return input.getMaxSize();
	}

	public String getString()
	{
		String result;
		TextInput input;
		synchronized(input = this.input)
		{
			result = input.getString();
		}
		return result;
	}

	void paint(Graphics render, ScrollBar horzScrollBar, ScrollBar vertScrollBar)
	{
		int i;
		int s;
		int len;
		int top;
		int bottom;
		int lineHeight;
		int topMarginElement;
		int caretLine;
		int caretPosition;
		char[] text;
		Font font;
		TextInput input;
		bottom = (top = vertScrollBar.getPosition()) + vertScrollBar.getPage();
		lineHeight = (font = this.font).getHeight();
		topMarginElement = 0;
		render.setFont(font);
		synchronized(input = this.input)
		{
			render.setColor(RasterCanvas.getSystemColor(
					(input.getConstraints() & UNEDITABLE) != 0 ? 0x23 : 0x21));
			caretLine = input.getLineAt(caretPosition = input.getCaretPosition());
			caretPosition -= input.getLineStart(caretLine);
			text = input.getChars();
			for(len = input.getLinesCount(), i = 0; i < len; render.translate(0, lineHeight),
					topMarginElement += lineHeight, i++)
			{
				if(topMarginElement >= bottom || topMarginElement + lineHeight <= top)
				{
					continue;
				}
				render.drawChars(text, s = input.getLineStart(i), input.getLineLength(i),
						1, 0, Graphics.LEFT | Graphics.TOP);
				if(i != caretLine)
				{
					continue;
				}
				s = font.charsWidth(text, s, caretPosition);
				render.drawLine(s, 0, s, lineHeight - 1);
			}
		}
	}

	void paintClient(Graphics render, int width, int height, int clipLeft, int clipTop, int clipWidth, int clipHeight)
	{
		Graphics clientRender;
		render.drawElementOfGUI(8, (input.getConstraints() & UNEDITABLE) != 0 ? 3 : 1, 0,
				0, 0, render.getClipWidth(), render.getClipHeight());
		(clientRender = getClientContext()).reset();
		clientRender.translate(getClientLeft(), getClientTop());
		clientRender.setClip(0, 0, getClientWidth(), getClientHeight());
		paint(clientRender);
	}

	void paintVertScrollBar(Graphics clientRender, ScrollBar scrollbar, int length, int width)
	{
		systemStyle.paintVertScrollBar(clientRender, scrollbar, length, width);
	}

	void onKeyPressed(int key, int charCode)
	{
		TextInput input;
		switch(key)
		{
		default:
			if(charCode <= 0)
			{
				break;
			}
			/* fall through */
		case MIDletProxy.KEY_TAB:
		case MIDletProxy.KEY_SHIFT:
		case MIDletProxy.KEY_LSHIFT:
		case MIDletProxy.KEY_RSHIFT:
		case MIDletProxy.KEY_BACKSPACE:
		case MIDletProxy.KEY_ENTER:
			synchronized(input = this.input)
			{
				input.keyPressed(key, charCode);
				if(input.isChanged())
				{
					updateView();
					correctScrollBarPositionToLine();
				}
			}
			return;
		case MIDletProxy.KEY_LEFT:
			synchronized(input = this.input)
			{
				input.setCaretPosition(input.getCaretPosition() - 1);
				correctScrollBarPositionToLine();
			}
			return;
		case MIDletProxy.KEY_RIGHT:
			synchronized(input = this.input)
			{
				input.setCaretPosition(input.getCaretPosition() + 1);
				correctScrollBarPositionToLine();
			}
			return;
		case MIDletProxy.KEY_UP:
			synchronized(input = this.input)
			{
				moveCaretUp();
				correctScrollBarPositionToLine();
			}
			return;
		case MIDletProxy.KEY_DOWN:
			synchronized(input = this.input)
			{
				moveCaretDown();
				correctScrollBarPositionToLine();
			}
			return;
		}
		super.onKeyPressed(key, charCode);
	}

	void onKeyRepeated(int key, int charCode)
	{
		TextInput input;
		switch(key)
		{
		default:
			if(charCode <= 0)
			{
				break;
			}
			/* fall through */
		case MIDletProxy.KEY_TAB:
		case MIDletProxy.KEY_SHIFT:
		case MIDletProxy.KEY_LSHIFT:
		case MIDletProxy.KEY_RSHIFT:
		case MIDletProxy.KEY_BACKSPACE:
		case MIDletProxy.KEY_ENTER:
			synchronized(input = this.input)
			{
				input.keyPressed(key, charCode);
				if(input.isChanged())
				{
					updateView();
					correctScrollBarPositionToLine();
				}
			}
			return;
		case MIDletProxy.KEY_LEFT:
			synchronized(input = this.input)
			{
				input.setCaretPosition(input.getCaretPosition() - 1);
			}
			correctScrollBarPositionToLine();
			return;
		case MIDletProxy.KEY_RIGHT:
			synchronized(input = this.input)
			{
				input.setCaretPosition(input.getCaretPosition() + 1);
			}
			correctScrollBarPositionToLine();
			return;
		case MIDletProxy.KEY_UP:
			synchronized(this.input)
			{
				moveCaretUp();
				correctScrollBarPositionToLine();
			}
			return;
		case MIDletProxy.KEY_DOWN:
			synchronized(this.input)
			{
				moveCaretDown();
				correctScrollBarPositionToLine();
			}
			return;
		}
		super.onKeyRepeated(key, charCode);
	}

	void onKeyReleased(int key)
	{
		switch(key)
		{
		case MIDletProxy.KEY_SHIFT:
		case MIDletProxy.KEY_LSHIFT:
		case MIDletProxy.KEY_RSHIFT:
			input.keyReleased(key);
			return;
		}
		super.onKeyReleased(key);
	}

	void onClientPointerPressed(int x, int y, int button)
	{
		pointerHandle(x, y);
	}

	void onClientPointerDragged(int x, int y)
	{
		pointerHandle(x, y);
	}

	void onClientPointerReleased(int x, int y, int button)
	{
		pointerHandle(x, y);
	}

	void onCommandAction(Command command)
	{
		if(TextInput.isAdditionalCapabilitiesCommand(command))
		{
			capabilities.commandAction(command, this);
			return;
		}
		super.onCommandAction(command);
	}

	void onVertScrollBarPointerPressed(ScrollBar scrollbar, int length, int width,
			int x, int y, int button)
	{
		systemStyle.onVertScrollBarPointerPressed(scrollbar, length, width, x, y, button);
	}

	void onVertScrollBarPointerDragged(ScrollBar scrollbar, int length, int width,
			int x, int y)
	{
		systemStyle.onVertScrollBarPointerDragged(scrollbar, length, width, x, y);
	}

	void onVertScrollBarPointerReleased(ScrollBar scrollbar, int length, int width,
			int x, int y, int button)
	{
		systemStyle.onVertScrollBarPointerReleased(scrollbar, length, width, x, y, button);
	}

	int getMarginLeft()
	{
		return 2;
	}

	int getMarginTop()
	{
		return 2;
	}

	int getMarginRight()
	{
		return getVertScrollBar().isVisible() ? getVertScrollBarWidth() + 2 : 2;
	}

	int getMarginBottom()
	{
		return getHorzScrollBar().isVisible() ? getHorzScrollBarWidth() + 2 : 2;
	}

	int getVertScrollBarWidth()
	{
		return systemStyle.getVertScrollBarWidth();
	}

	private void pointerHandle(int x, int y)
	{
		int i;
		int w1;
		int w2;
		int start;
		int length;
		int caretLine;
		int caretPosition;
		char[] text;
		Font font;
		TextInput input;
		synchronized(input = this.input)
		{
			font = this.font;
			text = input.getChars();
			caretLine = Math.min(y / font.getHeight(), input.getLinesCount() - 1);
			start = input.getLineStart(caretLine);
			length = input.getLineLength(caretLine);
			label0:
			{
				for(w1 = 0, i = 1; i <= length; w1 = w2, i++)
				{
					w2 = font.charsWidth(text, start, i);
					if(x >= w1 && x < w2)
					{
						caretPosition = x - w1 <= w2 - x ? i - 1 : i;
						break label0;
					}
				}
				caretPosition = length;
			}
			input.setCaretPosition(start + caretPosition);
			correctScrollBarPositionToLine();
		}
	}

	private void insert(char src)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.insert(src);
			if(input.isChanged())
			{
				updateView();
				correctScrollBarPositionToLine();
			}
		}
	}

	private void insert(String src)
	{
		TextInput input;
		synchronized(input = this.input)
		{
			input.insert(src);
			if(input.isChanged())
			{
				updateView();
				correctScrollBarPositionToLine();
			}
		}
	}

	private void updateView()
	{
		Font font;
		TextInput input;
		(input = this.input).split(font = this.font, getClientWidth() - 2);
		getVertScrollBar().setRange(input.getLinesCount() * font.getHeight());
		callSeriallyPaintScreen(CLIENT);
	}

	private void moveCaretUp()
	{
		int i;
		int w1;
		int w2;
		int start;
		int length;
		int charsWidth;
		int caretLine;
		int caretPosition;
		char[] text;
		Font font;
		TextInput input;
		if((caretLine = (input = this.input).
				getLineAt(caretPosition = input.getCaretPosition())) <= 0)
		{
			return;
		}
		font = this.font;
		caretPosition -= (start = input.getLineStart(caretLine));
		charsWidth = font.charsWidth(text = input.getChars(), start, caretPosition);
		start = input.getLineStart(--caretLine);
		length = input.getLineLength(caretLine);
		label0:
		{
			for(w1 = 0, i = 1; i <= length; w1 = w2, i++)
			{
				w2 = font.charsWidth(text, start, i);
				if(charsWidth >= w1 && charsWidth < w2)
				{
					caretPosition = charsWidth - w1 <= w2 - charsWidth ? i - 1 : i;
					break label0;
				}
			}
			caretPosition = length;
		}
		input.setCaretPosition(start + caretPosition);
	}

	private void moveCaretDown()
	{
		int i;
		int w1;
		int w2;
		int start;
		int length;
		int charsWidth;
		int caretLine;
		int caretPosition;
		char[] text;
		Font font;
		TextInput input;
		if((caretLine = (input = this.input).
				getLineAt(caretPosition = input.getCaretPosition())) >= input.getLinesCount() - 1)
		{
			return;
		}
		font = this.font;
		caretPosition -= (start = input.getLineStart(caretLine));
		charsWidth = font.charsWidth(text = input.getChars(), start, caretPosition);
		start = input.getLineStart(++caretLine);
		length = input.getLineLength(caretLine);
		label0:
		{
			for(w1 = 0, i = 1; i <= length; w1 = w2, i++)
			{
				w2 = font.charsWidth(text, start, i);
				if(charsWidth >= w1 && charsWidth < w2)
				{
					caretPosition = charsWidth - w1 <= w2 - charsWidth ? i - 1 : i;
					break label0;
				}
			}
			caretPosition = length;
		}
		input.setCaretPosition(start + caretPosition);
	}

	private void correctScrollBarPositionToLine()
	{
		int pos;
		int need;
		int lineHeight = font.getHeight();
		ScrollBar scrollbar = getVertScrollBar();
		if((pos = scrollbar.getPosition()) > (need = input.getCaretLine() * lineHeight))
		{
			scrollbar.setPosition(need);
		}
		else if(pos < (need -= scrollbar.getPage() - lineHeight))
		{
			scrollbar.setPosition(need);
		}
		callSeriallyPaintScreen(CLIENT);
	}

	private void addCommands(Command[] commands)
	{
		int i;
		int len;
		for(len = commands.length, i = 0; i < len; super.addCommand(commands[i]), i++);
	}

	private void removeCommands(Command[] commands)
	{
		int i;
		for(i = commands.length; i-- > 0; super.removeCommand(commands[i]));
	}
}
