package com.genymobile.scrcpy;

import android.graphics.Point;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;

import com.genymobile.scrcpy.wrappers.InputManager;

import java.io.IOException;

public class EventController {

    private final Device device;
    private final InputManager inputManager;
    private final DesktopConnection connection;

    private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);

    private long lastMouseDown;
    private final MotionEvent.PointerProperties[] pointerProperties = { new MotionEvent.PointerProperties() };
    private final MotionEvent.PointerCoords[] pointerCoords = { new MotionEvent.PointerCoords() };

    public EventController(Device device, DesktopConnection connection) {
        this.device = device;
        this.connection = connection;
        inputManager = device.getInputManager();
        initPointer();
    }

    private void initPointer() {
        MotionEvent.PointerProperties props = pointerProperties[0];
        props.id = 0;
        props.toolType = MotionEvent.TOOL_TYPE_FINGER;

        MotionEvent.PointerCoords coords = pointerCoords[0];
        coords.orientation = 0;
        coords.pressure = 1;
        coords.size = 1;
        coords.toolMajor = 1;
        coords.toolMinor = 1;
        coords.touchMajor = 1;
        coords.touchMinor = 1;
    }

    private void setPointerCoords(Point point) {
        MotionEvent.PointerCoords coords = pointerCoords[0];
        coords.x = point.x;
        coords.y = point.y;
    }

    private void setScroll(int hScroll, int vScroll) {
        MotionEvent.PointerCoords coords = pointerCoords[0];
        coords.setAxisValue(MotionEvent.AXIS_SCROLL, hScroll);
        coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
    }

    public void control() throws IOException {
        while (handleEvent());
    }

    private boolean handleEvent() throws IOException {
        ControlEvent controlEvent = connection.receiveControlEvent();
        if (controlEvent == null) {
            return false;
        }
        switch (controlEvent.getType()) {
            case ControlEvent.TYPE_KEYCODE:
                injectKeycode(controlEvent.getAction(), controlEvent.getKeycode(), controlEvent.getMetaState());
                break;
            case ControlEvent.TYPE_TEXT:
                injectText(controlEvent.getText());
                break;
            case ControlEvent.TYPE_MOUSE:
                injectMouse(controlEvent.getAction(), controlEvent.getButtons(), controlEvent.getPosition());
                break;
            case ControlEvent.TYPE_SCROLL:
                injectScroll(controlEvent.getPosition(), controlEvent.getHScroll(), controlEvent.getVScroll());
        }
        return true;
    }

    private boolean injectKeycode(int action, int keycode, int metaState) {
        return injectKeyEvent(action, keycode, 0, metaState);
    }

    private boolean injectText(String text) {
        KeyEvent[] events = charMap.getEvents(text.toCharArray());
        if (events == null) {
            return false;
        }
        for (KeyEvent event : events) {
            if (!injectEvent(event)) {
                return false;
            }
        }
        return true;
    }

    private boolean injectMouse(int action, int buttons, Position position) {
        long now = SystemClock.uptimeMillis();
        if (action == MotionEvent.ACTION_DOWN) {
            lastMouseDown = now;
        }
        Point point = device.getPhysicalPoint(position);
        if (point == null) {
            // ignore event
            return false;
        }
        setPointerCoords(point);
        MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
        return injectEvent(event);
    }

    private boolean injectScroll(Position position, int hScroll, int vScroll) {
        long now = SystemClock.uptimeMillis();
        Point point = device.getPhysicalPoint(position);
        if (point == null) {
            // ignore event
            return false;
        }
        setPointerCoords(point);
        setScroll(hScroll, vScroll);
        MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
        return injectEvent(event);
    }

    private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
        long now = SystemClock.uptimeMillis();
        KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
        return injectEvent(event);
    }

    private boolean injectEvent(InputEvent event) {
        return inputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    }
}

@@ -694,25 +694,24 @@
 operties = %7B
- 
 new MotionEv
@@ -733,17 +733,16 @@
 erties()
- 
 %7D;%0A    p
@@ -799,17 +799,16 @@
 ords = %7B
- 
 new Moti
@@ -830,17 +830,16 @@
 Coords()
- 
 %7D;%0A%0A    
@@ -2005,24 +2005,25 @@
 ndleEvent())
+ 
 ;%0A    %7D%0A%0A   
@@ -3936,32 +3936,48 @@
 s, 1f, 1f, 0, 0,
+%0A               
  InputDevice.SOU
@@ -4529,24 +4529,40 @@
 , 1f, 1f, 0,
+%0A               
  0, InputDev
@@ -4753,32 +4753,32 @@
 uptimeMillis();%0A
-
         KeyEvent
@@ -4884,16 +4884,32 @@
 D, 0, 0,
+%0A               
  InputDe

Reformat EventController