class SubscribeEnvelope:
    def __init__(self, messages=None, metadata=None):
        assert isinstance(messages, (list, None))
        assert isinstance(metadata, (SubscribeMetadata, None))

        self.messages = messages
        self.metadata = metadata

    @classmethod
    def from_json(cls, json_input):
        messages = []

        for raw_message in json_input["m"]:
            messages.append(SubscribeMessage.from_json(raw_message))

        metadata = SubscribeMetadata.from_json(json_input["t"])
        return SubscribeEnvelope(messages, metadata)


class SubscribeMessage:
    def __init__(self):
        self.shard = None
        self.subscription_match = None
        self.channel = None
        self.payload = None
        self.flags = None
        self.issuing_client_id = None
        self.subscribe_key = None
        self.origination_timetoken = None
        self.publish_metadata = None
        self.only_channel_subscription = False
        self.type = 0

    @classmethod
    def from_json(cls, json_input):
        message = SubscribeMessage()
        if "a" in json_input:
            message.shard = json_input["a"]
        if "b" in json_input:
            message.subscription_match = json_input["b"]
        message.channel = json_input["c"]
        message.payload = json_input["d"]
        message.flags = json_input["f"]
        if "i" in json_input:
            message.issuing_client_id = json_input["i"]
        message.subscribe_key = json_input["k"]
        if "o" in json_input:
            message.origination_timetoken = json_input["o"]
        message.publish_metadata = PublishMetadata.from_json(json_input["p"])
        if "e" in json_input:
            message.type = json_input["e"]
        return message


class SubscribeMetadata:
    def __init__(self, timetoken=None, region=None):
        self.timetoken = timetoken
        self.region = region

    @classmethod
    def from_json(cls, json_input):
        assert isinstance(json_input, dict)
        assert "r" in json_input
        assert "t" in json_input

        return SubscribeMetadata(json_input["t"], json_input["r"])


class PresenceEnvelope:
    def __init__(self, action, uuid, occupancy, timestamp, data=None):
        assert isinstance(action, str)
        assert isinstance(occupancy, int)
        assert isinstance(timestamp, int)
        if data is not None:
            assert isinstance(data, dict)

        self.action = action
        self.uuid = uuid
        self.occupancy = occupancy
        self.timestamp = timestamp
        self.data = data

    @classmethod
    def extract_value(cls, json, key):
        if key in json:
            return json[key]
        else:
            return None

    @classmethod
    def from_json_payload(cls, json):
        return PresenceEnvelope(
            action=cls.extract_value(json, "action"),
            uuid=cls.extract_value(json, "uuid"),
            occupancy=cls.extract_value(json, "occupancy"),
            timestamp=cls.extract_value(json, "timestamp"),
            data=cls.extract_value(json, "data"),
        )


class PublishMetadata:
    def __init__(self, publish_timetoken=None, region=None):
        self.publish_timetoken = publish_timetoken
        self.region = region

    @classmethod
    def from_json(cls, json_input):
        assert "r" in json_input
        assert "t" in json_input

        return PublishMetadata(int(json_input["t"]), int(json_input["r"]))
