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

public abstract class ScrollingScreen extends Screen
{
    static final byte SCROLL_NONE = 0x00;
    static final byte SCROLL_HORZ = 0x10;
    static final byte SCROLL_VERT = 0x20;
    static final byte SCROLL_BOTH = 0x30;
    private static final int WHEEL_SCROLL = 16; /* скорость прокрутки с помощью колеса мыши */

    class Helper extends Screen.Helper
    {
        private int scrollHorz;
        private int scrollVert;

        public Helper() {
            this.scrollHorz = -1;
            this.scrollVert = -1;
        }

        protected void execute() {
            serviceSizeChanged();
            serviceScroll();
            servicePaint();
        }

        protected final void serviceScroll() {
            int hpos;
            int vpos;
            ScrollingScreen owner;
            if((owner = ScrollingScreen.this).getParentDisplay() == null) return;
            hpos = owner.horz.invalidatePosition();
            vpos = owner.vert.invalidatePosition();
            if(scrollHorz == hpos && scrollVert == vpos) return;
            try
            {
                owner.onClientScroll(scrollHorz = hpos, scrollVert = vpos, availableWidth(), availableHeight());
            }
            catch(RuntimeException e)
            {
                e.printRealStackTrace();
            }
        }

        protected final int positionHorizontal() {
            return scrollHorz;
        }

        protected final int positionVertical() {
            return scrollVert;
        }
    }

    private abstract class BasicScrollBar extends CustomScrollBar
    {
        protected BasicScrollBar(boolean visibility, int range, Object monitor) {
            super(visibility, range, monitor);
        }

        public abstract int getPage();

        public void repaint() {
            (ScrollingScreen.this).requestPaint(Displayable.CLIENT);
        }

        protected void notifyFieldsChanged(int fields) {
            (ScrollingScreen.this).requestPaint(Displayable.CLIENT);
        }
    }

    private final class HorzScrollBar extends BasicScrollBar
    {
        public HorzScrollBar(boolean visibility, int range, Object monitor) {
            super(visibility, range, monitor);
        }

        public int getPage() {
            return (ScrollingScreen.this).getApplicationWidth();
        }
    }

    private final class VertScrollBar extends BasicScrollBar
    {
        public VertScrollBar(boolean visibility, int range, Object monitor) {
            super(visibility, range, monitor);
        }

        public int getPage() {
            return (ScrollingScreen.this).getApplicationHeight();
        }
    }

    private boolean shift;
    private int focused;
    private final int size;
    private final int[] rect;
    final CustomScrollBar vert;
    final CustomScrollBar horz;
    private final ScrollBarStyle style;

    ScrollingScreen(String title, Ticker ticker, boolean fullScreen, byte scrollbars, ScrollBarStyle style) {
        this(title, ticker, fullScreen, 0, 0, scrollbars, style);
    }

    ScrollingScreen(String title, Ticker ticker, boolean fullScreen, int horizontalRange, int verticalRange, byte scrollbars, ScrollBarStyle style) {
        super(title, ticker, fullScreen);
        Object monitor = new Object();
        if(style == null) style = new ScreenScrollBarStyle();
        this.size = style.size();
        this.rect = new int[4];
        this.vert = new VertScrollBar((scrollbars & SCROLL_VERT) != 0, verticalRange, monitor);
        this.horz = new HorzScrollBar((scrollbars & SCROLL_HORZ) != 0, horizontalRange, monitor);
        this.style = style;
    }

    public final void setFullScreenMode(boolean fullScreen) {
        super.setFullScreenMode(fullScreen);
    }

    public final boolean isFullScreenMode() {
        return super.isFullScreenMode();
    }

    public final void scroll(int deltaHorizontal, int deltaVertical) {
        boolean exec = false;
        int hpage = getApplicationWidth();
        int vpage = getApplicationHeight();
        ScrollBar horz = this.horz;
        ScrollBar vert = this.vert;
        synchronized(horz.monitor)
        {
            int maximum;
            int oldPosition;
            int newPosition;
            oldPosition = horz.position;
            newPosition = deltaHorizontal + oldPosition;
            maximum = horz.range - hpage;
            if(newPosition > maximum) newPosition = maximum;
            if(newPosition < 0) newPosition = 0;
            if(oldPosition != newPosition)
            {
                horz.position = newPosition;
                exec = true;
            }
            oldPosition = vert.position;
            newPosition = deltaVertical + oldPosition;
            maximum = vert.range - vpage;
            if(newPosition > maximum) newPosition = maximum;
            if(newPosition < 0) newPosition = 0;
            if(oldPosition != newPosition)
            {
                vert.position = newPosition;
                exec = true;
            }
        }
        if(exec) requestPaint(CLIENT);
    }

