from __future__ import division
import struct
from binascii import hexlify

from umodbus import log
from umodbus.server.serial import AbstractSerialServer
from umodbus.client.serial.redundancy_check import get_crc, validate_crc


def get_char_size(baudrate):
    """ Get the size of 1 character in seconds.

    From the implementation guide:

        "The implementation of RTU reception driver may imply the management of
        a lot of interruptions due to the t 1.5  and t 3.5  timers. With high

        communication baud rates, this leads to a heavy CPU load. Consequently
        these two timers must be strictly respected when the baud rate is equal
        or lower than 19200 Bps. For baud rates greater than 19200 Bps, fixed
        values for the 2 timers should be used:  it is recommended to use a
        value of 750us for the inter-character time-out (t 1.5) and a value of
        1.750ms for inter-frame delay (t 3.5)."
    """
    if baudrate <= 19200:
        # One frame is 11 bits.
        return 11 / baudrate

    # 750 us / 1.5 = 500 us or 0.0005 s.
    return 0.0005


class RTUServer(AbstractSerialServer):
    @property
    def serial_port(self):
        return self._serial_port

    @serial_port.setter
    def serial_port(self, serial_port):
        """ Set timeouts on serial port based on baudrate to detect frames. """
        char_size = get_char_size(serial_port.baudrate)

        # See docstring of get_char_size() for meaning of constants below.
        serial_port.inter_byte_timeout = 1.5 * char_size
        serial_port.timeout = 3.5 * char_size
        self._serial_port = serial_port

    def serve_once(self):
        """ Listen and handle 1 request. """
        # 256 is the maximum size of a Modbus RTU frame.
        request_adu = self.serial_port.read(256)
        log.debug('<-- {0}'.format(hexlify(request_adu)))

        if len(request_adu) == 0:
            raise ValueError

        response_adu = self.process(request_adu)
        self.respond(response_adu)

    def process(self, request_adu):
        """ Process request ADU and return response.

        :param request_adu: A bytearray containing the ADU request.
        :return: A bytearray containing the response of the ADU request.
        """
        validate_crc(request_adu)
        return super(RTUServer, self).process(request_adu)

    def create_response_adu(self, meta_data, response_pdu):
        """ Build response ADU from meta data and response PDU and return it.

        :param meta_data: A dict with meta data.
        :param request_pdu: A bytearray containing request PDU.
        :return: A bytearray containing request ADU.
        """
        first_part_adu = struct.pack('>B', meta_data['unit_id']) + response_pdu
        return first_part_adu + get_crc(first_part_adu)
