# -*- coding: utf-8 -*-

########################################
# REflex communication module          #
# EoF 2016 EoF@itphx.ru                #
########################################

import threading
import time
import HX


class REflexClass():

    def __init__(self, hxbot):
        self.i2c_bus = hxbot.i2c_bus
        self.hxbot = hxbot

        self.__e = None
        self.__t = None
        self.lock = hxbot.reflex_lock

        self.joystick = None
        self.hxpower = None
        self.control = HX.CONTROL_SRC_DEFAULT

        self.error_count = 0
        self.online = False
        self.is_running = False

        self.ready_to_move = False
        self.last_command_time = 0.0

        self.__move_float = 0
        self.__steer_center = 0

        # Tuple of commands for execute
        self.m = (
            False,  # Check
            HX.MOVE_FLOAT,  # Move
            HX.STEER_CENTER,  # Steer
            HX.HEAD_V_CENTER,  # Head vertical orientation
            HX.HEAD_H_CENTER  # Head horizontal orientation
            )

        # Tuple with results of executions
        self.s = (
            HX.NO_RSP,
            0,
            0,
            0,
            0
            )

    def start(self):
        self.joystick = self.hxbot.joystick
        self.hxpower = self.hxbot.hxpower

        self.__e = threading.Event()
        self.__t = threading.Thread(target=self.__process, args=(self.__e, ))

        self.__t.start()
        self.is_running = True

    def stop(self):
        self.__e.set()
        self.__t.join()
        self.is_running = False

        self.__e = None
        self.__t = None

        self.joystick = None
        self.hxpower = None

    def start_move(self):
        result = HX.OK_RSP

        # Enable moving
        if not self.hxpower.mv_enabled:
            result = self.hxpower.enable_mv()

        if result != HX.OK_RSP:
            return

        self.ready_to_move = True

    def stop_move(self, non_block=False):
        result = HX.OK_RSP

        # Disable moving
        if self.hxpower.mv_blocked and not non_block:
            return

        if self.hxpower.mv_enabled:
            result = self.hxpower.disable_mv()

        if result != HX.OK_RSP:
            return

        self.ready_to_move = False

    def get_temp(self):
        return self.__send_command(HX.COM_GET_TEMP)

    def __send_command(self, command):
        try:
            with self.lock:
                self.i2c_bus.write_word_data(HX.REFLEX_ADDRESS, command, 0)
                time.sleep(HX.REFLEX_RESPONSE_DELAY)
                response = self.i2c_bus.read_byte(HX.REFLEX_ADDRESS)
        except IOError:
            if self.online:
                self.error_count += 1
            if self.error_count >= HX.REFLEX_ERROR_LIMIT:
                self.online = False
            return HX.IOE_RSP
        else:
            self.error_count = 0
            self.online = True
            return response

        return HX.NO_RSP

    def __send_ex_command(self, flag, command):
        try:
            with self.lock:
                self.i2c_bus.write_word_data(HX.REFLEX_ADDRESS, HX.EXT_COM,
                    flag << 8 | command)
                time.sleep(HX.REFLEX_RESPONSE_DELAY)
                response = self.i2c_bus.read_byte(HX.REFLEX_ADDRESS)
        except IOError:
            if self.online:
                self.error_count += 1
            if self.error_count >= HX.REFLEX_ERROR_LIMIT:
                self.online = False
            return HX.IOE_RSP
        else:
            self.error_count = 0
            self.online = True
            return response

        return HX.NO_RSP

    def __get_byte(self):
        try:
            with self.lock:
                response = self.i2c_bus.read_byte(HX.REFLEX_ADDRESS)
        except IOError:
            if self.online:
                self.error_count += 1
            if self.error_count >= HX.REFLEX_ERROR_LIMIT:
                self.online = False
            return HX.IOE_RSP
        else:
            self.error_count = 0
            self.online = True
            return response

    def __process(self, stopRequest):
        # Process loop
        while not stopRequest.is_set():
            # Init cycle
            have_commands = False

            # Ping REflex
            self.__send_command(HX.COM_PING)

            # Joystick
            if self.control == HX.CONTROL_SRC_JOYSTICK and self.joystick:
                if self.joystick.ready:
                    # Joystick ready! Let's go!
                    if not self.ready_to_move:
                        self.start_move()

                    # Get m
                    self.m = self.joystick.get_m()
                    have_commands = self.m[0]
                else:
                    if self.ready_to_move:
                        self.stop_move(True)
                    time.sleep(0.5)

            # Execute received commands
            if have_commands:
                self.s = self.execute(self.m)

            # No commands? Stop move...
            elif (time.clock() - self.last_command_time >=
                HX.REFLEX_MOVE_TIMEOUT / 100):
                self.stop_move()

    def execute(self, m):
        # Move
        if m[1] != HX.MOVE_FLOAT:
            move_rsp = self.__send_ex_command(HX.MOVE_FLAG, m[1])
            self.__move_float = 0
        elif self.__move_float < 3:
            move_rsp = self.__send_ex_command(HX.MOVE_FLAG, m[1])
            self.__move_float += 1
        else:
            move_rsp = HX.OK_RSP

        # Steer
        if m[2] != HX.STEER_CENTER:
            steer_rsp = self.__send_ex_command(HX.STEER_FLAG, m[2])
            self.__steer_center = 0
        elif self.__steer_center < 3:
            steer_rsp = self.__send_ex_command(HX.STEER_FLAG, m[2])
            self.__steer_center += 1
        else:
            steer_rsp = HX.OK_RSP

        # Head vertiacal orientation
        head_v_rsp = self.__send_ex_command(HX.HEAD_V_FLAG, m[3])

        # Head horizontal orientation
        head_h_rsp = self.__send_ex_command(HX.HEAD_H_FLAG, m[4])

        # Set last command time
        self.last_command_time = time.clock()

        # Return execution status
        return (HX.OK_RSP, move_rsp, steer_rsp, head_v_rsp, head_h_rsp)