    public final void scrollTo(int positionHorizontal, int positionVertical) {
        boolean exec = false;
        int hpage = getApplicationWidth();
        int vpage = getApplicationHeight();
        ScrollBar horz = this.horz;
        ScrollBar vert = this.vert;
        synchronized(horz.monitor)
        {
            int maximum;
            int oldPosition;
            int newPosition;
            oldPosition = horz.position;
            newPosition = positionHorizontal;
            maximum = horz.range - hpage;
            if(newPosition > maximum) newPosition = maximum;
            if(newPosition < 0) newPosition = 0;
            if(oldPosition != newPosition)
            {
                horz.position = newPosition;
                exec = true;
            }
            oldPosition = vert.position;
            newPosition = positionVertical;
            maximum = vert.range - vpage;
            if(newPosition > maximum) newPosition = maximum;
            if(newPosition < 0) newPosition = 0;
            if(oldPosition != newPosition)
            {
                vert.position = newPosition;
                exec = true;
            }
        }
        if(exec) requestPaint(CLIENT);
    }

    public final ScrollBar getVerticalScrollBar() {
        return vert;
    }

    public final ScrollBar getHorizontalScrollBar() {
        return horz;
    }

    abstract void paint(ScreenGraphics render);

    void paintClient(ScreenGraphics render, int width, int height, int clipLeft, int clipTop, int clipWidth, int clipHeight, Image clipBuffer) {
        boolean hvisible;
        boolean vvisible;
        int hpos;
        int vpos;
        int size = this.size;
        int left = super.getMarginLeft();
        int top = super.getMarginTop();
        int right = super.getMarginRight();
        int bottom = super.getMarginBottom();
        int tx = left + render.getTranslateX();
        int ty = top + render.getTranslateY();
        ScrollBar horz = this.horz;
        ScrollBar vert = this.vert;
        ScrollBarStyle style = this.style;
        hvisible = horz.visibility;
        vvisible = vert.visibility;
        hpos = horz.position;
        vpos = vert.position;
        width -= left + right + (vvisible ? size : 0);
        height -= top + bottom + (hvisible ? size : 0);
        if(hvisible)
        {
            render.reset();
            render.translate(tx, ty + height);
            style.horizontalScrollBarPaintEvent(horz, render, width, size);
        }
        if(vvisible)
        {
            render.reset();
            render.translate(tx + width, ty);
            style.verticalScrollBarPaintEvent(vert, render, size, height);
            if(hvisible)
            {
                render.reset();
                render.translate(tx + width, ty + height);
                style.sizeGripPaintEvent(false, render, size, size);
            }
        }
        render.reset();
        render.restricts(tx, ty, width, height);
        render.setStartPoint(tx - hpos, ty - vpos);
        render.setClip(tx, ty, width, height);
        render.clipRect(tx + clipLeft, ty + clipTop, clipWidth, clipHeight);
        render.translate(tx - hpos, ty - vpos);
        paint(render);
    }

    boolean onKeyboardEvent(KeyboardEvent event) {
        if(super.onKeyboardEvent(event)) return true;
        shift = event.isKeyPressed(KeyboardEvent.KEY_SHIFT) || event.isKeyPressed(KeyboardEvent.KEY_LSHIFT) || event.isKeyPressed(KeyboardEvent.KEY_RSHIFT);
        onClientKeyboardEvent(event);
        return true;
    }

