from ..common import *


class TD_COAP_OBS_05(CoAPTestCase):
    """
---
TD_COAP_OBS_05:
    obj: Server detection of deregistration (client OFF)
    cfg: CoAP_CFG_BASIC
    ref: '[OBSERVE] 4.5 §2'
    pre:
        - Client supports Observe option
        - Server supports Observe option
        - "Server offers an observable resource /obs which changes periodically (e.g. every
        5s) which produces confirmable notifications"

    seq:
    -   s:
        - 'Client is requested to send to the server a confirmable GET
        request with observe option for resource /obs'

    -   c:
        - 'The request sent by client contains:'
        -   - Type = 0 (CON)
            - Code = 1 (GET)
            - Token value  = a value generated by the client
            - Observe option = empty

    -   c:
        - 'Server sends the response containing:'
        -   - Type = 2 (ACK)
            - Code = 2.05 (Content)
            - Content-format of the resource /obs
            - Token value = same as one found in the step 2
            - Observe option with a sequence number

    -   c:
        - 'Server sends a notification containing:'
        -   - Type = 0 (CON)
            - Code = 2.05 (Content)
            - Content-format = same as one found in the step 3
            - Token value = same as one found in the step 2
            - Observe option indicating increasing values

    -   c: Client displays the received information

    -   c: Client sends an ACK

    -   s: Client is switched off

    -   c:
        - Server’s confirmable responses are not acknowledged
        - Server’s retransmissions have an updated Observe option value

    -   c: "Server can keep retransmitting the responses until at least
        Max-Age seconds after the first un-acknowledged response."

    """
    @classmethod
    @typecheck
    def get_stimulis(cls) -> list_of(Value):
        """
        Get the stimulis of this test case. This has to be be implemented into
        each test cases class.

        :return: The stimulis of this TC
        :rtype: [Value]
        """
        return [
            CoAP(type='con', code='get', opt=Opt(CoAPOptionObserve(0),CoAPOptionUriPath("obs")))
        ]

    def run (self):
        request = CoAP (type="con", code="get",	opt=self.uri("/obs", CoAPOptionObserve (0)))
        response= CoAP (type="ack", code=2.05, opt=Opt(CoAPOptionObserve(), CoAPOptionContentFormat()))
        notif   = CoAP (type="con", code=2.05, opt=Opt(CoAPOptionObserve(), CoAPOptionContentFormat()))

        # Step 2
        self.match("client", request)

        token = self.coap["tok"]
        uri = self.coap.get_uri()

        self.next()

        # Step 3
        if not self.match("server", response):
            raise self.Stop()
        self.match("server", CoAP(tok = token), "fail")

        content_format = self.coap["opt"][CoAPOptionContentFormat]["val"]
        index          = self.coap["opt"][CoAPOptionObserve]["val"]

        last_maxage = self.coap["opt"][CoAPOptionMaxAge]["val"]

        self.next()
        last_ts = self._frame.timestamp
        # Step 4
        unacknowledged_notification = False
        while self.match("server", notif, None) and not unacknowledged_notification:
            self.match("server", CoAP(tok = token), "fail")
            self.match("server", CoAP(opt = Opt(CoAPOptionContentFormat(content_format))), "fail")

            # increasing observe value
            new_index = self.coap["opt"][CoAPOptionObserve]["val"]
            self.set_verdict(("pass" if new_index > index else "fail"),
                "value of observe option must be increasing")
            index = new_index

            maxage = self.coap["opt"][CoAPOptionMaxAge]["val"]

            self.next()

            if not self.match("client", CoAP(type = "ack"), None):
                unacknowledged_notification = True

            last_maxage = maxage
            last_ts = self._frame.timestamp
            self.next()

        nb_nonack = 1
        while self.match("server", notif, None):
            self.match("server", CoAP(tok = token), "fail")
            self.match("server", CoAP(opt = Opt(CoAPOptionContentFormat(content_format))), "fail")

            nb_nonack += 1
            last_retrans_ts = self._frame.timestamp
            # self.next()
            if not self.next(optional = True):
                break

        if nb_nonack == 1:
            self.set_verdict("inconclusive", "server must retransmit non-acknowledged notifications")
        else:
            self.set_verdict("pass" if (last_retrans_ts - last_ts) >= last_maxage else "inconclusive", "server should keep retransmitting at least until Max-Age (%d) seconds" % last_maxage)
