import time
from serial import Serial 
from threading import Thread

from unilabos.device_comms.universal_driver import UniversalDriver

class EleGripper(UniversalDriver):
    @property
    def status(self) -> str:
        return f"spin_pos: {self.rot_data[0]}, grasp_pos: {self.gri_data[0]}, spin_v: {self.rot_data[1]}, grasp_v: {self.gri_data[1]}, spin_F: {self.rot_data[2]}, grasp_F: {self.gri_data[2]}"

    def __init__(self,port,baudrate=115200, pos_error=-11, id = 9):
        self.ser = Serial(port,baudrate)
        self.pos_error = pos_error

        self.success = False

        # [pos, speed, force]
        self.gri_data = [0,0,0]
        self.rot_data = [0,0,0]

        self.id = id

        self.init_gripper()
        self.wait_for_gripper_init()

        t = Thread(target=self.data_loop)
        t.start()

        self.gripper_move(0,255,255)
        self.rotate_move_abs(0,255,255)

    def modbus_crc(self, data: bytes) -> bytes:
        crc = 0xFFFF
        for pos in data:
            crc ^= pos
            for _ in range(8):
                if (crc & 0x0001) != 0:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc.to_bytes(2, byteorder='little')

    def send_cmd(self, id, fun, address, data:str):
        data_len = len(bytes.fromhex(data))
        data_ = f"{id:02X} {fun} {address} {data_len//2:04X} {data_len:02X} {data}"
        data_bytes = bytes.fromhex(data_)
        data_with_checksum = data_bytes + self.modbus_crc(data_bytes)
        self.ser.write(data_with_checksum)
        time.sleep(0.01)
        self.ser.read(8)

    def read_address(self, id , address, data_len):
        data = f"{id:02X} 03 {address} {data_len:04X}"
        data_bytes = bytes.fromhex(data)
        data_with_checksum = data_bytes + self.modbus_crc(data_bytes)
        self.ser.write(data_with_checksum)
        time.sleep(0.01)
        res_len = 5+data_len*2
        res = self.ser.read(res_len)
        return res

    def init_gripper(self):
        self.send_cmd(self.id,'10','03 E8','00 01')
        self.send_cmd(self.id,'10','03 E9','00 01')

    def gripper_move(self, pos, speed, force):
        self.send_cmd(self.id,'10', '03 EA', f"{speed:02x} {pos:02x} {force:02x} 01")

    def rotate_move_abs(self, pos, speed, force):
        pos += self.pos_error
        if pos < 0:
            pos = (1 << 16) + pos
        self.send_cmd(self.id,'10', '03 EC', f"{(pos):04x} {force:02x} {speed:02x} 0000 00 01")

    # def rotate_move_rel(self, pos, speed, force):
    #     if pos < 0:
    #         pos = (1 << 16) + pos
    #     print(f'{pos:04x}')
    #     self.send_cmd(self.id,'10', '03 EC', f"0000 {force:02x} {speed:02x} {pos:04x} 00 02")

    def wait_for_gripper_init(self):
        res = self.read_address(self.id, "07 D0", 1)
        res_r = self.read_address(self.id, "07 D1", 1)
        while res[4] == 0x08 or res_r[4] == 0x08:
            res = self.read_address(self.id, "07 D0", 1)
            res_r = self.read_address(self.id, "07 D1", 1)
            time.sleep(0.1)

    def wait_for_gripper(self):
        while self.gri_data[1] != 0:
            time.sleep(0.1)

    def wait_for_rotate(self):
        while self.rot_data[1] != 0:
            time.sleep(0.1)

    def data_reader(self):
        res_g = self.read_address(self.id, "07 D2", 2)
        res_r = self.read_address(self.id, "07 D4", 4)
        int32_value = (res_r[3] << 8) | res_r[4]
        if int32_value > 0x7FFF:
            int32_value -= 0x10000  
        self.gri_data = (int(res_g[4]), int(res_g[3]), int(res_g[5]))
        self.rot_data = (int32_value, int(res_r[5]), int(res_r[6]))

    def data_loop(self):
        while True:
            self.data_reader()
            time.sleep(0.1)


    def node_gripper_move(self, cmd:str):
        self.success = False
        
        try:
            cmd_list = [int(x) for x in cmd.replace(' ','').split(',')]
            self.gripper_move(*cmd_list)
            self.wait_for_gripper()
        except Exception as e:
            raise e

        self.success = True

    def node_rotate_move(self, cmd:str):
        self.success = False

        try:
            cmd_list = [int(x) for x in cmd.replace(' ','').split(',')]
            self.rotate_move_abs(*cmd_list)
            self.wait_for_rotate()
        except Exception as e:
            raise e
        
        self.success = True

    def move_and_rotate(self, spin_pos, grasp_pos, spin_v, grasp_v, spin_F, grasp_F):
        self.gripper_move(grasp_pos, grasp_v, grasp_F)
        self.wait_for_gripper()
        self.rotate_move_abs(spin_pos, spin_v, spin_F)
        self.wait_for_rotate()

if __name__ == "__main__":
    gripper = EleGripper("COM12")
    gripper.init_gripper()
    gripper.wait_for_gripper_init()
    gripper.gripper_move(210,127,255)
    gripper.wait_for_gripper()
    gripper.rotate_move_abs(135,10,255)
    gripper.data_reader()
    print(gripper.rot_data)
    
