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

public class PointerEvent extends InputEvent
{
    public static final int BUTTON_MAIN       =  0;
    public static final int BUTTON_AUX_1      =  1;
    public static final int BUTTON_AUX_2      =  2;
    public static final int BUTTON_AUX_3      =  3;
    public static final int BUTTON_AUX_4      =  4;
    public static final int BUTTON_AUX_5      =  5;
    public static final int BUTTON_AUX_6      =  6;
    public static final int BUTTON_AUX_7      =  7;
    public static final int BUTTON_AUX_8      =  8;
    public static final int BUTTON_AUX_9      =  9;
    public static final int BUTTON_AUX_10     = 10;
    public static final int BUTTON_AUX_11     = 11;
    public static final int BUTTON_AUX_12     = 12;
    public static final int BUTTON_WHEEL      = 13;
    public static final int BUTTON_WHEEL_UP   = 14;
    public static final int BUTTON_WHEEL_DOWN = 15;
    public static final int ACTION_POINTER_PRESSED  = Requestable.ACTION_POINTER_PRESSED;
    public static final int ACTION_POINTER_DRAGGED  = Requestable.ACTION_POINTER_DRAGGED;
    public static final int ACTION_POINTER_RELEASED = Requestable.ACTION_POINTER_RELEASED;
    public static final int ACTION_BUTTON_PRESSED   = Requestable.ACTION_BUTTON_PRESSED;
    public static final int ACTION_BUTTON_RELEASED  = Requestable.ACTION_BUTTON_RELEASED;
    private static final int OFFSET_BUTTON          = 0x01;
    private static final int OFFSET_COORDS          = 0x02;
    private static final int OFFSET_SOURCE          = 0x03;
    private static final int OFFSET_EVENT_TIME_LO   = 0x04;
    private static final int OFFSET_EVENT_TIME_HI   = 0x05;
    private static final int OFFSET_VIRTUAL_DATA_LO = 0x06;
    private static final int OFFSET_VIRTUAL_DATA_HI = 0x07;
    private static final int ELEMENTS_PER_STORY = 0x08;

    private int tx;
    private int ty;
    private int base;
    private int length;
    private final int[] history;

    public PointerEvent() {
        this(0x0100);
    }

    public PointerEvent(int historyCapacity) {
        if(historyCapacity < 0x0002) historyCapacity = 0x0002;
        if(historyCapacity > 0x1000) historyCapacity = 0x1000;
        this.base = (historyCapacity <<= 3) - ELEMENTS_PER_STORY;
        this.length = ELEMENTS_PER_STORY;
        this.history = new int[historyCapacity];
    }

    public int getSource() {
        return history[base + OFFSET_SOURCE];
    }

    public long getEventTime() {
        int b = base;
        int[] h = history;
        return (long) h[b + OFFSET_EVENT_TIME_LO] & 0x00000000ffffffffL | (long) h[b + OFFSET_EVENT_TIME_HI] << 32;
    }

    public long getVirtualData() {
        int b = base;
        int[] h = history;
        return (long) h[b + OFFSET_VIRTUAL_DATA_LO] & 0x00000000ffffffffL | (long) h[b + OFFSET_VIRTUAL_DATA_HI] << 32;
    }

