class robot_gui(Ui_MainWindow):

    def __init__(self):
        super(robot_gui, self).__init__()

        self.robot = Robot()
        self.robot.initialise()

        self.microsteps = 2
        self.rpm = 100
        self.intervals = 500
        self.direction = 1
        self.max_runs = 10
        self.running = False
        self.position = 0
        self.jog_mode = False
        self.probe_data = []
        self.errors = 0

        self.obj = Worker()  # no parent!
        self.obj.finished.connect(self.status_bar_done)
        self.obj.position.connect(self.update_lcd)

        self.ui = Ui_MainWindow()
        self.setupUi(self)
        self.link_buttons()
        self.statusBar().showMessage("Ready")

    def update_lcd(self, i):
        if 0 <= i < 10:
            self.motor_pos_lcd.setDigitCount(4)
        elif -10 < i < 0 or i >= 10:
            self.motor_pos_lcd.setDigitCount(5)
        elif i <= -10 or i >= 100:
            self.motor_pos_lcd.setDigitCount(6)
        elif i <= -100 or i >= 1000:
            self.motor_pos_lcd.setDigitCount(7)

        self.motor_pos_lcd.display(i)

    def status_bar_done(self):
        self.statusBar().showMessage("Done")
        self.start_run_button.setDisabled(False)
        self.stop_run_button.setDisabled(True)

    def showDialog(self):
        fname = QFileDialog.getSaveFileName(self, 'Save File', 'data.csv')
        with open(fname[0], "w", newline='') as csvfile:
            csvwriter = csv.writer(csvfile)
            csvwriter.writerow(("Position", "Encoder", "A0", "A1", "A2"))
            for row in self.probe_data:
                csvwriter.writerow(row)

    def link_buttons(self):
        self.link_speed_buttons()
        self.link_microstep_buttons()
        self.link_jog_buttons()
        self.stop_run_button.clicked.connect(self.stop)
        self.start_run_button.clicked.connect(self.run)
        self.pushButton.clicked.connect(self.zero)
        self.pushButton_2.clicked.connect(self.showDialog)

    def link_jog_buttons(self):
        self.jog_up_fast.clicked.connect(lambda: self.set_jog_speed("1500"))
        self.jog_up_slow.clicked.connect(lambda: self.set_jog_speed("100"))
        self.jog_stop.clicked.connect(self.set_jog_stop)
        self.jog_down_slow.clicked.connect(lambda: self.set_jog_speed("-100"))
        self.jog_down_fast.clicked.connect(lambda: self.set_jog_speed("-1500"))

    @pyqtSlot()
    def set_jog_speed(self, speed):
        self.jog_mode = False
        self.robot.jog_speed(speed)
        self.jog_mode = True
        threading.Thread(target=self.run_jog_mode).start()

    @pyqtSlot()
    def set_jog_stop(self):
        self.robot.jog_speed(0)
        sleep(0.1)
        self.robot.jog_speed(0)
        self.jog_stop.setAutoExclusive(False)
        self.jog_stop.setChecked(False)
        self.jog_stop.setAutoExclusive(True)
        self.jog_mode = False

    def stop(self):
        self.running = False

    def run(self):
        self.running = True
        self.start_run_button.setDisabled(True)
        self.stop_run_button.setDisabled(False)
        self.statusBar().showMessage("Running")
        threading.Thread(target=self.run_commands).start()

    def zero(self):
        # Update motor position QLCDNumber widget
        self.obj.position.emit(self.robot.zero())

    def link_speed_buttons(self):
        self.speed_50.pressed.connect(lambda: self.set_speed(self.speed_50))
        self.speed_100.pressed.connect(lambda: self.set_speed(self.speed_100))
        self.speed_150.pressed.connect(lambda: self.set_speed(self.speed_150))
        self.speed_200.pressed.connect(lambda: self.set_speed(self.speed_200))

    def link_microstep_buttons(self):
        self.microstep_1.pressed.connect(
            lambda: self.set_microsteps(self.microstep_1))
        self.microstep_2.pressed.connect(
            lambda: self.set_microsteps(self.microstep_2))
        self.microstep_4.pressed.connect(
            lambda: self.set_microsteps(self.microstep_4))
        self.microstep_8.pressed.connect(
            lambda: self.set_microsteps(self.microstep_8))

    def set_speed(self, b):
        self.rpm = int(b.text()[:3])

    def set_microsteps(self, b):
        self.microsteps = int(b.text()[0])

    def handle_sensor_data(self, line):
        data = self.robot.handle_sensor_data(line)
        # Update motor position QLCDNumber widget
        self.obj.position.emit(self.robot.position)
        # Check data isn't blank
        if len(data) == 5:
            # Add readings to dataset
            self.probe_data.append(data)
        else:
            self.errors += 1

    def handle_status_message(self, line):
        self.position = self.robot.handle_status_message(line)
        # Update motor position QLCDNumber widget
        self.obj.position.emit(self.position)
        # Check data isn't blank

    def run_jog_mode(self):
        # Run until jog mode flag is set to False
        while self.jog_mode:
            # Try/Except statements as serial read can fail
            try:
                # Check for available data to read in
                if self.robot.xbee.inWaiting():

                    # Decode and strip EOL characters
                    line = self.robot.get_cleaned_line()
                    # Indicates a robot status string
                    if line[0] == "s":
                        self.handle_status_message(line)
                    # Any other string is invalid
                    else:
                        print("Invalid String: {0}".format(line))

            # Debug print any failure in the above code
            except Exception as e:
                self.errors += 1
                print("Exception: {0}".format(e))

        self.handle_end_data()

    def run_commands(self):

        start_time = time()
        # Generate a command string from set variables
        self.robot.construct_command(
            MIC=self.microsteps,
            STP=self.rpm,
            INT=self.intervals,
            DIR=self.direction,
            RUN=1)

        # Start first run
        self.robot.send_command()

        # Variable to keep track of how many runs have happened
        current_run = 0

        # Keep track if end stop hit
        end_hit = False

        # Run until self.running flag is set to False
        while self.running:
            # Try/Except statements as serial read can fail
            try:
                # Check for available data to read in
                if self.robot.xbee.inWaiting():

                    # Decode and strip EOL characters
                    line = self.robot.get_cleaned_line()
                    # Get first character in string to indicate type
                    indicator = line[0]

                    # Indicates a sensor data string
                    if indicator == "v":
                        self.handle_sensor_data(line)

                    # Indicates a robot status string
                    elif indicator == "s":
                        self.handle_status_message(line)

                    # Indicates completion of last command set
                    elif line == "ack":
                        # Move to next run through commands
                        # current_run += 1
                        # Stop sending command strings if at max runs
                        if current_run == self.max_runs or end_hit:
                            self.running = False
                        # Send repeat command to keep running
                        else:
                            self.robot.send_command()
                    elif line == "END":
                        end_hit = True
                    # Debug output for invalid data string
                    else:
                        self.errors += 1
                        print("Invalid String: {0}".format(line))

            # Debug print any failure in the above code
            except Exception as e:
                self.errors += 1
                print("Exception: {0}".format(e))
                # Wait for any remaining serial data
        self.handle_end_data()
        # Update status bar to "Done" and enable/disable run/stop buttons
        self.obj.finished.emit()
        print("Error rate: {0}%".format(
            self.errors / len(self.probe_data) * 100))
        print("Frequncy: {0}Hz".format(
            len(self.probe_data) / (time() - start_time)))

    def handle_end_data(self):
        # Wait for any remaining serial data
        sleep(0.1)
        # Handle last few status messages
        while self.robot.xbee.inWaiting():
            try:
                # Decode and strip EOL characters
                line = self.robot.get_cleaned_line()
                # Indicates a robot status string
                if line[0] == "v":
                    self.handle_sensor_data(line)
                # Indicates a robot status string
                elif line[0] == "s":
                    self.handle_status_message(line)
            except:
                self.errors += 1
                print(self.errors)
                pass
            sleep(0.005)