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

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

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

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

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

package javax.microedition.lcdui;

import malik.emulator.application.*;
import malik.emulator.media.graphics.*;
import malik.emulator.microedition.lcdui.*;

public class TextField extends InteractiveItem implements Input
{
    private static final Font EDITOR_FONT;

    static {
        EDITOR_FONT = Font.getFont(Font.FONT_INPUT_TEXT);
    }

    private int scroll;
    private char[] buffer;
    private final CommandOwner box;
    private final Input alternative;
    private final InputStringBuilder builder;
    private final Object monitor;

    public TextField(String label, String inputText, int maximumLength, int constraints) {
        super(label);
        Command[] commands;
        CommandOwner box;
        InputStringBuilder builder;
        Object monitor = builder = new InputStringBuilder(inputText, maximumLength, constraints);
        this.box = box = builder.new AdditionalCapabilities(false, monitor) {
            public void commandAction(Command command, Displayable screen) {
                super.commandAction(command, screen);
                synchronized(monitor)
                {
                    if(parentBuilder().isModified()) (TextField.this).onStateChanged();
                }
            }
        };
        this.alternative = builder.newInput();
        this.builder = builder;
        this.monitor = monitor;
        for(int len = (commands = box.getMyOwnedCommands()) == null ? 0 : commands.length, i = 0; i < len; i++)
        {
            Command command;
            if((command = commands[i]) != null) super.addCommand(command);
        }
    }

