from __future__ import absolute_import

# Some utilities for speaking s3g
import struct
import array
import time
import serial

import makerbot_driver
import uuid


class s3g(object):
    """ Represents an interface to a s3g driven bot. Contains methods and functions to
    read and write data to the bot.  No data is cached by this driver, all data is requested
    over the USB bus when queried.
    """

    POINT_LENGTH = 3
    EXTENDED_POINT_LENGTH = 5

    @classmethod
    def from_filename(cls, port, condition, baudrate=115200, timeout=.2):
        """Constructs and returns an s3g object connected to the
        passed file endpoint passed as a string @port (ie 'COM0' or '/dev/tty9')

        @param port target Serial port name
        @param baurdate baudrace 115200 assumed
        @param timeout, time allowance before timeout error. 0.2 assummed
        @return s3g object, equipped with a StreamWrtier directed at port.
        """
        s = serial.Serial(port, baudrate=baudrate, timeout=timeout)

        # begin baud rate hack
        #
        # There is an interaction between the 8U2 firmware and PySerial where
        # PySerial thinks the 8U2 is already running at the specified baud rate and
        # it doesn't actually issue the ioctl calls to set the baud rate. We work
        # around it by setting the baud rate twice, to two different values. This
        # forces PySerial to issue the correct ioctl calls.
        s.baudrate = 9600
        s.baudrate = baudrate
        # end baud rate hack

        mb_streamWriter = makerbot_driver.Writer.StreamWriter(s, condition)
        return s3g(mb_streamWriter)

    def __init__(self, mb_stream_writer=None):
        self.writer = mb_stream_writer
        self._eeprom_reader = None
        self.print_to_file_type = 's3g'
        self.tool_query_code = 'Bbb'

    def set_print_to_file_type(self, print_to_file_type):
        self.print_to_file_type = print_to_file_type

    def init_eeprom_reader(self, firmware_version=None):
            self._eeprom_reader = makerbot_driver.EEPROM.EepromReader.factory(
                self, firmware_version)

    @property
    def eeprom_reader(self):
        if self._eeprom_reader is None:
            self._eeprom_reader = makerbot_driver.EEPROM.EepromReader.factory(
                self)
        return self._eeprom_reader

    def close(self):
        """ If any ports are open for this s3g bot, it closes those ports """
        if self.writer:
            self.writer.close()

    def is_open(self):
        """@returns true if we have a writer and it is open. False otherwise."""
        if self.writer:
            return self.writer.is_open()
        return False

    def open(self):
        """ If a writer with data exists in this bot, attempts to open that writer."""
        if self.writer:
            self.writer.open()

    def get_version(self):
        """
        Get the firmware version number of the connected machine
        @return Version number
        """
        payload = struct.pack(
            '<BH',
            makerbot_driver.host_query_command_dict['GET_VERSION'],
            makerbot_driver.s3g_version,
        )

        response = self.writer.send_query_payload(payload)
        [response_code, version] = makerbot_driver.Encoder.unpack_response(
            '<BH', response)
        # TODO: check response_code
        return version

    def get_name(self):
        """
        Get stored Bot Name
        @return a string for the name
        TODO: merge this function with future eeprom read/write module
        """
        name = self.eeprom_reader.read_data('MACHINE_NAME')
        return name[0]

    def get_toolhead_count(self):
        """
        @return the toolhead count of this bot. -1 on error
        """
        data = self.eeprom_reader.read_data('TOOL_COUNT')
        return data[0]

    def get_vid_pid(self):
        """
        Due to a production issue with a vendor, we do not
        trust the VID/PID in all EEPROMS, and the generic
        get_vid_pid most used should return the interface VID/PID,
        not the eeprom vid_pid. """
        return self.get_vid_pid_iface()

    def get_vid_pid_eeprom(self):
        """
        @returns tuple of vid,pid. tuple from EEPROM (None,None) on error
        """
        reader = self.create_reader()
        data = reader.read_data('VID_PID_INFO')
        return data[0], data[1]

    def get_vid_pid_iface(self):
        """
        Reads VID/PID values from the serial port object associated
        with this device.
        @return USB vid/pid tuple from the usb chip on the machine,
        """
        vid = None
        pid = None
        if self.writer is not None:
            if isinstance(self.writer, makerbot_driver.Writer.StreamWriter):
                portname = self.writer.file.port
                detector = makerbot_driver.get_gMachineDetector()
                vid, pid = detector.vid_pid_from_portname(portname)
        return vid, pid

    def get_verified_status(self, verified_pid=makerbot_driver.vid_pid[1]):
        """
        @returns true if this firmware is marked as verified
        """
        vid_pid = self.get_vid_pid()
        return vid_pid[1] is verified_pid

    def get_advanced_version(self):
        """
        Get the firmware version number of the connected machine
        @return Version number
        """
        payload = struct.pack(
            '<BH',
            makerbot_driver.host_query_command_dict['GET_ADVANCED_VERSION'],
            makerbot_driver.s3g_version,
        )

        response = self.writer.send_query_payload(payload)
        [response_code,
         version,
         internal_version,
         software_variant,
         reserved_a,
         reserved_b] = makerbot_driver.Encoder.unpack_response('<BHHBBH', response)
        # TODO: check response_code

        version_info = {
            'Version': version,
            'InternalVersion': internal_version,
            'SoftwareVariant': sof
tware_variant,
            'ReservedA': reserved_a,
            'ReservedB': reserved_b,
        }

        return version_info

    def capture_to_file(self, filename):
        """
        Capture all subsequent commands up to the 'end capture' command to a file with the given filename on an SD card.
        @param str filename: The name of the file to write to on the SD card
        """
     
   payload = struct.pack(
            '<B',
            makerbot_driver.host_query_command_dict['CAPTURE_TO_FILE'],
        )
        payload += filename
        payload += '\x00'

        response = self.writer.send_query_payload(payload)

        [response_code, sd_response_code] = makerbot_driver.Encoder.unpack_response('<BB', response)
        # TODO: check response_code
        if sd_response_code != makerbot_driver.sd_error_dict['SUCCESS']:
            raise makerbot_driver.SDCardError(sd_response_code)

    def end_capture_to_file(self):
        """
        Send the end capture signal to the bot, so it stops capturing data and writes all commands out to a file on the SD card
        @return The number of bytes written to file
        """
        payload = struct.pack(
            '<B',
            makerbot_driver.host_query_command_dict['END_CAPTURE'],
        )

        response = self.writer.send_query_payload(payload)

        [response_code, sdResponse] = makerbot_driver.Encoder.unpack_response(
            '<BI', response)
        # TODO: check response_code
        return sdResponse

    def reset(self):
        """
        reset the bot, unless the bot is waiting to tell us a build is cancelled.
        """
        payload = struct.pack(
            '<B',
            makerbot_driver.host_query_command_dict['RESET'],
        )

        # TODO: mismatch here.
        self.writer.send_action_payload(payload)

    def is_finished(self):
        """
        Checks if the steppers are still executing a command
        """
        payload = struct.pack(
            '<B',
            makerbot_driver.host_query_command_dict['IS_FINISHED'],
        )

        response = self.writer.send_query_payload(payload)

        [response_code,