import tkinter as tk
from tkinter import ttk, messagebox
import threading
import subprocess
import time
import os
import sys
import re


def get_adb_path():
    """Get the path to the bundled adb.exe.

    Looks for ./adb/adb.exe relative to the script/executable location,
    so the folder is fully portable. Falls back to system adb if not found.
    """
    if getattr(sys, 'frozen', False):
        # Running as PyInstaller exe
        base_dir = os.path.dirname(sys.executable)
    else:
        # Running as script
        base_dir = os.path.dirname(os.path.abspath(__file__))

    local_adb = os.path.join(base_dir, "adb", "adb.exe")
    if os.path.isfile(local_adb):
        return local_adb

    # Fallback: adb in PATH (system-installed)
    return "adb"


ADB_PATH = get_adb_path()


class ADBSwiper:
    def __init__(self, root):
        self.root = root
        self.root.title("ADB 自动滑动工具")
        self.root.geometry("420x600")
        self.root.resizable(False, False)

        self.running = False
        self.thread = None
        self.stop_event = threading.Event()

        # Screen resolution (will be auto-detected)
        self.screen_w = 2400
        self.screen_h = 1080

        self._build_ui()
        self._detect_device()

    def _build_ui(self):
        # === Device Info Frame ===
        info_frame = ttk.LabelFrame(self.root, text="设备信息")
        info_frame.pack(fill="x", padx=10, pady=(10, 5))

        self.lbl_device = ttk.Label(info_frame, text="检测中...", foreground="gray")
        self.lbl_device.pack(side="left", padx=10, pady=8)

        btn_refresh = ttk.Button(info_frame, text="刷新", width=6, command=self._detect_device)
        btn_refresh.pack(side="right", padx=10)

        # === Parameters Frame ===
        param_frame = ttk.LabelFrame(self.root, text="滑动参数")
        param_frame.pack(fill="x", padx=10, pady=5)

        # Frequency
        ttk.Label(param_frame, text="频率 (次/秒):").grid(row=0, column=0, sticky="w", padx=10, pady=8)
        self.var_freq = tk.StringVar(value="2")
        spin_freq = ttk.Spinbox(param_frame, from_=1, to=50, width=8, textvariable=self.var_freq)
        spin_freq.grid(row=0, column=1, padx=10, pady=8, sticky="w")

        # Intensity
        ttk.Label(param_frame, text="强度 (像素):").grid(row=1, column=0, sticky="w", padx=10, pady=8)
        self.var_intensity = tk.StringVar(value="100")
        spin_intensity = ttk.Spinbox(param_frame, from_=10, to=500, increment=10, width=8,
                                     textvariable=self.var_intensity)
        spin_intensity.grid(row=1, column=1, padx=10, pady=8, sticky="w")

        # Touch mode (pointer / motionevent / swipe)
        ttk.Label(param_frame, text="触摸模式:").grid(row=2, column=0, sticky="w", padx=10, pady=8)
        self.var_mode = tk.StringVar(value="pointer")
        mode_combo = ttk.Combobox(param_frame, textvariable=self.var_mode, width=12,
                                  state="readonly",
                                  values=["pointer", "motionevent", "swipe"])
        mode_combo.grid(row=2, column=1, padx=10, pady=8, sticky="w")

        # Pointer ID (for pointer/motionevent mode)
        ttk.Label(param_frame, text="触摸 ID:").grid(row=3, column=0, sticky="w", padx=10, pady=8)
        self.var_pointer_id = tk.StringVar(value="5")
        spin_pid = ttk.Spinbox(param_frame, from_=1, to=20, width=8,
                               textvariable=self.var_pointer_id)
        spin_pid.grid(row=3, column=1, padx=10, pady=8, sticky="w")

        # Mode help
        self.lbl_mode_help = ttk.Label(
            param_frame,
            text="pointer=不断触(推荐) motionevent=会断触 swipe=最兼容",
            foreground="gray", font=("Arial", 8))
        self.lbl_mode_help.grid(row=4, column=0, columnspan=2, sticky="w", padx=10, pady=(0, 4))

        # Position preview
        self.lbl_pos = ttk.Label(param_frame, text="滑动起点: 自动计算", foreground="blue")
        self.lbl_pos.grid(row=5, column=0, columnspan=2, sticky="w", padx=10, pady=8)

        # === Control Frame ===
        ctrl_frame = ttk.Frame(self.root)
        ctrl_frame.pack(fill="x", padx=10, pady=10)

        self.btn_start = ttk.Button(ctrl_frame, text="▶ 开始", command=self._start)
        self.btn_start.pack(side="left", padx=10, pady=5, expand=True, fill="x")

        self.btn_stop = ttk.Button(ctrl_frame, text="■ 停止", command=self._stop,
                                    state="disabled")
        self.btn_stop.pack(side="left", padx=10, pady=5, expand=True, fill="x")

        # === Status Frame ===
        status_frame = ttk.LabelFrame(self.root, text="运行状态")
        status_frame.pack(fill="both", expand=True, padx=10, pady=5)

        self.lbl_status = ttk.Label(status_frame, text="就绪", foreground="gray", font=("Arial", 10, "bold"))
        self.lbl_status.pack(pady=5)

        self.text_log = tk.Text(status_frame, height=8, width=48, state="disabled",
                                 font=("Consolas", 9))
        self.text_log.pack(fill="both", expand=True, padx=5, pady=5)

        # === Count ===
        self.lbl_count = ttk.Label(self.root, text="已执行: 0 次", foreground="gray")
        self.lbl_count.pack(pady=(0, 10))

    def _log(self, msg):
        """Append a message to the log area (thread-safe)."""
        def _append():
            self.text_log.config(state="normal")
            self.text_log.insert("end", msg + "\n")
            self.text_log.see("end")
            self.text_log.config(state="disabled")
        self.root.after(0, _append)

    def _run_adb(self, cmd):
        """Run an ADB command and return stdout."""
        # Quote the adb path in case of spaces, then append the command
        full_cmd = f'"{ADB_PATH}" {cmd}'
        try:
            result = subprocess.run(
                full_cmd, shell=True, capture_output=True,
                text=True, timeout=10
            )
            return result.stdout.strip(), result.stderr.strip(), result.returncode
        except Exception as e:
            return "", str(e), -1

    def _get_rotation(self):
        """Detect current screen rotation angle (0, 90, 180, 270).

        Uses multiple strategies for reliability across Android versions.
        Returns the rotation angle in degrees.
        """
        # Strategy 1: dumpsys input (most reliable)
        out, err, rc = self._run_adb("shell dumpsys input")
        if rc == 0 and out:
            match = re.search(r'ROTATION_(\d+)', out)
            if match:
                return int(match.group(1))
            match = re.search(r'[Rr]otation[=:]\s*(\d+)', out)
            if match:
                rot = int(match.group(1))
                # Some devices map 0/1/2/3 to rotation
                rotation_map = {0: 0, 1: 90, 2: 180, 3: 270}
                return rotation_map.get(rot, rot * 90)

        # Strategy 2: dumpsys window displays
        out, err, rc = self._run_adb("shell dumpsys window displays")
        if rc == 0 and out:
            match = re.search(r'ROTATION_(\d+)', out)
            if match:
                return int(match.group(1))

        # Strategy 3: settings (simplest)
        out, err, rc = self._run_adb("shell settings get system user_rotation")
        if rc == 0 and out and out.strip():
            try:
                rot = int(out.strip())
                rotation_map = {0: 0, 1: 90, 2: 180, 3: 270}
                return rotation_map.get(rot, 0)
            except ValueError:
                pass

        # Fallback: assume portrait
        return 0

    def _detect_device(self):
        """Detect ADB device and screen resolution."""
        self.lbl_device.config(text="检测中...", foreground="gray")
        self._log(f"ADB 路径: {ADB_PATH}")
        self._log("正在检测 ADB 设备...")

        stdout, stderr, rc = self._run_adb("devices")
        if rc != 0:
            self.lbl_device.config(text=f"ADB 错误: {stderr}", foreground="red")
            self._log(f"ADB 命令失败: {stderr}")
            self._update_pos_label()
            return

        lines = [l.strip() for l in stdout.splitlines() if l.strip()]
        device_lines = [l for l in lines if l and "List of devices" not in l]

        if not device_lines:
            self.lbl_device.config(text="未检测到设备", foreground="orange")
            self._log("⚠ 未检测到 ADB 设备，请连接设备并开启 USB 调试")
            self._log(f"将使用默认分辨率 {self.screen_w}x{self.screen_h}")
            self._update_pos_label()
            return

        # Check state
        for dl in device_lines:
            parts = dl.split("\t")
            if len(parts) >= 2 and parts[1] == "device":
                # Get resolution
                out, err, rc2 = self._run_adb("shell wm size")
                if rc2 == 0 and out:
                    try:
                        # Parse: Physical size: 1080x2400
                        size_str = out.split("Physical size:")[1].strip().split("\n")[0].strip()
                        physical_w, physical_h = size_str.split("x")
                        physical_w = int(physical_w)
                        physical_h = int(physical_h)

                        # Detect rotation and swap if needed
                        rotation = self._get_rotation()
                        self._log(f"检测到屏幕旋转: {rotation}°")

                        if rotation in (90, 270):
                            # Landscape: swap width and height
                            self.screen_w = physical_h
                            self.screen_h = physical_w
                            self._log(f"✓ 横屏模式: {physical_w}x{physical_h} → 有效 {self.screen_w}x{self.screen_h}")
                            orient_label = "横屏"
                        else:
                            self.screen_w = physical_w
                            self.screen_h = physical_h
                            self._log(f"✓ 竖屏模式: {self.screen_w}x{self.screen_h}")
                            orient_label = "竖屏"

                        self.lbl_device.config(
                            text=f"已连接 ({self.screen_w}x{self.screen_h} {orient_label})",
                            foreground="green"
                        )
                    except Exception:
                        self.lbl_device.config(
                            text=f"已连接 (默认 {self.screen_w}x{self.screen_h})",
                            foreground="green"
                        )
                        self._log(f"✓ 设备已连接，使用默认分辨率 {self.screen_w}x{self.screen_h}")
                else:
                    self.lbl_device.config(
                        text=f"已连接 (默认 {self.screen_w}x{self.screen_h})",
                        foreground="green"
                    )
                    self._log(f"✓ 设备已连接，使用默认分辨率")
                self._update_pos_label()
                return

        self.lbl_device.config(text=f"设备离线 ({device_lines[0]})", foreground="red")
        self._log(f"⚠ 设备状态异常: {device_lines[0]}")
        self._update_pos_label()

    def _update_pos_label(self):
        """Update the position preview label."""
        x, y = self._calc_swipe_start()
        intensity = self._get_intensity()
        self.lbl_pos.config(
            text=f"起点: ({x}, {y})  终点: ({x}, {y + intensity})"
        )

    def _calc_swipe_start(self):
        """Calculate swipe start position: middle-right, slightly above center."""
        # x: 3/5 of screen width (slightly right of center)
        # y: 2/5 of screen height (slightly above center)
        x = int(self.screen_w * 3 / 5)
        y = int(self.screen_h * 2 / 5)
        return x, y

    def _get_frequency(self):
        try:
            return max(1, min(50, int(self.var_freq.get())))
        except ValueError:
            return 2

    def _get_intensity(self):
        try:
            return max(10, min(2000, int(self.var_intensity.get())))
        except ValueError:
            return 100

    def _get_mode(self):
        return self.var_mode.get()

    def _get_pointer_id(self):
        try:
            return max(1, min(20, int(self.var_pointer_id.get())))
        except ValueError:
            return 5

    def _start(self):
        """Start the swipe loop."""
        if self.running:
            return

        self._detect_device()

        freq = self._get_frequency()
        intensity = self._get_intensity()
        mode = self._get_mode()
        pid = self._get_pointer_id()
        interval = 1.0 / freq

        self.running = True
        self.stop_event.clear()
        self.btn_start.config(state="disabled")
        self.btn_stop.config(state="normal")
        self.lbl_status.config(text="● 运行中", foreground="green")
        self._log(f"▶ 开始滑动 | 频率: {freq} 次/秒 | 强度: {intensity} 像素 | 模式: {mode}")
        if mode == "pointer":
            self._log(f"   pointer: POINTER_DOWN/UP (action=5/6) 不清除已有触摸点")
        elif mode == "motionevent":
            self._log(f"   motionevent: DOWN/UP (action=0/1) 会清除其他触摸点")

        self.thread = threading.Thread(
            target=self._swipe_loop,
            args=(interval, intensity, mode, pid),
            daemon=True
        )
        self.thread.start()

    def _stop(self):
        """Stop the swipe loop."""
        if not self.running:
            return

        self.stop_event.set()
        self.running = False
        self.btn_start.config(state="normal")
        self.btn_stop.config(state="disabled")
        self.lbl_status.config(text="■ 已停止", foreground="orange")
        self._log("■ 滑动已停止")

    def _execute_swipe(self, x1, y1, x2, y2, mode, pid):
        """Execute one swipe operation. Returns (success, error_msg).

        mode='swipe':        input swipe (ACTION_DOWN cancels all existing touches)
        mode='motionevent':  motionevent DOWN/MOVE/UP with pointer id
                             (ACTION_DOWN still cancels existing touches)
        mode='pointer':      motionevent POINTER_DOWN/MOVE/POINTER_UP with pointer id
                             (ACTION_POINTER_DOWN adds a touch point WITHOUT clearing
                             existing touches — does NOT interrupt finger-held keys)
        """
        if mode == "swipe":
            cmd = f'shell input swipe {x1} {y1} {x2} {y2} 30'
            out, err, rc = self._run_adb(cmd)
            if rc != 0:
                return False, err or out
            return True, ""

        # Build the 3-step motionevent sequence based on mode
        if mode == "pointer":
            down_action, up_action = "POINTER_DOWN", "POINTER_UP"
        else:  # motionevent
            down_action, up_action = "DOWN", "UP"

        # Standard syntax: input touchscreen motionevent <ACTION> <x> <y>
        # (pointer_id is NOT part of the standard command — including it shifts
        #  x/y and corrupts coordinates. We rely on ACTION_POINTER_DOWN/UP which
        #  adds/removes a touch point without clearing existing touches.)
        cmds = [
            f'shell input touchscreen motionevent {down_action} {x1} {y1}',
            f'shell input touchscreen motionevent MOVE {x2} {y2}',
            f'shell input touchscreen motionevent {up_action} {x2} {y2}',
        ]
        for cmd in cmds:
            out, err, rc = self._run_adb(cmd)
            if rc != 0:
                return False, f"{cmd} -> {err or out}"
        return True, ""

    def _swipe_loop(self, interval, intensity, mode, pid):
        """Main swipe loop running in a background thread.

        Implements a 3-level fallback chain:
          pointer (POINTER_DOWN, no interrupt) ->
          motionevent (DOWN, may interrupt) ->
          swipe (always works, interrupts)
        Once a mode succeeds it becomes the working mode for subsequent iterations.
        """
        count = 0
        x, y = self._calc_swipe_start()
        x2, y2 = x, y + intensity
        logged_first = False

        while not self.stop_event.is_set():
            if self.stop_event.is_set():
                break

            success, err = self._execute_swipe(x, y, x2, y2, mode, pid)

            if not success:
                # Try falling back to a more compatible mode
                fallback = {"pointer": "motionevent",
                            "motionevent": "swipe"}.get(mode)
                if fallback:
                    self._log(f"⚠ {mode} 失败, 降级到 {fallback}: {err}")
                    mode = fallback
                    success, err = self._execute_swipe(x, y, x2, y2, mode, pid)
                    if not success:
                        self._log(f"⚠ ADB 错误: {err}")
                        self.root.after(0, self._stop)
                        break
                else:
                    self._log(f"⚠ ADB 错误: {err}")
                    self.root.after(0, self._stop)
                    break

            if not logged_first:
                if mode == "pointer":
                    self._log(f"命令: motionevent POINTER_DOWN/MOVE/POINTER_UP ({x},{y})->({x2},{y2})")
                elif mode == "motionevent":
                    self._log(f"命令: motionevent DOWN/MOVE/UP ({x},{y})->({x2},{y2})")
                else:
                    self._log(f"命令: input swipe {x} {y} {x2} {y2} 30")
                logged_first = True

            count += 1
            self.root.after(0, lambda c=count: self.lbl_count.config(text=f"已执行: {c} 次"))

            self.stop_event.wait(interval)

        self.running = False
        self.root.after(0, self._on_loop_done)

    def _on_loop_done(self):
        """Called from main thread when loop finishes."""
        self.btn_start.config(state="normal")
        self.btn_stop.config(state="disabled")
        self.lbl_status.config(text="已停止", foreground="gray")
        self._log("循环结束")


def main():
    root = tk.Tk()
    app = ADBSwiper(root)
    root.protocol("WM_DELETE_WINDOW", lambda: (app.stop_event.set(), root.destroy()))
    root.mainloop()


if __name__ == "__main__":
    main()