    public void insert(char[] src, int offset, int length, int position) {
        int error;
        if(src == null)
        {
            throw new NullPointerException("TextField.insert: аргумент src равен нулевой ссылке.");
        }
        Array.checkBound("TextField.insert", src.length, offset, length);
        if(length <= 0) return;
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int currentLength;
                InputStringBuilder builder = this.builder;
                if(length + (currentLength = builder.length()) > builder.getMaximumLength())
                {
                    error = 1;
                    break label0;
                }
                alternative.insert(src, offset, length, position);
                if(currentLength == builder.length()) error = 2;
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalArgumentException("TextField.insert: попытка превысить максимальную длину.");
        case 2:
            throw new IllegalArgumentException("TextField.insert: текст не соответствует ограничениям.");
        }
        requestPaint();
    }

    public void insert(String text, int position) {
        int error;
        int length;
        if(text == null)
        {
            throw new NullPointerException("TextField.insert: аргумент text равен нулевой ссылке.");
        }
        if((length = text.length()) <= 0) return;
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int currentLength;
                InputStringBuilder builder = this.builder;
                if(length + (currentLength = builder.length()) > builder.getMaximumLength())
                {
                    error = 1;
                    break label0;
                }
                alternative.insert(text, position);
                if(currentLength == builder.length()) error = 2;
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalArgumentException("TextField.insert: попытка превысить максимальную длину.");
        case 2:
            throw new IllegalArgumentException("TextField.insert: текст не соответствует ограничениям.");
        }
        requestPaint();
    }

    public void delete(int offset, int length) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int currentLength;
                InputStringBuilder builder;
                if(!Array.isBoundValid(currentLength = (builder = this.builder).length(), offset, length))
                {
                    error = 1;
                    break label0;
                }
                alternative.delete(offset, length);
                if(length > 0 && currentLength == builder.length()) error = 2;
            }
        }
        switch(error)
        {
        case 1:
            throw new StringIndexOutOfBoundsException("TextField.delete: индекс выходит из диапазона.");
        case 2:
            throw new IllegalArgumentException("TextField.delete: текст не соответствует ограничениям.");
        }
        requestPaint();
    }

    public void setConstraints(int constraints) {
        if(!InputStringBuilder.isConstraintsValid(constraints))
        {
            throw new IllegalArgumentException("TextField.setConstraints: аргумент constraints имеет недопустимое значение.");
        }
        synchronized(monitor)
        {
            Command[] commands;
            CommandOwner box;
            for(int i = (commands = (box = this.box).getMyOwnedCommands()) == null ? 0 : commands.length; i-- > 0; ) super.removeCommand(commands[i]);
            alternative.setConstraints(constraints);
            for(int len = (commands = box.getMyOwnedCommands()) == null ? 0 : commands.length, i = 0; i < len; i++)
            {
                Command command;
                if((command = commands[i]) != null) super.addCommand(command);
            }
        }
        requestPaint();
    }

    public void setChars(char[] src, int offset, int length) {
        int error;
        if(src != null)
        {
            Array.checkBound("TextField.setChars", src.length, offset, length);
            Array.copy(src, offset, src = new char[length], offset = 0, length);
        } else
        {
            offset = length = 0;
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                InputStringBuilder builder;
                if(length > (builder = this.builder).getMaximumLength())
                {
                    error = 1;
                    break label0;
                }
                if(!InputStringBuilder.isConstraintsMatch(builder.getConstraints(), src, offset, length))
                {
                    error = 2;
                    break label0;
                }
                alternative.setChars(src, offset, length);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalArgumentException("TextField.setChars: попытка превысить максимальную длину.");
        case 2:
            throw new IllegalArgumentException("TextField.setChars: текст не соответствует ограничениям.");
        }
        requestPaint();
    }

    public void setString(String inputText) {
        int error = 0;
        int length = inputText == null ? 0 : inputText.length();
        synchronized(monitor)
        {
            label0:
            {
                InputStringBuilder builder;
                if(length > (builder = this.builder).getMaximumLength())
                {
                    error = 1;
                    break label0;
                }
                if(!InputStringBuilder.isConstraintsMatch(builder.getConstraints(), inputText))
                {
                    error = 2;
                    break label0;
                }
                alternative.setString(inputText);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalArgumentException("TextField.setString: попытка превысить максимальную длину.");
        case 2:
            throw new IllegalArgumentException("TextField.setString: текст не соответствует ограничениям.");
        }
        requestPaint();
    }

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

    public int setMaxSize(int maximumSize) {
        int result;
        if(maximumSize <= 0)
        {
            throw new IllegalArgumentException("TextField.setMaxSize: аргумент maximumSize может быть только положительным.");
        }
        synchronized(monitor)
        {
            result = alternative.setMaxSize(maximumSize);
        }
        requestPaint();
        return result;
    }

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

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

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

    public int getChars(char[] dst) {
        int error;
        int result;
        if(dst == null)
        {
            throw new NullPointerException("TextField.getChars: аргумент dst равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(dst.length < builder.length())
                {
                    error = 1;
                    result = 0;
                    break label0;
                }
                result = alternative.getChars(dst);
            }
        }
        if(error == 1)
        {
            throw new ArrayIndexOutOfBoundsException("TextField.getChars: индекс массива выходит из диапазона.");
        }
        return result;
    }

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

    public String getString() {
        String result;
        synchronized(monitor)
        {
            result = alternative.getString();
        }
        return result;
    }

    void paint(Graphics render, int contentWidth, int contentHeight) {
        synchronized(monitor)
        {
            boolean f;
            boolean r;
            int bpos;
            int blen;
            int bwid;
            int bscr;
            char[] lbuf;
            Font font = EDITOR_FONT;
            InputStringBuilder builder = this.builder;
            f = focused;
            r = (builder.getConstraints() & UNEDITABLE) != 0;
            bpos = builder.getCaretPosition();
            blen = builder.length();
            if((lbuf = buffer) == null || lbuf.length < blen) lbuf = buffer = new char[InputStringBuilder.optimalCapacity(blen - 1) + 1];
            builder.copy(0, blen, lbuf, 0);
            if((bwid = font.charsWidth(lbuf, 0, bpos)) < (bscr = scroll) || bwid >= bscr + contentWidth - 8)
            {
                int nscr = bwid - ((contentWidth - 8) >> 1);
                scroll = bscr = nscr < 0 ? 0 : nscr;
            }
            render.setFont(font);
            render.setColor(RasterCanvas.getSystemColor(f ? r ? 0x23 : 0x21 : 0x20));
            render.drawElement(8, f ? r ? 3 : 1 : 0, 0, 0, 0, contentWidth, contentHeight);
            render.clipRect(3, 2, contentWidth - 6, contentHeight - 4);
            render.drawChars(lbuf, 0, blen, 4 - bscr, 2, 0);
            if(f) render.drawLine(bscr = bwid - bscr + 3, 2, bscr, font.getHeight() + 1);
        }
    }

    void onCommandAction(Command command) {
        Screen owner;
        CommandOwner box;
        if((box = this.box).isMyOwnedCommand(command) && (owner = this.owner) != null)
        {
            box.commandAction(command, owner);
            return;
        }
        super.onCommandAction(command);
    }

    void onKeyboardEvent(KeyboardEvent event) {
        synchronized(monitor)
        {
            InputStringBuilder builder;
            (builder = this.builder).keyboardEvent(event);
            if(builder.isModified()) onStateChanged();
        }
    }

    void onPointerEvent(PointerEvent event) {
        int action = event.getAction();
        if(
            event.isButtonPressed(PointerEvent.BUTTON_MAIN) || (action == PointerEvent.ACTION_BUTTON_RELEASED ||
            action == PointerEvent.ACTION_POINTER_RELEASED) && event.getButton() == PointerEvent.BUTTON_MAIN
        ) synchronized(monitor)
        {
            moveCaret(builder, scroll + event.getX() - 4);
        }
    }

    boolean onFocusMove(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
        boolean result;
        if(!super.onFocusMove(direction, viewportWidth, viewportHeight, visibleRectangle))
        {
            switch(direction)
            {
            case DIR_RIGHT:
                synchronized(monitor)
                {
                    builder.setCaretPosition(0);
                }
                break;
            case DIR_LEFT:
                synchronized(monitor)
                {
                    InputStringBuilder builder;
                    (builder = this.builder).setCaretPosition(builder.length());
                }
                break;
            }
            requestPaint();
            return true;
        }
        switch(direction)
        {
        case DIR_NONE:
            return true;
        case DIR_UP:
        case DIR_DOWN:
            return false;
        case DIR_RIGHT:
            synchronized(monitor)
            {
                int position;
                InputStringBuilder builder;
                if(result = (position = (builder = this.builder).getCaretPosition()) < builder.length()) builder.setCaretPosition(position + 1);
            }
            if(!result) return false;
            break;
        case DIR_LEFT:
            synchronized(monitor)
            {
                int position;
                InputStringBuilder builder;
                if(result = (position = (builder = this.builder).getCaretPosition()) > 0) builder.setCaretPosition(position - 1);
            }
            if(!result) return false;
            break;
        }
        requestPaint();
        return true;
    }

    int getMinimumContentWidth() {
        return 100;
    }

    int getMinimumContentHeight() {
        return EDITOR_FONT.getHeight() + 4;
    }

    final void onStateChanged() {
        super.notifyStateChanged();
        super.requestPaint();
    }

    private void moveCaret(InputStringBuilder builder, int charsWidth) {
        int blen = builder.length();
        int caretCol;
        char[] lbuf;
        Font font = EDITOR_FONT;
        if(charsWidth < 0) charsWidth = 0;
        if((lbuf = buffer) == null || lbuf.length < blen) lbuf = buffer = new char[InputStringBuilder.optimalCapacity(blen - 1) + 1];
        builder.copy(0, blen, lbuf, 0);
        label0:
        {
            for(int w1 = 0, w2, i = 1; i <= blen; w1 = w2, i++)
            {
                w2 = font.charsWidth(lbuf, 0, i);
                if(charsWidth >= w1 && charsWidth < w2)
                {
                    caretCol = charsWidth - w1 <= w2 - charsWidth ? i - 1 : i;
                    break label0;
                }
            }
            caretCol = blen;
        }
        builder.setCaretPosition(caretCol);
        if(builder.isModified()) requestPaint();
    }
}
