# Copyright (c) 2016-2017 Anki, Inc.
#
# 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 in the file LICENSE.txt or 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.

'''This module provides a simple GUI viewer for Cozmo's camera.

It uses Tkinter, the standard Python GUI toolkit which is optionally available
on most platforms, and also depends on the Pillow and numpy libraries for
image processing.


See the online SDK documentation for details on how to install these extra
packages on your platform.

The easiest way to make use of this viewer is to call
:func:`cozmo.run.connect_with_tkviewer`.

Warning:
    This package requires Python to have Tkinter installed to display the GUI.
'''


# __all__ should order by constants, event classes, other classes, functions.
__all__ = ['TkImageViewer']

import cozmo
import collections
import functools
import queue
import platform
import time

from PIL import Image, ImageDraw, ImageTk
import tkinter

from . import world


class TkThreadable:
    '''A mixin for adding threadsafe calls to tkinter methods.'''
    #pylint: disable=no-member
    # no-member errors are raised in pylint regarding members/methods called but not defined in our mixin.
    def __init__(self, *a, **kw):
        self._thread_queue = queue.Queue()
        self.after(50, self._thread_call_dispatch)

    def call_threadsafe(self, method, *a, **kw):
        self._thread_queue.put((method, a, kw))

    def _thread_call_dispatch(self):
        while True:
            try:
                method, a, kw = self._thread_queue.get(block=False)
                self.after_idle(method, *a, **kw)
            except queue.Empty:
                break
        self.after(50, self._thread_call_dispatch)


class TkImageViewer(tkinter.Frame, TkThreadable):
    '''Simple Tkinter camera viewer.'''

    # TODO: rewrite this whole thing.  Make a generic camera widget
    # that can be used in other Tk applications.  Also handle resizing
    # the window properly.
    def __init__(self,
            tk_root=None, refresh_interval=10, image_scale = 2,
            window_name = "CozmoView", force_on_top=True):
        if tk_root is None:
            tk_root = tkinter.Tk()
        tkinter.Frame.__init__(self, tk_root)
        TkThreadable.__init__(self)
        self._img_queue = collections.deque(maxlen=1)

        self._refresh_interval = refresh_interval
        self.scale = image_scale
        self.width = None
        self.height = None

        self.tk_root = tk_root
        tk_root.wm_title(window_name)
        # Tell the TK root not to resize based on the contents of the window.
        # Necessary to get the resizing to function properly
        tk_root.pack_propagate(False)
        # Set the starting window size
        tk_root.geometry('{}x{}'.format(720, 540))

        self.label = tkinter.Label(self.tk_root,image=None)
        self.tk_root.protocol("WM_DELETE_WINDOW", self._delete_window)
        self._isRunning = True
        self.robot = None
        self.handler = None
        self._first_image = True
        tk_root.aspect(4,3,4,3)

        if force_on_top:
            # force window on top of all others, regardless of focus
            tk_root.wm_attributes("-topmost", 1)

        self.tk_root.bind("<Configure>", self.configure)
        self._repeat_draw_frame()

    async def connect(self, coz_conn):
        self.robot = await coz_conn.wait_for_robot()
        self.robot.camera.image_stream_enabled = True
        self.handler = self.robot.world.add_event_handler(
            world.EvtNewCameraImage, self.image_event)

    def disconnect(self):
        if self.handler:
            self.handler.disable()
        self.call_threadsafe(self.quit)

    # The base class configure doesn't take an event
    #pylint: disable=arguments-differ
    def configure(self, event):
        if event.width < 50 or event.height < 50:
            return
        self.height = event.height
        self.width = event.width


    def image_event(self, evt, *, image, **kw):
        if self._first_image or self.width is None:
            img = image.annotate_image(scale=self.scale)
        else:
            img = image.annotate_image(fit_size=(self.width, self.height))
        self._img_queue.append(img)

    def _delete_window(self):
        self.tk_root.destroy()
        self.quit()
        self._isRunning = False

    def _draw_frame(self):
        if ImageTk is None:
            return

        try:
            image = self._img_queue.popleft()
        except IndexError:
            # no new image
            return

        self._first_image = False
        photoImage = ImageTk.PhotoImage(image)

        self.label.configure(image=photoImage)
        self.label.image = photoImage
        # Dynamically expand the image to fit the window. And fill in both X and Y directions.
        self.label.pack(fill=tkinter.BOTH, expand=True)

    def _repeat_draw_frame(self, event=None):
        self._draw_frame()
        self.after(self._refresh_interval, self._repeat_draw_frame)
