from udsoncan import services
import inspect
import struct

class Request:
    """
    Represents a UDS Request.

    :param service: The service for which to make the request. This parameter must be a class that extends :class:`udsoncan.services.BaseService`
    :type service: class

    :param subfunction: The service subfunction. This value may be ignored if the given service does not supports subfunctions
    :type subfunction: int or None

    :param suppress_positive_response: Indicates that the server should not send a response if the response code is positive. 
            This parameter has effect only when the given service supports subfunctions
    :type suppress_positive_response: bool

    :param data: The service data appended after service ID and payload
    :type data: bytes
    """
    def __init__(self, service = None, subfunction = None, suppress_positive_response = False, data=None):
        if service is None:
            self.service = None
        elif isinstance(service, services.BaseService):
            self.service = service.__class__
        elif inspect.isclass(service) and issubclass(service, services.BaseService):
            self.service = service
        elif service is not None:
            raise ValueError("Given service must be a service class or instance")

        if not isinstance(suppress_positive_response, bool):
            raise ValueError("suppress_positive_response must be a boolean value")

        if subfunction is not None:
            if isinstance(subfunction, int):
                self.subfunction = subfunction
            else:
                raise ValueError("Given subfunction must be a valid integer")
        else:
            self.subfunction = None

        if self.service is not None:
            if suppress_positive_response and self.service.use_subfunction() == False:
                raise ValueError('Cannot suppress positive response for service %s. This service does not have a subfunction' % (self.service.get_name()))
        self.suppress_positive_response = suppress_positive_response

        if data is not None and not isinstance(data, bytes):
            raise ValueError("data must be a valid bytes object")

        self.data = data

    def get_payload(self, suppress_positive_response=None):
        """
        Generates a payload to be given to the underlying protocol.
        This method is meant to be used by a UDS client

        :return: A payload to be sent through the underlying protocol
        :rtype: bytes
        """
        if not issubclass(self.service, services.BaseService):
            raise ValueError("Cannot generate a payload. Given service is not a subclass of BaseService")

        if self.service.use_subfunction() and not isinstance(self.subfunction, int):
            raise ValueError("Cannot generate a payload. Given subfunction is not a valid integer")

        requestid = self.service.request_id()	# Returns the service ID used to make a client request

        payload = struct.pack("B", requestid)
        if self.service.use_subfunction():
            subfunction = self.subfunction
            if suppress_positive_response is None:
                if self.suppress_positive_response:
                    subfunction |= 0x80
            else:
                if suppress_positive_response == True:
                    subfunction |= 0x80
                elif suppress_positive_response == False:
                    subfunction &= ~0x80
            payload += struct.pack("B", subfunction)
        else:
            if suppress_positive_response == True or self.suppress_positive_response == True:
                raise ValueError('Cannot suppress positive response for service %s. This service does not have a subfunction' % (self.service.get_name()))

        if self.data is not None:
            payload += self.data

        return payload

    @classmethod
    def from_payload(cls, payload):
        """
        Creates a ``Request`` object from a payload coming from the underlying protocols.
        This method is meant to be used by a UDS server

        :param payload: The payload of data to parse
        :type payload: bytes

        :return: A :ref:`Request<Request>` object with populated fields
        :rtype: :ref:`Request<Request>`
        """
        req = cls()

        if len(payload) >= 1:
            req.service = services.cls_from_request_id(payload[0])
            if req.service is not None:		# Invalid service ID will make service None
                offset = 0
                if req.service.use_subfunction():
                    offset += 1
                    if len(payload) >= offset+1: 
                        req.subfunction = int(payload[1]) & 0x7F
                        req.suppress_positive_response = True if payload[1] & 0x80 > 0 else False
                if len(payload) > offset+1:
                    req.data = payload[offset+1:]
        return req

    def __repr__(self):
        suppress_positive_response = '[SuppressPosResponse] ' if self.suppress_positive_response else ''
        subfunction_name = '(subfunction=%d) ' % self.subfunction if self.service.use_subfunction() and self.subfunction is not None else ''
        bytesize = len(self.data) if self.data is not None else 0
        return '<Request: [%s] %s- %d data bytes %sat 0x%08x>' % (self.service.get_name(), subfunction_name, bytesize, suppress_positive_response, id(self))

    def __len__(self):
        try:
            return len(self.get_payload())
        except:
            return 0