    boolean onPointerEvent(PointerEvent event) {
        boolean down;
        int b;
        int f;
        if(super.onPointerEvent(event)) return true;
        switch(event.getAction())
        {
        case PointerEvent.ACTION_POINTER_PRESSED:
            if((down = (b = event.getButton()) == PointerEvent.BUTTON_WHEEL_DOWN) || b == PointerEvent.BUTTON_WHEEL_UP)
            {
                ((ScrollBar) (shift ? horz : vert)).scroll(down ? WHEEL_SCROLL : -WHEEL_SCROLL);
                return true;
            }
            return handlePointerEvent(focused = getFocusedElement(event.getX(), event.getY(), b), event);
        case PointerEvent.ACTION_BUTTON_PRESSED:
            if((down = (b = event.getButton()) == PointerEvent.BUTTON_WHEEL_DOWN) || b == PointerEvent.BUTTON_WHEEL_UP)
            {
                ((ScrollBar) (shift ? horz : vert)).scroll(down ? WHEEL_SCROLL : -WHEEL_SCROLL);
                return true;
            }
            /* fall through */
        case PointerEvent.ACTION_POINTER_DRAGGED:
            return handlePointerEvent(focused, event);
        case PointerEvent.ACTION_BUTTON_RELEASED:
            return (b = event.getButton()) == PointerEvent.BUTTON_WHEEL_DOWN || b == PointerEvent.BUTTON_WHEEL_UP || handlePointerEvent(focused, event);
        case PointerEvent.ACTION_POINTER_RELEASED:
            f = focused;
            focused = 0;
            return (b = event.getButton()) == PointerEvent.BUTTON_WHEEL_DOWN || b == PointerEvent.BUTTON_WHEEL_UP || handlePointerEvent(f, event);
        }
        return false;
    }

    Displayable.Helper createHelper() {
        return this.new Helper();
    }

    void onClientScroll(int positionHorizontal, int positionVertical, int clientWidth, int clientHeight) {
    }

    final int getMarginLeft() {
        return super.getMarginLeft();
    }

    final int getMarginTop() {
        return super.getMarginTop();
    }

    final int getMarginRight() {
        return super.getMarginRight() + (vert.visibility ? size : 0);
    }

    final int getMarginBottom() {
        return super.getMarginBottom() + (horz.visibility ? size : 0);
    }

    private boolean handlePointerEvent(int focused, PointerEvent event) {
        int a;
        int[] r = rect;
        switch(focused)
        {
        case 1:
            event.translate(r[0], r[1]);
            style.horizontalScrollBarPointerEvent(horz, event, r[2], r[3]);
            return true;
        case 2:
            event.translate(r[0], r[1]);
            style.verticalScrollBarPointerEvent(vert, event, r[2], r[3]);
            return true;
        case 3:
            event.translate(r[0] - horz.position, r[1] - vert.position);
            onClientPointerEvent(event);
            return true;
        case 4:
            if((a = event.getAction()) == PointerEvent.ACTION_POINTER_DRAGGED || a == PointerEvent.ACTION_BUTTON_RELEASED || a == PointerEvent.ACTION_POINTER_RELEASED)
            {
                event.translate(r[0], r[1]);
                scroll(event.historicalX(1) - event.getX(), event.historicalY(1) - event.getY());
            }
            return true;
        }
        return false;
    }

    private int getFocusedElement(int x, int y, int button) {
        boolean hvisible = horz.visibility;
        boolean vvisible = vert.visibility;
        byte e = getElements();
        int l = super.getMarginLeft();
        int t = super.getMarginTop() + ((e & TICKER) != 0 ? getTickerHeight() : 0) + ((e & TITLE) != 0 ? getTitleHeight() : 0);
        int w = getTotalWidth() - super.getMarginRight() - l;
        int h = getTotalHeight() - super.getMarginBottom() - t - ((e & PANEL) != 0 ? getPanelHeight() : 0);
        int s = size;
        int[] r = rect;
        if(hvisible)
        {
            int st = t + h - s;
            int sw = vvisible ? w - s : w;
            if(x >= l && x < l + sw && y >= st && y < st + s)
            {
                r[0] = l;
                r[1] = st;
                r[2] = sw;
                r[3] = s;
                return 1;
            }
        }
        if(vvisible)
        {
            int sl = l + w - s;
            int sh = hvisible ? h - s : h;
            if(x >= sl && x < sl + s && y >= t && y < t + sh)
            {
                r[0] = sl;
                r[1] = t;
                r[2] = s;
                r[3] = sh;
                return 2;
            }
        }
        if(hvisible) h -= s;
        if(vvisible) w -= s;
        if(x >= l && x < l + w && y >= t && y < t + h)
        {
            r[0] = l;
            r[1] = t;
            r[2] = w;
            r[3] = h;
            return button == PointerEvent.BUTTON_WHEEL ? 4 : 3;
        }
        return 0;
    }
}
