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

public class Gauge extends InteractiveItem
{
    public static final int INDEFINITE = -1;
    public static final int CONTINUOUS_IDLE = 0;
    public static final int INCREMENTAL_IDLE = 1;
    public static final int CONTINUOUS_RUNNING = 2;
    public static final int INCREMENTAL_UPDATING = 3;

    private static final int THUMB_HEIGHT;
    private static final int TRACK_HEIGHT;
    private static final int THUMB_WIDTH;
    private static final Font FONT;

    static {
        int i;
        int s = RasterCanvas.getGUIElementSize(11, 0, 0);
        THUMB_WIDTH = (i = (short) s) == -1 ? 8 : i;
        THUMB_HEIGHT = (i = s >> 16) == -1 ? 16 : i;
        TRACK_HEIGHT = (i = RasterCanvas.getGUIElementSize(11, 3, 0) >> 16) == -1 ? 8 : i;
        FONT = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
    }

    final boolean interactive;
    private boolean newInput;
    private boolean pressed;
    private int frame;
    private int width;
    private int height;
    private long value;
    private final char[] buffer;
    private final StringBuilder text;
    private final Object monitor;

    public Gauge(String label, boolean interactive, int maximumValue, int initialValue) {
        super(label);
        if(interactive ? maximumValue <= 0 : maximumValue != INDEFINITE && maximumValue <= 0)
        {
            throw new IllegalArgumentException("Gauge: аргумент maximumValue имеет недопустимое значение.");
        }
        if(!interactive && maximumValue == INDEFINITE && (initialValue < CONTINUOUS_IDLE || initialValue > INCREMENTAL_UPDATING))
        {
            throw new IllegalArgumentException("Gauge: аргумент initialValue имеет недопустимое значение.");
        }
        if(maximumValue > 0)
        {
            if(initialValue < 0) initialValue = 0;
            if(initialValue > maximumValue) initialValue = maximumValue;
        }
        this.interactive = interactive;
        this.newInput = true;
        this.buffer = interactive ? new char[23] : null;
        this.text = interactive ? new StringBuilder(23) : null;
        this.monitor = new Object();
        setValue(initialValue, maximumValue);
    }

