__copyright__ = """
    Copyright 2019 Amazon.com, Inc. or its affiliates.
    Copyright 2019 Netflix Inc.
    Copyright 2019 Google LLC
"""
__license__ = """
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

from threading import Event
from threading import Thread
from mqttclient import MqttClient
from mqttrequest import MqttRequest
from mqttwait import MqttWait
import logging
import json
import time


class RoundRobin:
    """Round robin through all the discovered media apps

    This class will walk through the list of discovered media apps, launching each one and
    waiting for it's status to change to "running".
    """
    def __init__(self, host, port):
        """Create the RoundRobin object."""
        self.host = host
        self.port = port
        self.mqtt_client = MqttClient(host, port)
        self.thread = None
        self.stop_event = Event()
        self.active_requests = []
        self.app_args = [
            {
                "app_id": "netflix",
                "startup-args": {"args": []},
                "args": {"args": ["m=https://api-global.netflix.com/catalog/titles/movie/80223967&trackId=14855318"]}},
            {
                "app_id": "prime-video",
                "startup-args": {"args": []},
                "args": {"contentId": "B015YJRQ8U", "mediaType": "movie"}},
            {
                "app_id": "youtube",
                "startup-args": {},
                "args": {"video_id": "KEcbFSaLpoM"}},
        ]
        self.logger = logging.getLogger(__name__)

    def execute(self, request):
        """Execute a request.

        Basically block until a request has been executed and the response returned.
        """
        self.active_requests.append(request)
        response = request.execute()
        self.active_requests.remove(request)
        return response

    @staticmethod
    def build_app_topic(app, topic):
        return "apps/{}/{}".format(app["app_id"], topic)

    @staticmethod
    def build_platform_topic(capability, topic):
        return "platform/{}/{}".format(capability, topic)

    def start_app(self, app, content_reference):
        """Attempt to launch the specified app.

        This function executes the start request and waits for the app status to change to "running".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "app/start"), json.dumps(content_reference))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/lifecycle"), {"status": "started"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for app status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def stop_app(self, app):
        """Attempt to launch the specified app.

        This function executes the start request and waits for the app status to change to "running".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "app/stop"), json.dumps({"args": []}))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/lifecycle"), {"status": "stopped"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for app status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def start_media(self, app, content_reference):
        """Attempt to play specified content on the specified app.

        This function executes the media/start request and waits for the media status to change to "playing".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "media/start"), json.dumps(content_reference))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/media"), {"status": "playing"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for media status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def send_input_event(self, app, device, key):
        req = MqttRequest(self.mqtt_client,
                          self.build_platform_topic("input", "{}/{}".format(device, key)),
                          json.dumps({"app_id": app["app_id"]}))
        response = self.execute(req)
        if response["status"] != 200:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def memory_monitor(self, app, action):
        """Control monitoring memory on the specified app.

        This function executes the telemetry/memory/monitor/<action> request.
        """
        req = MqttRequest(self.mqtt_client,
                          self.build_platform_topic("telemetry", "{}/{}".format("monitor", action)),
                          json.dumps({"app_id": app["app_id"]}))
        response = self.execute(req)
        if response["status"] != 200:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def run_test(self, app):
        self.logger.info("Running test on {}".format(app["name"]))
        self.logger.info("Starting app...")
        if not self.stop_event.is_set():
            # Start the app and wait for it to startup
            content_reference = next(args for args in self.app_args if args["app_id"] == app["app_id"])
            if content_reference:
                self.start_app(app, content_reference["startup-args"])
            else:
                raise Exception("No args found for app {}.".format(app["name"]))

            time.sleep(1)

        self.memory_monitor(app, "start")

        if app["name"] == "Netflix":
            # Enter the default profile for Netflix
            time.sleep(5)
            self.send_input_event(app, "remote", "OK")
            time.sleep(2)

        if app["name"] == "Prime Video":
            self.send_input_event(app, "remote", "down")
            time.sleep(2)

            self.send_input_event(app, "remote", "down")
            time.sleep(2)

        self.send_input_event(app, "remote", "right")
        time.sleep(2)

        self.send_input_event(app, "remote", "right")
        time.sleep(2)

        self.send_input_event(app, "remote", "down")
        time.sleep(2)

        self.logger.info("Starting playback...")
        if not self.stop_event.is_set():
            # Play some media
            content_reference = next(args for args in self.app_args if args["app_id"] == app["app_id"])
            if content_reference:
                self.start_media(app, content_reference["args"])
            else:
                raise Exception("No args found for app {}.".format(app["name"]))

        # Let playback start and run for a bit...
        sleep_time_seconds = 10
        self.logger.info("Play for {} seconds...".format(sleep_time_seconds))
        if not self.stop_event.is_set():
            time.sleep(sleep_time_seconds)

        # Pause playback
        self.send_input_event(app, "remote", "pause")
        time.sleep(5)
        self.send_input_event(app, "remote", "play")
        time.sleep(5)
        self.send_input_event(app, "remote", "pause")
        time.sleep(5)
        self.send_input_event(app, "remote", "play")
        time.sleep(5)

        self.memory_monitor(app, "stop")

        self.logger.info("Stopping app...")
        if not self.stop_event.is_set():
            # Stop the app
            self.stop_app(app)

        self.logger.info("Test complete.")

    def _start(self):
        """Private start function that starts the client and loops through the apps forever."""
        self.mqtt_client.start()
        discovered_apps = self.mqtt_client.get_discovered_apps()
        while not self.stop_event.is_set():
            for app in discovered_apps:
                self.run_test(app)

    def start(self):
        """Public start function to get the party started."""
        self.thread = Thread(target=self._start, daemon=True)
        self.thread.start()
        return self.thread

    def stop(self):
        """Party's over."""
        self.mqtt_client.stop()
        self.stop_event.set()
        for active_request in self.active_requests:
            active_request.cancel()

