import time
import serial
import pygame


class RobotDriver:
    def __init__(self, port="COM9", baud=115200):
        self.ser = None
        try:
            self.ser = serial.Serial(port, baud, timeout=0.2)
            print(f"[System] Robot connected on {port} @ {baud}")
            time.sleep(2.0)
        except Exception as e:
            print(f"[Error] Robot connection failed: {e}")

    def _send(self, cmd):
        if self.ser is None:
            return
        packet = f"{cmd}\r\n"
        self.ser.write(packet.encode("ascii"))
        self.ser.flush()

    def motor_open(self):
        self._send("M:OPEN")

    def motor_close(self):
        self._send("M:CLOSE")

    def motor_stop(self):
        self._send("M:STOP")

    def set_servo(self, servo_id, angle):
        self._send(f"S{servo_id}:{angle}")

    def set_config(self, mode):
        # 与原脚本保持一致的三种构型
        if mode == 0:
            self.set_servo(1, 90)
            self.set_servo(2, 90)
        elif mode == 1:
            self.set_servo(1, 30)
            self.set_servo(2, 150)
        elif mode == 2:
            self.set_servo(1, 120)
            self.set_servo(2, 60)

    def close(self):
        self.motor_stop()
        if self.ser is not None:
            self.ser.close()
            self.ser = None


class GamepadRemoteController:
    # 默认按键映射（Xbox 常见布局）
    # A/B/X 控制构型 0/1/2
    # LB 打开，RB 闭合
    # START 退出
    BTN_A = 0
    BTN_B = 1
    BTN_X = 2
    BTN_LB = 4
    BTN_RB = 5
    BTN_BACK = 6
    BTN_START = 7

    def __init__(self, robot):
        self.robot = robot
        self.joystick = None
        self.estop_active = False

        self.last_open_pressed = False
        self.last_close_pressed = False

        self.last_mode_buttons = {
            self.BTN_A: False,
            self.BTN_B: False,
            self.BTN_X: False,
            self.BTN_BACK: False,
        }

    def init_gamepad(self):
        pygame.init()
        pygame.joystick.init()

        if pygame.joystick.get_count() < 1:
            raise RuntimeError("No gamepad detected. Please connect controller via Bluetooth first.")

        self.joystick = pygame.joystick.Joystick(0)
        self.joystick.init()
        print(f"[System] Gamepad connected: {self.joystick.get_name()}")

    def _edge_pressed(self, btn_id, cache_dict):
        now_pressed = bool(self.joystick.get_button(btn_id))
        prev_pressed = cache_dict.get(btn_id, False)
        cache_dict[btn_id] = now_pressed
        return now_pressed and (not prev_pressed)

    def _handle_mode_buttons(self):
        if self._edge_pressed(self.BTN_A, self.last_mode_buttons):
            self.robot.set_config(0)
            print("[Action] Config 0")
        if self._edge_pressed(self.BTN_B, self.last_mode_buttons):
            self.robot.set_config(1)
            print("[Action] Config 1")
        if self._edge_pressed(self.BTN_X, self.last_mode_buttons):
            self.robot.set_config(2)
            print("[Action] Config 2")

    def _handle_estop_toggle(self):
        if not self._edge_pressed(self.BTN_BACK, self.last_mode_buttons):
            return

        self.estop_active = not self.estop_active
        self.robot.motor_stop()

        if self.estop_active:
            print("\n" + "!" * 40)
            print("!!! EMERGENCY STOP ACTIVE !!!")
            print("!!! Motion locked. Press BACK again to resume.")
            print("!" * 40)
        else:
            print("\n" + "=" * 40)
            print(">>> EMERGENCY STOP RELEASED")
            print("= " * 20)

    def _handle_open_close_hold(self):
        # 按住 LB 持续张开，按住 RB 持续闭合；松开则停止
        open_pressed = bool(self.joystick.get_button(self.BTN_LB))
        close_pressed = bool(self.joystick.get_button(self.BTN_RB))

        if open_pressed and not close_pressed:
            if not self.last_open_pressed or self.last_close_pressed:
                self.robot.motor_open()
                print("[Action] Palm OPEN")
        elif close_pressed and not open_pressed:
            if not self.last_close_pressed or self.last_open_pressed:
                self.robot.motor_close()
                print("[Action] Palm CLOSE")
        else:
            if self.last_open_pressed or self.last_close_pressed:
                self.robot.motor_stop()
                print("[Action] Palm STOP")

        self.last_open_pressed = open_pressed
        self.last_close_pressed = close_pressed

    def loop(self):
        print("\n=== Gamepad Remote Started ===")
        print("A -> Config 0")
        print("B -> Config 1")
        print("X -> Config 2")
        print("LB hold -> Palm OPEN")
        print("RB hold -> Palm CLOSE")
        print("Release LB/RB -> STOP")
        print("BACK -> Global E-STOP toggle")
        print("START -> Exit")

        running = True
        while running:
            pygame.event.pump()

            self._handle_estop_toggle()

            if self.joystick.get_button(self.BTN_START):
                print("[System] Exit requested by START")
                running = False
                continue

            if self.estop_active:
                # 急停锁定期间屏蔽所有动作命令
                self.last_open_pressed = bool(self.joystick.get_button(self.BTN_LB))
                self.last_close_pressed = bool(self.joystick.get_button(self.BTN_RB))
                time.sleep(0.02)
                continue

            self._handle_mode_buttons()
            self._handle_open_close_hold()

            time.sleep(0.02)

        self.robot.motor_stop()

    def shutdown(self):
        try:
            if self.joystick is not None:
                self.joystick.quit()
        finally:
            pygame.joystick.quit()
            pygame.quit()


def main():
    # 根据实际串口修改
    port = "COM9"
    baud = 115200

    robot = RobotDriver(port=port, baud=baud)
    if robot.ser is None:
        return

    controller = GamepadRemoteController(robot)

    try:
        controller.init_gamepad()
        controller.loop()
    except KeyboardInterrupt:
        print("\n[System] Keyboard interrupt")
    except Exception as e:
        print(f"[Error] {e}")
    finally:
        controller.shutdown()
        robot.close()
        print("[System] Shutdown complete.")


if __name__ == "__main__":
    main()
