#MIT License
#Copyright (c) 2017 Tim Wentzlau

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""
The display module holds classes for handling displays.
"""
import os
import threading
import time
from kervi.controllers.controller import Controller
from kervi.values import *
from kervi.sensors.sensor import Sensor
from kervi.values.value_list import ValueList
from kervi.actions import action 
from PIL import Image, ImageDraw


class _PageTimer(threading.Thread):
    def __init__(self, display):
        threading.Thread.__init__(self, name="pageTimer")
        self.deamon = True
        self._display = display
        self._terminated = False

    def stop(self):
        self._terminated = True
    
    def run(self):
        while not self._terminated:
            self._display.activate_page("next")
            time.sleep(self._display.page_change_speed)

class Display(Controller):
    r"""
    The display class manage a display device.



    :param display_id:
            Id of the display. This id is never displayed it is used to reference the display in code.
    :type display_id: ``str``

    :param name:
            Name of the display.
    :type name: ``str``

    :param device:
        The display device that should be used. Could be one of the displays from the kervi device library
        or a display device driver that inherits from kervi.hal.DisplayDeviceDriver
    :type device: ``DisplayDeviceDriver``

    """
    def __init__(self, display_id, name, device_driver):
        Controller.__init__(self, display_id, name)
        self._device = device_driver
        self.text = self.inputs.add("text", "Text", StringValue)
        self._bitmap = None
        self._display_active = False
        self._line_height = 0
        self._font = None
        self._background_color=(0)
        self._text_color = (255)
        self._pages = {}
        self._active_page = None
        self._page_change_speed = 5
        self._page_timer = None
        self._bitmap_lock = threading.Lock()
    
    def add_page(self, page, default = True):
        r"""
        Add a display page to the display.

        :param page:  Page to be added
        :type display_id: ``DisplayPage``

        :param default: True if this page should be shown upon initialization.
            
        :type name: ``bool``
        """
        self._pages[page.page_id] = page
        page._add_display(self)
        if default or not self._active_page:
            self._active_page = page
    
    @property
    def page_change_speed(self):
        return self._page_change_speed

    @page_change_speed.setter
    def page_change_speed(self, value):
        self._page_change_speed = value
    
    @property
    def display_pages(self):
        r"""
        Returns the display pages added to this display 
        """
        return self._pages

    @property
    def active_page(self):
        r"""
        The current active display page or None if no pages are added.
        """
        return self._active_page

    @action
    def activate_page(self, page_id):
        r"""
        Activates a display page. Content of the active page is shown in the display.

        :param page_id:  Id of page to activate
        :type page_id: ``str``

        """
        if page_id == "next":
            page_keys = list(self._pages.keys())
            key_count = len(page_keys)
            if key_count > 0:
                idx = page_keys.index(self._active_page.page_id)
                if idx >= key_count-1:
                    idx = 0
                else:
                    idx += 1
                self._active_page = self._pages[page_keys[idx]]
                self._active_page._render()
                self._page_changed(self._active_page)

        elif page_id in self._pages:
            self._active_page = self._pages[page_id]
            self._page_changed(self._active_page)
        else:
            raise ValueError("Page with {page_id} not found in page list".format(page_id = page_id))

    @property
    def bitmap(self):
        return self._bitmap

    @bitmap.setter
    def bitmap(self, value):
        self._bitmap = value

    @property
    def line_height(self):
        return self._line_height

    @line_height.setter
    def line_height(self, value):
        self._line_height = value

    @property
    def font(self):
        if not self._font:
            self._font = self._get_font()
        return self._font
    
    @action
    def display_active(self, state=True):
        self._display_active = state
        self._device.enable_display(self._display_active)

    @display_active.set_interrupt
    def display_active_interupt(self):
        self._display_active = False
        self._device.enable_display(self._display_active)

    @action
    def activate_page_scroll(self):
        if not self._page_timer:
            self._page_timer = _PageTimer(self)
            self._page_timer.start()

    @activate_page_scroll.set_interrupt
    def activate_page_scroll_interrupt(self):
        if self._page_timer:
            self._page_timer.stop()
            self._page_timer.join()
            self._page_timer = None

    @action
    def scroll_v(self):
        pass

    @action
    def scroll_h(self):
        pass

    def _get_font(self, size=8, name="PressStart2P.ttf"):
        """
        Returns a font that can be used by pil image functions.
        This default font is "SourceSansVariable-Roman" that is available on all platforms.
        """
        import kervi.vision as vision
        from PIL import ImageFont
        vision_path = os.path.dirname(vision.__file__)
        fontpath = os.path.join(vision_path, "fonts", name)
        font = ImageFont.truetype(fontpath, size)
        return font
    
    def _page_changed(self, changed_page):
        if self._active_page == changed_page:
            self.text.value = changed_page.text
    
    def input_changed(self, changed_input):
        if changed_input == self.text:
            if self._device.display_type == "char":
                self._device.message(self.text.value)
            else:
                line_count = len(self.text.value.split("\n"))
                line_height = self._line_height
                if line_height == 0:
                    line_height = int(self._device.height / line_count )
                    if line_height < 16:
                        line_height = 8
                #print("l", line_height, line_count, self._device.height)
                if line_height == 8:
                    font = self._get_font(line_height, "PixelOperatorMono8-Bold.ttf")
                else:
                    font = self._get_font(line_height, "PixelOperatorMono-Bold.ttf")
                image = Image.new('1', size=(self._device.width, self._device.height), color=self._background_color)
                draw = ImageDraw.Draw(image)
                #draw.text((0, 0), self.text.value, font=self.font, fill=self._text_color)
                draw.multiline_text((0, 0), self.text.value, font=font, fill=self._text_color, spacing=2)
                self._bitmap_lock.acquire()
                try:
                    self._device.image(image)
                    self._device.display()
                finally:
                    self._bitmap_lock.release()
                
    def controller_start(self):
        if self._active_page:
            self._active_page._render()

    def controller_exit(self):
        if self._page_timer:
            self._page_timer.stop()


class _DisplayLink:
    def __init__(self, transform, value_id, link_id, x=0, y=0, size=0):
        self.transform = transform
        self.link_id = link_id
        self.value_id = value_id
        self.x= x
        self.y = y
        self.size = size
        self.value = ""
        self.unit = ""

    def _set_value(self, value, unit):
        if self.transform:
            self.value = self.transform(value)
        else:
            self.value = value

        self.unit = unit

class DisplayPage(Controller):
    def __init__(self, page_id, name = None):
        Controller.__init__(self, page_id, name)
        self.spine.register_event_handler("valueChanged", self._link_changed_event)
        self._template = ""
        self._links = {}
        self._displays = []
        self._text = None
        self._lines = 1

    def _link_changed_event(self, source_id, value, x):
        #print("lc", source_id, value)
        if source_id in self._links.keys():
            link = self._links[source_id]
            link._set_value(value["display_value"], value["display_unit"])
            self._render()

    def _add_display(self, display):
        self._displays.append(display)
    
    @property
    def line_count(self):
        return self._lines

    @property
    def text(self):
        return self._text
   
    @property
    def page_id(self):
        return self.component_id 

    @property
    def template(self):
        return self._template

    @template.setter
    def template(self, value):
        self._template = value
        self._lines = len(self._template.split("\n"))
    
    def _render(self):
        kwargs = {}
        for link_id in self._links:
            link = self._links[link_id]
            kwargs[link.link_id] = link.value
            kwargs[link.link_id + "_unit"] = link.unit
        self._text = self.template.format(self._template, **kwargs)
        for display in self._displays:
            display._page_changed(self) 
    
    def _transform(self, v, value_format):
        if value_format:
            return str.format("{:" + value_format + "}", v)
        else:
            return str(v)
    
    def input_changed(self, value):
        self._render()


    def link_value(self, source, format=None):
        id = None
        value_id = None
        if isinstance(source, Sensor):
            id = source.sensor_id
            value_id = source._sensor_value.value_id
        elif isinstance(source, KerviValue):
            id = source.value_id
            value_id = id
        elif isinstance(source, str):
            id = source
            value_id = id
        if id:
            self.spine.register_event_handler("valueChanged", self._link_changed_event, value_id)
            self._links[value_id] = _DisplayLink(lambda x: self._transform(x, format), value_id, id)
        else:
            raise ValueError("Source must be a KerviValue, Sensor or string")