__copyright__ = """
    Copyright 2019 Amazon.com, Inc. or its affiliates.
    Copyright 2019 Netflix Inc.
    Copyright 2019 Google LLC
"""
__license__ = """
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

from threading import Event
from threading import Thread
from mqttclient import MqttClient
from mqttrequest import MqttRequest
from mqttwait import MqttWait
import logging
import json
import time


class RoundRobin:
    """Round robin through all the discovered media apps

    This class will walk through the list of discovered media apps, launching each one and
    waiting for it's status to change to "running".
    """
    def __init__(self, host, port):
        """Create the RoundRobin object."""
        self.host = host
        self.port = port
        self.mqtt_client = MqttClient(host, port)
        self.thread = None
        self.stop_event = Event()
        self.active_requests = []
        self.app_args = [
            {
                "app_id": "netflix",
                "startup-args": {"args": []},
                "args": {"args": ["m=https://api-global.netflix.com/catalog/titles/movie/80223967&trackId=14855318"]}},
            {
                "app_id": "prime-video",
                "startup-args": {"args": []},
                "args": {"contentId": "B015YJRQ8U", "mediaType": "movie"}},
            {
                "app_id": "youtube",
                "startup-args": {},
                "args": {"video_id": "KEcbFSaLpoM"}},
        ]
        self.logger = logging.getLogger(__name__)

    def execute(self, request):
        """Execute a request.

        Basically block until a request has been executed and the response returned.
        """
        self.active_requests.append(request)
        response = request.execute()
        self.active_requests.remove(request)
        return response

    @staticmethod
    def build_app_topic(app, topic):
        return "apps/{}/{}".format(app["app_id"], topic)

    @staticmethod
    def build_platform_topic(capability, topic):
        return "platform/{}/{}".format(capability, topic)

    def start_app(self, app, content_reference):
        """Attempt to launch the specified app.

        This function executes the start request and waits for the app status to change to "running".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "app/start"), json.dumps(content_reference))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/lifecycle"), {"status": "started"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for app status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def stop_app(self, app):
        """Attempt to launch the specified app.

        This function executes the start request and waits for the app status to change to "running".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "app/stop"), json.dumps({"args": []}))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/lifecycle"), {"status": "stopped"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for app status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def start_media(self, app, content_reference):
        """Attempt to play specified content on the specified app.

        This function executes the media/start request and waits for the media status to change to "playing".
        """
        req = MqttRequest(self.mqtt_client, self.build_app_topic(app, "media/start"), json.dumps(content_reference))
        response = self.execute(req)
        if response["status"] == 200:
            wait = MqttWait(self.mqtt_client, self.build_app_topic(app, "status/media"), {"status": "playing"}, 30.0)
            response = self.execute(wait)
            if not response:
                raise Exception('Timeout waiting for media status')
        else:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def send_input_event(self, app, device, key):
        req = MqttRequest(self.mqtt_client,
                          self.build_platform_topic("input", "{}/{}".format(device, key)),
                          json.dumps({"app_id": app["app_id"]}))
        response = self.execute(req)
        if response["status"] != 200:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def memory_monitor(self, app, action):
        """Control monitoring memory on the specified app.

        This function executes the telemetry/memory/monitor/<action> request.
        """
        req = MqttRequest(self.mqtt_client,
                          self.build_platform_topic("telemetry", "{}/{}".format("monitor", action)),
                          json.dumps({"app_id": app["app_id"]}))
        response = self.execute(req)
        if response["status"] != 200:
            raise Exception('Bad status from request: ' + json.dumps(response))

    def run_test(self, app):
        self.logger.info("Running test on {}".format(app["name"]))
        self.logger.info("Starting app...")
        if not self.stop_event.is_set():
            # Start the app and wait for it to startup
            content_reference = next(args for args in self.app_args if args["app_id"] == app["app_id"])
            if content_reference:
                self.start_app(app, content_reference["startup-args"])
            else:
                raise Exception("No args found for app {}.".format(app["name"]))

            time.sleep(1)

        self.memory_monitor(app, "start")

        if app["name"] == "Netflix":
            # Enter the default profile for Netflix
            time.sleep(5)
            self.send_input_event(app, "remote", "OK")
            time.sleep(2)

        if app["name"] == "Prime Video":
            self.send_input_event(app, "remote", "down")
            time.sleep(2)

            self.send_input_event(app, "remote", "down")
            time.sleep(2)

        self.send_input_event(app, "remote", "right")
        time.sleep(2)

        self.send_input_event(app, "remote", "right")
        time.sleep(2)

        self.send_input_event(app, "remote", "down")
        time.sleep(2)

        self.logger.info("Starting playback...")
        if not self.stop_event.is_set():
            # Play some media
            content_reference = next(args for args in self.app_args if args["app_id"] == app["app_id"])
            if content_reference:
                self.start_media(app, content_reference["args"])
            else:
                raise Exception("No args found for app {}.".format(app["name"]))

        # Let playback start and run for a bit...
        sleep_time_seconds = 10
        self.logger.info("Play for {} seconds...".format(sleep_time_seconds))
        if not self.stop_event.is_set():
            time.sleep(sleep_time_seconds)

        # Pause playback
        self.send_input_event(app, "remote", "pause")
        time.sleep(5)
        self.send_input_event(app, "remote", "play")
        time.sleep(5)
        self.send_input_event(app, "remote", "pause")
        time.sleep(5)
        self.send_input_event(app, "remote", "play")
        time.sleep(5)

        self.memory_monitor(app, "stop")

        self.logger.info("Stopping app...")
        if not self.stop_event.is_set():
            # Stop the app
            self.stop_app(app)

        self.logger.info("Test complete.")

    def _start(self):
        """Private start function that starts the client and loops through the apps forever."""
        self.mqtt_client.start()
        discovered_apps = self.mqtt_client.get_discovered_apps()
        while not self.stop_event.is_set():
            for app in discovered_apps:
                self.run_test(app)

    def start(self):
        """Public start function to get the party started."""
        self.thread = Thread(target=self._start, daemon=True)
        self.thread.start()
        return self.thread

    def stop(self):
        """Party's over."""
        self.mqtt_client.stop()
        self.stop_event.set()
        for active_request in self.active_requests:
            active_request.cancel()

