// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.terminal.impl;

import java.io.IOException;
import java.io.IOError;
import java.io.EOFException;
import org.jline.utils.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.IntSupplier;
import org.jline.terminal.MouseEvent;
import org.jline.utils.InfoCmp;
import org.jline.terminal.Terminal;

public class MouseSupport
{
    public static boolean hasMouseSupport(final Terminal terminal) {
        return terminal.getStringCapability(InfoCmp.Capability.key_mouse) != null;
    }
    
    public static boolean trackMouse(final Terminal terminal, final Terminal.MouseTracking tracking) {
        if (hasMouseSupport(terminal)) {
            switch (tracking) {
                case Off: {
                    terminal.writer().write("\u001b[?1000l\u001b[?1002l\u001b[?1003l\u001b[?1005l\u001b[?1006l\u001b[?1015l\u001b[?1016l");
                    break;
                }
                case Normal: {
                    terminal.writer().write("\u001b[?1005h\u001b[?1006h\u001b[?1000h");
                    break;
                }
                case Button: {
                    terminal.writer().write("\u001b[?1005h\u001b[?1006h\u001b[?1002h");
                    break;
                }
                case Any: {
                    terminal.writer().write("\u001b[?1005h\u001b[?1006h\u001b[?1003h");
                    break;
                }
            }
            terminal.flush();
            return true;
        }
        return false;
    }
    
    public static MouseEvent readMouse(final Terminal terminal, final MouseEvent last) {
        return readMouse(() -> readExt(terminal), last, null);
    }
    
    public static MouseEvent readMouse(final Terminal terminal, final MouseEvent last, final String prefix) {
        return readMouse(() -> readExt(terminal), last, prefix);
    }
    
    public static MouseEvent readMouse(final IntSupplier reader, final MouseEvent last) {
        return readMouse(reader, last, null);
    }
    
    public static MouseEvent readMouse(final IntSupplier reader, final MouseEvent last, final String prefix) {
        if (prefix != null && !prefix.isEmpty()) {
            if (prefix.equals("\u001b[<")) {
                final IntSupplier prefixReader = createReaderFromString("<");
                return readMouse(chainReaders(prefixReader, reader), last, null);
            }
            if (prefix.equals("\u001b[M")) {
                final IntSupplier prefixReader = createReaderFromString("M");
                return readMouse(chainReaders(prefixReader, reader), last, null);
            }
        }
        final int c = reader.getAsInt();
        if (c == 60) {
            return readMouseSGR(reader, last);
        }
        if (c >= 48 && c <= 57) {
            return readMouseURXVT(c, reader, last);
        }
        if (c != 77) {
            return readMouseX10(c - 32, reader, last);
        }
        final int cb = reader.getAsInt();
        final int cx = reader.getAsInt();
        final int cy = reader.getAsInt();
        if ((cx & 0x80) != 0x0 || (cy & 0x80) != 0x0) {
            return readMouseUTF8(cb, cx, cy, reader, last);
        }
        return readMouseX10(cb - 32, cx - 32 - 1, cy - 32 - 1, last);
    }
    
    private static MouseEvent readMouseX10(final int cb, final IntSupplier reader, final MouseEvent last) {
        final int cx = reader.getAsInt() - 32 - 1;
        final int cy = reader.getAsInt() - 32 - 1;
        return parseMouseEvent(cb, cx, cy, false, last);
    }
    
    private static MouseEvent readMouseX10(final int cb, final int cx, final int cy, final MouseEvent last) {
        return parseMouseEvent(cb, cx, cy, false, last);
    }
    
    private static MouseEvent readMouseUTF8(final int cb, final int cx, final int cy, final IntSupplier reader, final MouseEvent last) {
        int x = decodeUtf8Coordinate(cx, reader);
        int y = decodeUtf8Coordinate(cy, reader);
        --x;
        --y;
        return parseMouseEvent(cb - 32, x, y, false, last);
    }
    
    private static int decodeUtf8Coordinate(final int firstByte, final IntSupplier reader) {
        if ((firstByte & 0x80) == 0x0) {
            return firstByte - 32;
        }
        if ((firstByte & 0xE0) == 0xC0) {
            final int secondByte = reader.getAsInt();
            final int value = (firstByte & 0x1F) << 6 | (secondByte & 0x3F);
            return value - 32;
        }
        if ((firstByte & 0xF0) == 0xE0) {
            final int secondByte = reader.getAsInt();
            final int thirdByte = reader.getAsInt();
            final int value2 = (firstByte & 0xF) << 12 | (secondByte & 0x3F) << 6 | (thirdByte & 0x3F);
            return value2 - 32;
        }
        return firstByte - 32;
    }
    
    private static MouseEvent readMouseSGR(final IntSupplier reader, final MouseEvent last) {
        final StringBuilder sb = new StringBuilder();
        final int[] params = new int[3];
        int paramIndex = 0;
        final boolean isPixels = false;
        boolean isRelease = false;
        int c;
        while ((c = reader.getAsInt()) != -1) {
            if (c == 77 || c == 109) {
                isRelease = (c == 109);
                break;
            }
            if (c == 59) {
                if (paramIndex >= params.length) {
                    continue;
                }
                try {
                    params[paramIndex++] = Integer.parseInt(sb.toString());
                }
                catch (final NumberFormatException e) {
                    params[paramIndex++] = 0;
                }
                sb.setLength();
            }
            else {
                if (c < 48 || c > 57) {
                    continue;
                }
                sb.append((char)c);
            }
        }
        if (sb.length() > 0 && paramIndex < params.length) {
            try {
                params[paramIndex] = Integer.parseInt(sb.toString());
            }
            catch (final NumberFormatException e) {
                params[paramIndex] = 0;
            }
        }
        final int cb = params[0];
        final int cx = params[1] - 1;
        final int cy = params[2] - 1;
        return parseMouseEvent(cb, cx, cy, isRelease, last);
    }
    
