import kivy
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup

from connector import *

kivy.require('1.10.0')

import asyncio
import threading

from kivy.app import App
from kivy.clock import mainthread
from kivy.event import EventDispatcher
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

# main Layout class. see .kv file
class RootLayout(BoxLayout):
    def popup_server_settings(self):
        show = ServerSettings()

        # setup server settings window
        settingsWindow = Popup(title='Server settings',
                            content=show,
                            size_hint=(None, None),
                            size=(400, 400))
        # start popup new window with server settings
        settingsWindow.open()

# Server settings poupup window. Description in .kv file
class ServerSettings(FloatLayout):
    pass

# main async dispatcher
class EventLoopWorker(EventDispatcher):
    __events__ = ('on_pulse',)  # defines this EventDispatcher's sole event

    def __init__(self):
        print('_init_. create obj corutine')
        super().__init__()
        self._thread = threading.Thread(target=self._run_loop)  # note the Thread target here
        self._thread.daemon = True
        self.loop = None
        # the following are for the pulse() coroutine, see below
        self._default_pulse = ['Tacho', 'status']
        self._pulse = None
        self._pulse_task = None

    def _run_loop(self):
        self.loop = asyncio.get_event_loop_policy().new_event_loop()
        asyncio.set_event_loop(self.loop)

        # asyncio.run(tcp_echo_client('Hello World!'))

        self._restart_pulse()
        # this example doesn't include any cleanup code, see the docs on how
        # to properly set up and tear down an asyncio event loop
        self.loop.run_forever()

    def start(self):
        self._thread.start()

    async def pulse(self):
        print('pulse')
        """Core coroutine of this asyncio event loop.

        Repeats a pulse message in a short interval on three channels:

        - using `print()`
        - by dispatching a Kivy event `on_pulse` with the help of `@mainthread`
        - on the Kivy thread through `kivy_update_status()` with the help of
          `@mainthread`

        The decorator `@mainthread` is a convenience wrapper around
        `Clock.schedule_once()` which ensures the callables run on the Kivy
        thread.
        """

        for msg in self._pulse_messages():
            # show it through the console:
            print('Incomming message:', msg)

            # `EventLoopWorker` is an `EventDispatcher` to which others can
            # subscribe. See `display_on_pulse()` in `start_event_loop_thread()`
            # on how it is bound to the `on_pulse` event.  The indirection
            # through the `notify()` function is necessary to apply the
            # `@mainthread` decorator (left label):
            @mainthread
            def notify(text):
                self.dispatch('on_pulse', text)

            notify(msg)  # dispatch the on_pulse event

            # Same, but with a direct call instead of an event (right label):
            @mainthread
            def kivy_update_status(text):
                status_label = App.get_running_app().root.ids.status
                status_label.text = text

            kivy_update_status(msg)  # control a Label directly

            await asyncio.sleep(1)

    def set_pulse_text(self, text):
        self._pulse = text
        # it's not really necessary to restart this task; just included for the
        # sake of this example.  Comment this line out and see what happens.
        self._restart_pulse()

    def _restart_pulse(self):
        """Helper to start/reset the pulse task when the pulse changes."""
        if self._pulse_task is not None:
            self._pulse_task.cancel()
        self._pulse_task = self.loop.create_task(self.pulse())

    def on_pulse(self, *_):
        """An EventDispatcher event must have a corresponding method."""
        pass

    def _pulse_messages(self):
        """A generator providing an inexhaustible supply of pulse messages."""
        while True:
            if isinstance(self._pulse, str) and self._pulse != '':
                pulse = self._pulse.split()
                yield from pulse
            else:
                yield from self._default_pulse


# main class for starting kiyv application
class AsyncTcaApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.event_loop_worker = None

    def build(self):
        print('start RootLayout')
        return RootLayout()

    def start_event_loop_thread(self):
        print('start start_event_loop_thread main loop')
        """Start the asyncio event loop thread. Bound to the top button."""
        if self.event_loop_worker is not None:
            return
        self.root.ids.btn.text = ("Authorization is running")
        self.event_loop_worker = worker = EventLoopWorker()
        pulse_listener_label = self.root.ids.pulse_listener

        def display_on_pulse(instance, text):
            pulse_listener_label.text = text

        # make the label react to the worker's `on_pulse` event:
        worker.bind(on_pulse=display_on_pulse)
        worker.start()

    def submit_pulse_text(self, text):
        """Send the TextInput string over to the asyncio event loop worker."""
        worker = self.event_loop_worker
        if worker is not None:
            loop = self.event_loop_worker.loop
            # use the thread safe variant to run it on the asyncio event loop:
            loop.call_soon_threadsafe(worker.set_pulse_text, text)


if __name__ == '__main__':
    AsyncTcaApp().run()