Copyright 2019 Amazon.com, Inc. or its affiliates.     Copyright 2019 Netflix Inc.     Copyright 2019 Google LLC Licensed under the Apache License, Version 2.0 (the "License");     you may not use this file except in compliance with the License.     You may obtain a copy of the License at          http://www.apache.org/licenses/LICENSE-2.0      Unless required by applicable law or agreed to in writing, software     distributed under the License is distributed on an "AS IS" BASIS,     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     See the License for the specific language governing permissions and     limitations under the License. Round robin through all the discovered media apps      This class will walk through the list of discovered media apps, launching each one and     waiting for it's status to change to "running". Create the RoundRobin object. Execute a request.          Basically block until a request has been executed and the response returned. Attempt to launch the specified app.          This function executes the start request and waits for the app status to change to "running". Attempt to launch the specified app.          This function executes the start request and waits for the app status to change to "running". Attempt to play specified content on the specified app.          This function executes the media/start request and waits for the media status to change to "playing". Control monitoring memory on the specified app.          This function executes the telemetry/memory/monitor/<action> request. # Start the app and wait for it to startup # Enter the default profile for Netflix # Play some media # Let playback start and run for a bit... # Pause playback # Stop the app Private start function that starts the client and loops through the apps forever. Public start function to get the party started. Party's over.