    public void setValue(int currentValue) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int maximumValue;
                if((maximumValue = (int) (value >> 32)) == INDEFINITE && (currentValue < CONTINUOUS_IDLE || currentValue > INCREMENTAL_UPDATING))
                {
                    error = 1;
                    break label0;
                }
                if(maximumValue > 0)
                {
                    if(currentValue < 0) currentValue = 0;
                    if(currentValue > maximumValue) currentValue = maximumValue;
                }
                else if(currentValue == INCREMENTAL_UPDATING)
                {
                    frame++;
                }
                newInput = true;
                setValue(currentValue, maximumValue);
            }
        }
        if(error == 1)
        {
            throw new IllegalArgumentException("Gauge.setValue: аргумент currentValue имеет недопустимое значение.");
        }
        requestPaint();
    }

    public void setMaxValue(int maximumValue) {
        if(interactive ? maximumValue <= 0 : maximumValue != INDEFINITE && maximumValue <= 0)
        {
            throw new IllegalArgumentException("Gauge.setMaxValue: аргумент maximumValue имеет недопустимое значение.");
        }
        synchronized(monitor)
        {
            int maximumPrevious;
            int currentValue;
            long fields;
            currentValue = (int) (fields = value);
            maximumPrevious = (int) (fields >> 32);
            if(maximumValue > 0)
            {
                if(maximumPrevious == INDEFINITE)
                {
                    currentValue = 0;
                }
                else if(currentValue > maximumValue)
                {
                    currentValue = maximumValue;
                }
            }
            else if(maximumPrevious > 0)
            {
                currentValue = CONTINUOUS_IDLE;
            }
            newInput = true;
            setValue(currentValue, maximumValue);
        }
        requestPaint();
    }

    public boolean isInteractive() {
        return interactive;
    }

    public int getValue() {
        return (int) value;
    }

    public int getMaxValue() {
        return (int) (value >> 32);
    }

    strictfp void paint(Graphics render, int contentWidth, int contentHeight) {
        int nframe;
        int current;
        int maximum;
        Object monitor;
        synchronized(monitor = this.monitor)
        {
            long fields;
            current = (int) (fields = value);
            maximum = (int) (fields >> 32);
        }
        if(interactive)
        {
            int left;
            int top;
            int len;
            char[] buf = buffer;
            StringBuilder builder = text;
            width = contentWidth;
            height = contentHeight;
            top = TRACK_HEIGHT < THUMB_HEIGHT ? (THUMB_HEIGHT - TRACK_HEIGHT) >> 1 : 0;
            render.drawElement(11, 3, focused ? 1 : 0, 1, top, contentWidth - 2, TRACK_HEIGHT);
            left = (int) ((long) (contentWidth - THUMB_WIDTH) * (long) current / (long) maximum);
            top = THUMB_HEIGHT < TRACK_HEIGHT ? (TRACK_HEIGHT - THUMB_HEIGHT) >> 1 : 0;
            render.drawElement(11, 0, focused ? pressed ? 1 : 3 : 0, left, top, THUMB_WIDTH, THUMB_HEIGHT);
            if((top = TRACK_HEIGHT < THUMB_HEIGHT ? THUMB_HEIGHT : TRACK_HEIGHT) < contentHeight)
            {
                builder.clear();
                builder.append(current).append(" / ").append(maximum).copy(0, len = builder.length(), buf, 0);
                render.setFont(FONT);
                render.setColor(RasterCanvas.getSystemColor(0x28));
                render.drawChars(buf, 0, len, contentWidth, top, Graphics.RIGHT | Graphics.TOP);
            }
            return;
        }
        if(maximum > 0)
        {
            int width = (int) ((long) (contentWidth - 4) * (long) current / (long) maximum);
            render.drawElement(9, 0, 0, 0, 0, contentWidth, contentHeight);
            if(width > 0) render.drawElement(9, 1, 0, 2, 2, width, contentHeight - 4);
            return;
        }
        switch(current)
        {
        default:
            render.drawElement(9, 0, 0, 0, 0, contentWidth, contentHeight);
            return;
        case CONTINUOUS_RUNNING:
            synchronized(monitor)
            {
                nframe = frame++;
            }
            requestPaint();
            break;
        case INCREMENTAL_UPDATING:
            nframe = frame;
            break;
        }
        render.drawElement(9, 0, 0, 0, 0, contentWidth, contentHeight);
        {
            int width = (int) (0.236067977f * (float) (contentWidth - 4));
            int half = (contentWidth - width - 4) >> 1;
            int left = (int) (Math.sin(0.196349540849362077d * (double) nframe) * (double) half) + half;
            render.drawElement(9, 1, 0, left + 2, 2, width, contentHeight - 4);
        }
    }

    void onKeyboardEvent(KeyboardEvent event) {
        int digit;
        if(interactive && (digit = event.getCharCode() - '0') >= 0 && digit <= 9)
        {
            int oldcurr;
            long newcurr;
            synchronized(monitor)
            {
                int maximum;
                long fields;
                oldcurr = (int) (fields = value);
                maximum = (int) (fields >> 32);
                if(newInput = (newcurr = newInput ? (long) digit : 10L * (long) oldcurr + (long) digit) < 0L || newcurr > (long) maximum) newcurr = (long) maximum;
                setValue((int) newcurr, maximum);
            }
            if((long) oldcurr != newcurr)
            {
                super.notifyStateChanged();
                super.requestPaint();
            }
        }
    }

    void onPointerEvent(PointerEvent event) {
        if(interactive)
        {
            int x = event.getX();
            int y = event.getY();
            switch(event.getAction())
            {
            case PointerEvent.ACTION_BUTTON_PRESSED:
            case PointerEvent.ACTION_POINTER_PRESSED:
                if(event.getButton() == PointerEvent.BUTTON_MAIN && y >= 0 && y < (THUMB_HEIGHT < TRACK_HEIGHT ? TRACK_HEIGHT : THUMB_HEIGHT))
                {
                    int oldcurr;
                    int newcurr;
                    synchronized(monitor)
                    {
                        int left;
                        int maximum;
                        long fields;
                        oldcurr = (int) (fields = value);
                        maximum = (int) (fields >> 32);
                        left = (int) ((long) (width - THUMB_WIDTH) * (long) oldcurr / (long) maximum);
                        if(x >= left + THUMB_WIDTH)
                        {
                            if((newcurr = oldcurr + (maximum >= 32 ? maximum / 16 : 1)) < 0 || newcurr > maximum) newcurr = maximum;
                            newInput = true;
                        }
                        else if(x < left)
                        {
                            if((newcurr = oldcurr - (maximum >= 32 ? maximum / 16 : 1)) < 0) newcurr = 0;
                            newInput = true;
                        }
                        else
                        {
                            newcurr = oldcurr;
                            pressed = true;
                        }
                        setValue(newcurr, maximum);
                    }
                    if(oldcurr != newcurr) super.notifyStateChanged();
                    requestPaint();
                }
                break;
            case PointerEvent.ACTION_POINTER_DRAGGED:
                if(pressed)
                {
                    computeValue(x);
                    requestPaint();
                }
                break;
            case PointerEvent.ACTION_POINTER_RELEASED:
            case PointerEvent.ACTION_BUTTON_RELEASED:
                if(pressed)
                {
                    pressed = false;
                    computeValue(x);
                    requestPaint();
                }
                break;
            }
        }
    }

    void onFocusLost() {
        pressed = false;
        super.onFocusLost();
    }

    boolean onFocusMove(int direction, int viewportWidth, int viewportHeight, int[] visibleRectangle) {
        int w;
        int h;
        int oldcurr;
        int newcurr;
        if(!interactive) return false;
        visibleRectangle[0] = viewportWidth >= (w = width) ? 0 : w - viewportWidth;
        visibleRectangle[1] = viewportHeight >= (h = height) ? 0 : h - viewportHeight;
        visibleRectangle[2] = viewportWidth >= w ? w : viewportWidth;
        visibleRectangle[3] = viewportHeight >= h ? h : viewportHeight;
        if(!super.onFocusMove(direction, viewportWidth, viewportHeight, visibleRectangle))
        {
            pressed = false;
            return true;
        }
        switch(direction)
        {
        default:
            return false;
        case DIR_LEFT:
            synchronized(monitor)
            {
                int maximum;
                long fields;
                oldcurr = (int) (fields = value);
                maximum = (int) (fields >> 32);
                if((newcurr = oldcurr - 1) < 0) newcurr = 0;
                newInput = true;
                setValue(newcurr, maximum);
            }
            break;
        case DIR_RIGHT:
            synchronized(monitor)
            {
                int maximum;
                long fields;
                oldcurr = (int) (fields = value);
                maximum = (int) (fields >> 32);
                if((newcurr = oldcurr + 1) > maximum) newcurr = maximum;
                newInput = true;
                setValue(newcurr, maximum);
            }
            break;
        }
        if(oldcurr != newcurr)
        {
            super.notifyStateChanged();
            super.requestPaint();
        }
        return true;
    }

    boolean hasRowBreakBefore(int layout, int alignment) {
        return interactive || super.hasRowBreakBefore(layout, alignment);
    }

    boolean hasRowBreakAfter(int layout, int alignment) {
        return interactive || super.hasRowBreakAfter(layout, alignment);
    }

    int getMinimumContentWidth() {
        return 100;
    }

    int getMinimumContentHeight() {
        return interactive ? (TRACK_HEIGHT < THUMB_HEIGHT ? THUMB_HEIGHT : TRACK_HEIGHT) + FONT.getHeight() : 24;
    }

    private void setValue(int currentValue, int maximumValue) {
        this.value = (long) maximumValue << 32 | (long) currentValue;
    }

    private strictfp void computeValue(int x) {
        int oldcurr;
        int newcurr;
        synchronized(monitor)
        {
            int contentWidth;
            int maximum;
            long fields;
            double position;
            oldcurr = (int) (fields = value);
            maximum = (int) (fields >> 32);
            if((contentWidth = width - THUMB_WIDTH) <= 0) contentWidth = 1;
            position = (double) (x - (THUMB_WIDTH >> 1)) * (double) maximum / (double) contentWidth;
            newcurr = 0.d > position ? 0 : position > (double) maximum ? maximum : (int) position;
            newInput = true;
            setValue(newcurr, maximum);
        }
        if(oldcurr != newcurr) super.notifyStateChanged();
    }
}