    public void addStory(int action, int button, int rawX, int rawY, InputDevice device, int source, long virtualData) {
        int[] history;
        if(action == ACTION_BUTTON_PRESSED) action = ACTION_POINTER_PRESSED;
        if(action == ACTION_BUTTON_RELEASED) action = ACTION_POINTER_RELEASED;
        if(action != ACTION_POINTER_DRAGGED && action != ACTION_POINTER_PRESSED && action != ACTION_POINTER_RELEASED)
        {
            throw new IllegalArgumentException("KeyboardEvent.addStory: аргумент action имеет недопустимое значение.");
        }
        synchronized(history = this.history)
        {
            int capacity = history.length;
            int oldBase = base;
            int newBase = base = (oldBase + capacity - ELEMENTS_PER_STORY) % capacity;
            int length = this.length;
            long time = System.currentTimeMillis();
            if(length < capacity) this.length = length + ELEMENTS_PER_STORY;
            history[newBase] = history[oldBase] & 0xffff0000;
            if(action == ACTION_POINTER_DRAGGED)
            {
                button = -1;
            }
            else if(button >= 0x00 && button <= 0x0f)
            {
                int index = newBase;
                int bitbutton = 1 << (button + 0x10);
                switch(action)
                {
                case ACTION_POINTER_PRESSED:
                    if(((history[index] |= bitbutton) & ~bitbutton) != 0) action = ACTION_BUTTON_PRESSED;
                    break;
                case ACTION_POINTER_RELEASED:
                    if((history[index] &= ~bitbutton) != 0) action = ACTION_BUTTON_RELEASED;
                    break;
                }
            }
            this.device = device;
            history[newBase] = history[newBase] & 0xffff0000 | action;
            history[newBase + OFFSET_BUTTON] = button;
            history[newBase + OFFSET_COORDS] = rawX & 0xffff | rawY << 0x0010;
            history[newBase + OFFSET_SOURCE] = source;
            history[newBase + OFFSET_EVENT_TIME_LO] = (int) time;
            history[newBase + OFFSET_EVENT_TIME_HI] = (int) (time >> 32);
            history[newBase + OFFSET_VIRTUAL_DATA_LO] = (int) virtualData;
            history[newBase + OFFSET_VIRTUAL_DATA_HI] = (int) (virtualData >> 32);
        }
    }

    public void translateReset() {
        tx = 0;
        ty = 0;
    }

    public void translate(int deltaX, int deltaY) {
        tx += deltaX;
        ty += deltaY;
    }

    public void translateTo(int rawX, int rawY) {
        tx = rawX;
        ty = rawY;
    }

    public boolean historicalFromSource(int storyIndex, int source) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalFromSource: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (h[b + OFFSET_SOURCE] & source) == source;
    }

    public int historicalSource(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalSource: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return h[b + OFFSET_SOURCE];
    }

    public int getTranslateX() {
        return tx;
    }

    public int getTranslateY() {
        return ty;
    }

    public long historicalEventTime(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalEventTime: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (long) h[b + OFFSET_EVENT_TIME_LO] & 0x00000000ffffffffL | (long) h[b + OFFSET_EVENT_TIME_HI] << 32;
    }

    public long historicalVirtualData(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalVirtualData: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (long) h[b + OFFSET_VIRTUAL_DATA_LO] & 0x00000000ffffffffL | (long) h[b + OFFSET_VIRTUAL_DATA_HI] << 32;
    }

    public final boolean isButtonPressed(int button) {
        return button >= 0x00 && button <= 0x0f && (history[base] & (1 << (button + 0x10))) != 0;
    }

    public final boolean historicalButtonPressed(int storyIndex, int button) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalButtonPressed: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return button >= 0x00 && button <= 0x0f && (h[b] & (1 << (button + 0x10))) != 0;
    }

    public final int historicalButton(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalButton: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return h[b + OFFSET_BUTTON];
    }

    public final int historicalAction(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalAction: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return h[b] & 0xffff;
    }

    public final int historicalRawX(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalRawX: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (short) h[b + OFFSET_COORDS];
    }

    public final int historicalRawY(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalRawY: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (h[b + OFFSET_COORDS] >> 16);
    }

    public final int historicalX(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalX: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (short) h[b + OFFSET_COORDS] - tx;
    }

    public final int historicalY(int storyIndex) {
        int b;
        int[] h;
        if(storyIndex < 0 || storyIndex >= (length >> 3))
        {
            throw new IndexOutOfBoundsException("PointerEvent.historicalY: аргумент storyIndex выходит из диапазона.");
        }
        h = history;
        b = (base + (storyIndex << 3)) % h.length;
        return (h[b + OFFSET_COORDS] >> 16) - ty;
    }

    public final int historyCapacity() {
        return history.length >> 3;
    }

    public final int historyLength() {
        return length >> 3;
    }

    public final int getButton() {
        return history[base + OFFSET_BUTTON];
    }

    public final int getAction() {
        return history[base] & 0xffff;
    }

    public final int getRawX() {
        return (short) history[base + OFFSET_COORDS];
    }

    public final int getRawY() {
        return (history[base + OFFSET_COORDS] >> 16);
    }

    public final int getX() {
        return (short) history[base + OFFSET_COORDS] - tx;
    }

    public final int getY() {
        return (history[base + OFFSET_COORDS] >> 16) - ty;
    }
}
