/*
 * Copyright (c) 2002-2016, the original author(s).
 *
 * This software is distributable under the BSD license. See the terms of the
 * BSD license in the documentation provided with this software.
 *
 * https://opensource.org/licenses/BSD-3-Clause
 */
package org.jline.terminal.impl;

import java.io.EOFException;
import java.io.IOError;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.EnumSet;
import java.util.function.IntSupplier;

import org.jline.terminal.MouseEvent;
import org.jline.terminal.Terminal;
import org.jline.utils.InfoCmp;
import org.jline.utils.InputStreamReader;

public class MouseSupport {

    public static boolean hasMouseSupport(Terminal terminal) {
        return terminal.getStringCapability(InfoCmp.Capability.key_mouse) != null;
    }

    public static boolean trackMouse(Terminal terminal, Terminal.MouseTracking tracking) {
        if (hasMouseSupport(terminal)) {
            switch (tracking) {
                case Off:
                    terminal.writer().write("\033[?1000l");
                    break;
                case Normal:
                    terminal.writer().write("\033[?1005h\033[?1000h");
                    break;
                case Button:
                    terminal.writer().write("\033[?1005h\033[?1002h");
                    break;
                case Any:
                    terminal.writer().write("\033[?1005h\033[?1003h");
                    break;
            }
            terminal.flush();
            return true;
        } else {
            return false;
        }
    }

    public static MouseEvent readMouse(Terminal terminal, MouseEvent last) {
        return readMouse(() -> readExt(terminal), last);
    }

    public static MouseEvent readMouse(IntSupplier reader, MouseEvent last) {
        int cb = reader.getAsInt() - ' ';
        int cx = reader.getAsInt() - ' ' - 1;
        int cy = reader.getAsInt() - ' ' - 1;
        MouseEvent.Type type;
        MouseEvent.Button button;
        EnumSet<MouseEvent.Modifier> modifiers = EnumSet.noneOf(MouseEvent.Modifier.class);
        if ((cb & 4) == 4) {
            modifiers.add(MouseEvent.Modifier.Shift);
        }
        if ((cb & 8) == 8) {
            modifiers.add(MouseEvent.Modifier.Alt);
        }
        if ((cb & 16) == 16) {
            modifiers.add(MouseEvent.Modifier.Control);
        }
        if ((cb & 64) == 64) {
            type = MouseEvent.Type.Wheel;
            button = (cb & 1) == 1 ? MouseEvent.Button.WheelDown : MouseEvent.Button.WheelUp;
        } else {
            int b = (cb & 3);
            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;
                    } else {
                        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;
                    } else {
                        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;
                    } else {
                        type = MouseEvent.Type.Pressed;
                    }
                    break;
                default:
                    if (last.getType() == MouseEvent.Type.Pressed || last.getType() == MouseEvent.Type.Dragged) {
                        button = last.getButton();
                        type = MouseEvent.Type.Released;
                    } else {
                        button = MouseEvent.Button.NoButton;
                        type = MouseEvent.Type.Moved;
                    }
                    break;
            }
        }
        return new MouseEvent(type, button, modifiers, cx, cy);
    }

    private static int readExt(Terminal terminal) {
        try {
            // The coordinates are encoded in UTF-8, so if that's not the input encoding,
            // we need to get around
            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 (IOException e) {
            throw new IOError(e);
        }
    }
}