    private static MouseEvent readMouseURXVT(final int firstDigit, final IntSupplier reader, final MouseEvent last) {
        final StringBuilder sb = new StringBuilder().append((char)firstDigit);
        final int[] params = new int[3];
        int paramIndex = 0;
        int c;
        while ((c = reader.getAsInt()) != -1 && c != 77) {
            if (c == 59) {
                if (paramIndex >= params.length) {
                    continue;
                }
                try {
                    params[paramIndex++] = Integer.parseInt(sb.toString());
                }
                catch (final NumberFormatException e) {
                    params[paramIndex++] = 0;
                }
                sb.setLength();
            }
            else {
                if (c < 48 || c > 57) {
                    continue;
                }
                sb.append((char)c);
            }
        }
        if (sb.length() > 0 && paramIndex < params.length) {
            try {
                params[paramIndex] = Integer.parseInt(sb.toString());
            }
            catch (final NumberFormatException e) {
                params[paramIndex] = 0;
            }
        }
        final int cb = params[0];
        final int cx = params[1] - 1;
        final int cy = params[2] - 1;
        return parseMouseEvent(cb, cx, cy, false, last);
    }
    
    private static MouseEvent parseMouseEvent(final int cb, final int cx, final int cy, final boolean isRelease, final MouseEvent last) {
        final EnumSet<MouseEvent.Modifier> modifiers = EnumSet.noneOf(MouseEvent.Modifier.class);
        if ((cb & 0x4) == 0x4) {
            modifiers.add(MouseEvent.Modifier.Shift);
        }
        if ((cb & 0x8) == 0x8) {
            modifiers.add(MouseEvent.Modifier.Alt);
        }
        if ((cb & 0x10) == 0x10) {
            modifiers.add(MouseEvent.Modifier.Control);
        }
        MouseEvent.Type type = null;
        MouseEvent.Button button = null;
        if ((cb & 0x40) == 0x40) {
            type = MouseEvent.Type.Wheel;
            button = (((cb & 0x1) == 0x1) ? MouseEvent.Button.WheelDown : MouseEvent.Button.WheelUp);
        }
        else if (isRelease) {
            button = getButtonForCode(cb & 0x3);
            type = MouseEvent.Type.Released;
        }
        else {
            final int b = cb & 0x3;
            switch (b) {
                case 0: {
                    button = MouseEvent.Button.Button1;
                    if (last.getButton() == button && (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
                        type = MouseEvent.Type.Dragged;
                        break;
                    }
                    type = MouseEvent.Type.Pressed;
                    break;
                }
                case 1: {
                    button = MouseEvent.Button.Button2;
                    if (last.getButton() == button && (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
                        type = MouseEvent.Type.Dragged;
                        break;
                    }
                    type = MouseEvent.Type.Pressed;
                    break;
                }
                case 2: {
                    button = MouseEvent.Button.Button3;
                    if (last.getButton() == button && (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged)) {
                        type = MouseEvent.Type.Dragged;
                        break;
                    }
                    type = MouseEvent.Type.Pressed;
                    break;
                }
                default: {
                    if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
                        button = last.getButton();
                        type = MouseEvent.Type.Released;
                        break;
                    }
                    button = MouseEvent.Button.NoButton;
                    type = MouseEvent.Type.Moved;
                    break;
                }
            }
        }
        return new MouseEvent(type, button, modifiers, cx, cy);
    }
    
    private static MouseEvent.Button getButtonForCode(final int code) {
        switch (code) {
            case 0: {
                return MouseEvent.Button.Button1;
            }
            case 1: {
                return MouseEvent.Button.Button2;
            }
            case 2: {
                return MouseEvent.Button.Button3;
            }
            default: {
                return MouseEvent.Button.NoButton;
            }
        }
    }
    
    public static String[] keys() {
        return new String[] { "\u001b[<", "\u001b[M" };
    }
    
    public static String[] keys(final Terminal terminal) {
        final String keyMouse = terminal.getStringCapability(InfoCmp.Capability.key_mouse);
        if (keyMouse == null) {
            return keys();
        }
        if (Arrays.asList(keys()).contains(keyMouse)) {
            return keys();
        }
        return new String[] { keyMouse, "\u001b[<", "\u001b[M" };
    }
    
    private static int readExt(final Terminal terminal) {
        try {
            int c;
            if (terminal.encoding() != StandardCharsets.UTF_8) {
                c = new InputStreamReader(terminal.input(), StandardCharsets.UTF_8).read();
            }
            else {
                c = terminal.reader().read();
            }
            if (c < 0) {
                throw new EOFException();
            }
            return c;
        }
        catch (final IOException e) {
            throw new IOError(e);
        }
    }
    
    private static IntSupplier createReaderFromString(final String s) {
        final int[] chars = s.chars().toArray();
        final int[] index = { 0 };
        return () -> {
            if (index[0] < chars.length) {
                return chars[index[0]++];
            }
            else {
                return -1;
            }
        };
    }
    
    private static IntSupplier chainReaders(final IntSupplier first, final IntSupplier second) {
        return new IntSupplier() {
            private boolean firstExhausted = false;
            
            @Override
            public int getAsInt() {
                if (!this.firstExhausted) {
                    final int c = first.getAsInt();
                    if (c != -1) {
                        return c;
                    }
                    this.firstExhausted = true;
                }
                return second.getAsInt();
            }
        };
    }
}
