/*
    Реализация спецификаций 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 malik.emulator.microedition.lcdui;

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

public class ChoiceList extends CustomSurfaceScreen implements TextImageMenu
{
    private static final int ICON_WIDTH = 32;
    private static final int ICON_HEIGHT = 32;
    private static final int MARGIN = 3;
    private static final int ELEMENT_HEIGHT = ICON_HEIGHT + MARGIN * 2;
    private static final int INITIAL_SCROLL = 32;

    public static int getBestImageWidth() {
        return ICON_WIDTH;
    }

    public static int getBestImageHeight() {
        return ICON_HEIGHT;
    }

    private static final class Element extends Object
    {
        public String text;
        public Image icon;
        public Font font;

        public Element(String text, Image icon, Font font) {
            this.text = text;
            this.icon = icon;
            this.font = font;
        }
    }

    private int selected;
    private int scroll;
    private int count;
    private Element[] elements;
    private final Object monitor;

    public ChoiceList(String title, ScrollBarStyle style) {
        this(title, style, new String[0], null);
    }

    public ChoiceList(String title, ScrollBarStyle style, String[] elements) {
        this(title, style, elements, null);
    }

    public ChoiceList(String title, ScrollBarStyle style, String[] elements, Image[] icons) {
        super(title, elements == null ? 0 : ELEMENT_HEIGHT * elements.length, true, style);
        int count;
        String[] copyElements;
        Element[] choiceElements;
        if(elements == null)
        {
            throw new NullPointerException("ChoiceList: аргумент elements равен нулевой ссылке.");
        }
        Array.copy(elements, 0, copyElements = new String[count = elements.length], 0, count);
        if(Array.findf(copyElements, 0, null) < count)
        {
            throw new NullPointerException("ChoiceList: аргумент elements содержит элементы, равные нулевой ссылке.");
        }
        if(icons != null && icons.length != count)
        {
            throw new IllegalArgumentException("ChoiceList: длина аргумента icons отличается от длины аргумента elements.");
        }
        choiceElements = new Element[count <= 0x0f ? 0x0f : StringBuilder.optimalCapacity(count)];
        for(int i = count; i-- > 0; ) choiceElements[i] = new Element(copyElements[i], icons == null ? null : icons[i], null);
        this.scroll = INITIAL_SCROLL;
        this.count = count;
        this.elements = choiceElements;
        this.monitor = new Object();
    }

    public void insert(int elementIndex, String text) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.insert: аргумент text равен нулевой ссылке.");
        }
        insert(elementIndex, new Element(text, null, null));
    }

    public void insert(int elementIndex, String text, Image icon) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.insert: аргумент text равен нулевой ссылке.");
        }
        insert(elementIndex, new Element(text, icon, null));
    }

    public void insert(int elementIndex, String text, Image icon, Font font) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.insert: аргумент text равен нулевой ссылке.");
        }
        insert(elementIndex, new Element(text, icon, font));
    }

    public void delete(int elementIndex) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int sel;
                int len;
                Element[] list;
                if(elementIndex < 0 || elementIndex > (len = count - 1))
                {
                    error = 1;
                    break label0;
                }
                list = elements;
                if(elementIndex < len) Array.copy(list, elementIndex + 1, list, elementIndex, len - elementIndex);
                list[count = len] = null;
                sel = selected;
                if(len > 0)
                {
                    if(sel == elementIndex)
                    {
                        if(sel >= len) sel = len - 1;
                        selected = sel;
                        scroll = INITIAL_SCROLL;
                    }
                    else if(sel > elementIndex)
                    {
                        selected = --sel;
                    }
                } else
                {
                    scroll = INITIAL_SCROLL;
                }
                super.getScrollBar().setRange(ELEMENT_HEIGHT * len);
                correctScrollBarPosition(sel);
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.delete: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void deleteAll() {
        synchronized(monitor)
        {
            Array.fill(elements, 0, count, null);
            selected = 0;
            scroll = INITIAL_SCROLL;
            count = 0;
            super.getScrollBar().setRange(0);
        }
    }

    public void setSelectedIndex(int elementIndex) {
        synchronized(monitor)
        {
            int lim;
            if(elementIndex > (lim = count - 1)) elementIndex = lim;
            if(elementIndex < 0) elementIndex = 0;
            correctScrollBarPosition(selected = elementIndex);
            scroll = INITIAL_SCROLL;
        }
    }

    public void setString(int elementIndex, String text) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.setString: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                elements[elementIndex].text = text;
                repaint();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.setString: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setString(int elementIndex, String text, Image icon) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.setString: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                Element element;
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                (element = elements[elementIndex]).text = text;
                element.icon = icon;
                repaint();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.setString: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setString(int elementIndex, String text, Image icon, Font font) {
        int error;
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.setString: аргумент text равен нулевой ссылке.");
        }
        error = 0;
        synchronized(monitor)
        {
            label0:
            {
                Element element;
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                (element = elements[elementIndex]).text = text;
                element.icon = icon;
                element.font = font;
                repaint();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.setString: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setImage(int elementIndex, Image icon) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                elements[elementIndex].icon = icon;
                repaint();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.setImage: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setFont(int elementIndex, Font font) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    break label0;
                }
                elements[elementIndex].font = font;
                repaint();
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.setFont: аргумент elementIndex выходит из диапазона.");
        }
    }

    public void setFont(Font font) {
        synchronized(monitor)
        {
            Element[] list = elements;
            for(int i = count; i-- > 0; list[i].font = font);
            repaint();
        }
    }

    public int append(String text) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.append: аргумент text равен нулевой ссылке.");
        }
        return append(new Element(text, null, null));
    }

    public int append(String text, Image icon) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.append: аргумент text равен нулевой ссылке.");
        }
        return append(new Element(text, icon, null));
    }

    public int append(String text, Image icon, Font font) {
        if(text == null)
        {
            throw new NullPointerException("ChoiceList.append: аргумент text равен нулевой ссылке.");
        }
        return append(new Element(text, icon, font));
    }

    public int getSize() {
        return count;
    }

    public int getSelectedIndex() {
        return count <= 0 ? -1 : selected;
    }

    public String getSelectedString() {
        int error = 0;
        String result;
        synchronized(monitor)
        {
            label0:
            {
                if(count <= 0)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[selected].text;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.getSelectedString: список пуст.");
        }
        return result;
    }

    public String getString(int elementIndex) {
        int error = 0;
        String result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].text;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.getString: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public Image getImage(int elementIndex) {
        int error = 0;
        Image result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].icon;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.getImage: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public Font getFont(int elementIndex) {
        int error = 0;
        Font result;
        synchronized(monitor)
        {
            label0:
            {
                if(elementIndex < 0 || elementIndex >= count)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                result = elements[elementIndex].font;
            }
        }
        if(error == 1)
        {
            throw new IndexOutOfBoundsException("ChoiceList.getFont: аргумент elementIndex выходит из диапазона.");
        }
        return result;
    }

    public Font getFont() {
        return null;
    }

    protected void paint(Graphics render) {
        int width = super.getWidth();
        int clipLeft = render.getClipX();
        int clipTop = render.getClipY();
        int clipWidth = render.getClipWidth();
        int clipHeight = render.getClipHeight();
        synchronized(monitor)
        {
            int srow;
            int frow;
            int length = this.count;
            int selected = this.selected;
            Element[] elements = this.elements;
            srow = clipTop / ELEMENT_HEIGHT;
            frow = (clipTop + clipHeight - 1) / ELEMENT_HEIGHT;
            for(int top = srow * ELEMENT_HEIGHT, index = srow; index <= frow && index < length; top += ELEMENT_HEIGHT, index++)
            {
                int left = MARGIN;
                Element element;
                String text;
                Image icon;
                Font font;
                text = (element = elements[index]).text;
                icon = element.icon;
                if((font = element.font) == null) font = Font.getDefaultFont();
                if(index == selected)
                {
                    int pos;
                    int wid = font.stringWidth(text);
                    int scroll = this.scroll;
                    render.setColor(RasterCanvas.getSystemColor(0x0d));
                    render.fillRect(0, top, width, ELEMENT_HEIGHT);
                    if(icon != null)
                    {
                        render.drawStretch(icon, left, top + MARGIN, ICON_WIDTH, ICON_HEIGHT, 0);
                        left += ICON_WIDTH + MARGIN;
                    }
                    render.setFont(font);
                    render.setColor(RasterCanvas.getSystemColor(0x0e));
                    render.setClip(left, top, pos = width - left - MARGIN, ELEMENT_HEIGHT);
                    render.drawString(text, wid > pos ? left + scroll : left, top + ((ELEMENT_HEIGHT - font.getHeight()) >> 1), 0);
                    render.setClip(clipLeft, clipTop, clipWidth, clipHeight);
                    if((scroll -= 2) < -wid || scroll >= pos) scroll = pos - 2;
                    if(wid > pos) repaint();
                    this.scroll = scroll;
                    continue;
                }
                if(icon != null)
                {
                    render.drawStretch(icon, left, top + MARGIN, ICON_WIDTH, ICON_HEIGHT, 0);
                    left += ICON_WIDTH + MARGIN;
                }
                render.setFont(font);
                render.setColor(RasterCanvas.getSystemColor(0x07));
                render.drawString(MultilinedStringBuilder.truncate(text, font, width - left - MARGIN), left, top + ((ELEMENT_HEIGHT - font.getHeight()) >> 1), 0);
            }
        }
    }

    protected void keyboardNotify(KeyboardEvent event) {
        int key = event.getKey();
        DeviceSettings settings = DeviceManager.getInstance().getSettings();
        switch(event.getAction())
        {
        case KeyboardEvent.ACTION_KEY_PRESSED:
        case KeyboardEvent.ACTION_KEY_REPEATED:
            if(settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_UP) == key)
            {
                synchronized(monitor)
                {
                    int len;
                    if((len = count) > 0)
                    {
                        int sel = (selected + len - 1) % len;
                        correctScrollBarPosition(selected = sel);
                        scroll = INITIAL_SCROLL;
                    }
                }
                break;
            }
            if(settings.getKeyUsedAs(DeviceSettings.DEVICE_KEY_DOWN) == key)
            {
                synchronized(monitor)
                {
                    int len;
                    if((len = count) > 0)
                    {
                        int sel = (selected + 1) % len;
                        correctScrollBarPosition(selected = sel);
                        scroll = INITIAL_SCROLL;
                    }
                }
                break;
            }
            break;
        }
    }

    protected void pointerNotify(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)
        {
            int lim;
            int index = event.getY() / ELEMENT_HEIGHT;
            if(index > (lim = count - 1)) index = lim;
            if(index < 0) index = 0;
            correctScrollBarPosition(selected = index);
            if(action != PointerEvent.ACTION_BUTTON_RELEASED && action != PointerEvent.ACTION_POINTER_RELEASED) scroll = INITIAL_SCROLL;
        }
    }

    private void correctScrollBarPosition(int selected) {
        int pos;
        int page;
        int line = ELEMENT_HEIGHT;
        int top1 = selected * line;
        int top2 = line + top1;
        ScrollBar scroll = super.getScrollBar();
        if(top1 < (pos = scroll.getPosition()))
        {
            scroll.setPosition(top1);
        }
        else if(top2 > pos + (page = scroll.getPage()))
        {
            scroll.setPosition(top2 - page);
        }
        else
        {
            repaint();
        }
    }

    private void insert(int index, Element element) {
        synchronized(monitor)
        {
            int sel;
            int len;
            Element[] list;
            if(index > (len = count)) index = len;
            if(index < 0) index = 0;
            if(len == (list = elements).length) Array.copy(list, 0, list = elements = new Element[(len << 1) + 1], 0, len);
            if(index < len) Array.copy(list, index, list, index + 1, len - index);
            list[index] = element;
            count = ++len;
            sel = selected;
            if(len > 1 && sel >= index) selected = ++sel;
            super.getScrollBar().setRange(ELEMENT_HEIGHT * len);
            correctScrollBarPosition(sel);
        }
    }

    private int append(Element element) {
        int result;
        synchronized(monitor)
        {
            int len;
            Element[] list;
            if((result = len = count) == (list = elements).length) Array.copy(list, 0, list = elements = new Element[(len << 1) + 1], 0, len);
            list[len++] = element;
            count = len;
            super.getScrollBar().setRange(ELEMENT_HEIGHT * len);
        }
        return result;
    }
}
