# -*- coding: utf-8 -*-
from __future__ import absolute_import
import warnings

from imgui_bundle import imgui

from pyglet.window import key, mouse, Window
import pyglet
import pyglet.clock


from imgui_bundle.python_backends import compute_fb_scale
from imgui_bundle.python_backends.opengl_backend import (
    FixedPipelineRenderer,
    ProgrammablePipelineRenderer,
)


class PygletMixin(object):
    _gui_time = None

    MOUSE_CURSORS = {
        imgui.MouseCursor_.arrow.value: Window.CURSOR_DEFAULT,
        imgui.MouseCursor_.text_input.value: Window.CURSOR_TEXT,
        imgui.MouseCursor_.resize_all.value: Window.CURSOR_SIZE,
        imgui.MouseCursor_.resize_ns.value: Window.CURSOR_SIZE_UP_DOWN,
        imgui.MouseCursor_.resize_ew.value: Window.CURSOR_SIZE_LEFT_RIGHT,
        imgui.MouseCursor_.resize_nesw.value: Window.CURSOR_SIZE_DOWN_LEFT,
        imgui.MouseCursor_.resize_nwse.value: Window.CURSOR_SIZE_DOWN_RIGHT,
        imgui.MouseCursor_.hand.value: Window.CURSOR_HAND,
    }

    def __init__(self):
        super(PygletMixin, self).__init__()
        self._cursor = -2
        self._window = None
        # Let Dear imgui know we have mouse cursor support
        self.io = imgui.get_io()
        self.io.backend_flags |= imgui.BackendFlags_.has_mouse_cursors
        self._map_keys()

    def _map_keys(self):
        self.key_map = {}
        key_map = self.key_map

        # Control Keys
        key_map[key.TAB] = imgui.Key.tab
        key_map[key.BACKSPACE] = imgui.Key.backspace
        key_map[key.RETURN] = imgui.Key.enter
        key_map[key.ESCAPE] = imgui.Key.escape
        key_map[key.INSERT] = imgui.Key.insert
        key_map[key.DELETE] = imgui.Key.delete
        key_map[key.SPACE] = imgui.Key.space

        # Function Keys
        key_map[key.F1] = imgui.Key.f1
        key_map[key.F2] = imgui.Key.f2
        key_map[key.F3] = imgui.Key.f3
        key_map[key.F4] = imgui.Key.f4
        key_map[key.F5] = imgui.Key.f5
        key_map[key.F6] = imgui.Key.f6
        key_map[key.F7] = imgui.Key.f7
        key_map[key.F8] = imgui.Key.f8
        key_map[key.F9] = imgui.Key.f9
        key_map[key.F10] = imgui.Key.f10
        key_map[key.F11] = imgui.Key.f11
        key_map[key.F12] = imgui.Key.f12

        # Navigation Keys
        key_map[key.LEFT] = imgui.Key.left_arrow
        key_map[key.RIGHT] = imgui.Key.right_arrow
        key_map[key.UP] = imgui.Key.up_arrow
        key_map[key.DOWN] = imgui.Key.down_arrow
        key_map[key.PAGEUP] = imgui.Key.page_up
        key_map[key.PAGEDOWN] = imgui.Key.page_down
        key_map[key.HOME] = imgui.Key.home
        key_map[key.END] = imgui.Key.end

        # Numeric Keys
        key_map[key._0] = imgui.Key._0
        key_map[key._1] = imgui.Key._1
        key_map[key._2] = imgui.Key._2
        key_map[key._3] = imgui.Key._3
        key_map[key._4] = imgui.Key._4
        key_map[key._5] = imgui.Key._5
        key_map[key._6] = imgui.Key._6
        key_map[key._7] = imgui.Key._7
        key_map[key._8] = imgui.Key._8
        key_map[key._9] = imgui.Key._9

        # Keypad Keys
        key_map[key.NUM_ENTER] = imgui.Key.keypad_enter
        key_map[key.NUM_0] = imgui.Key.keypad0
        key_map[key.NUM_1] = imgui.Key.keypad1
        key_map[key.NUM_2] = imgui.Key.keypad2
        key_map[key.NUM_3] = imgui.Key.keypad3
        key_map[key.NUM_4] = imgui.Key.keypad4
        key_map[key.NUM_5] = imgui.Key.keypad5
        key_map[key.NUM_6] = imgui.Key.keypad6
        key_map[key.NUM_7] = imgui.Key.keypad7
        key_map[key.NUM_8] = imgui.Key.keypad8
        key_map[key.NUM_9] = imgui.Key.keypad9
        key_map[key.NUM_DECIMAL] = imgui.Key.keypad_decimal
        key_map[key.NUM_DIVIDE] = imgui.Key.keypad_divide
        key_map[key.NUM_MULTIPLY] = imgui.Key.keypad_multiply
        key_map[key.NUM_SUBTRACT] = imgui.Key.keypad_subtract
        key_map[key.NUM_ADD] = imgui.Key.keypad_add

        # Alphabetic Keys
        key_map[key.A] = imgui.Key.a
        key_map[key.B] = imgui.Key.b
        key_map[key.C] = imgui.Key.c
        key_map[key.D] = imgui.Key.d
        key_map[key.E] = imgui.Key.e
        key_map[key.F] = imgui.Key.f
        key_map[key.G] = imgui.Key.g
        key_map[key.H] = imgui.Key.h
        key_map[key.I] = imgui.Key.i
        key_map[key.J] = imgui.Key.j
        key_map[key.K] = imgui.Key.k
        key_map[key.L] = imgui.Key.l
        key_map[key.M] = imgui.Key.m
        key_map[key.N] = imgui.Key.n
        key_map[key.O] = imgui.Key.o
        key_map[key.P] = imgui.Key.p
        key_map[key.Q] = imgui.Key.q
        key_map[key.R] = imgui.Key.r
        key_map[key.S] = imgui.Key.s
        key_map[key.T] = imgui.Key.t
        key_map[key.U] = imgui.Key.u
        key_map[key.V] = imgui.Key.v
        key_map[key.W] = imgui.Key.w
        key_map[key.X] = imgui.Key.x
        key_map[key.Y] = imgui.Key.y
        key_map[key.Z] = imgui.Key.z

        # Modifier Keys
        key_map[key.LCTRL] = imgui.Key.left_ctrl
        key_map[key.RCTRL] = imgui.Key.right_ctrl
        key_map[key.LSHIFT] = imgui.Key.left_shift
        key_map[key.RSHIFT] = imgui.Key.right_shift
        key_map[key.LALT] = imgui.Key.left_alt
        key_map[key.RALT] = imgui.Key.right_alt
        key_map[key.LCOMMAND] = imgui.Key.left_super
        key_map[key.RCOMMAND] = imgui.Key.right_super

        # Symbol Keys
        key_map[key.GRAVE] = imgui.Key.grave_accent  # `
        key_map[key.MINUS] = imgui.Key.minus         # -
        key_map[key.EQUAL] = imgui.Key.equal         # =
        key_map[key.BRACKETLEFT] = imgui.Key.left_bracket  # [
        key_map[key.BRACKETRIGHT] = imgui.Key.right_bracket  # ]
        key_map[key.BACKSLASH] = imgui.Key.backslash  # \
        key_map[key.SEMICOLON] = imgui.Key.semicolon  # ;
        key_map[key.APOSTROPHE] = imgui.Key.apostrophe  # '
        key_map[key.COMMA] = imgui.Key.comma  # ,
        key_map[key.PERIOD] = imgui.Key.period  # .
        key_map[key.SLASH] = imgui.Key.slash  # /

        # Miscellaneous Keys
        key_map[key.PRINT] = imgui.Key.print_screen
        key_map[key.PAUSE] = imgui.Key.pause

        # Lock Keys
        key_map[key.CAPSLOCK] = imgui.Key.caps_lock
        key_map[key.NUMLOCK] = imgui.Key.num_lock
        key_map[key.SCROLLLOCK] = imgui.Key.scroll_lock

    def _set_pixel_ratio(self, window):
        window_size = window.get_size()
        self.io.display_size = window_size
        # It is conceivable that the pyglet version will not be solely
        # determinant of whether we use the fixed or programmable, so do some
        # minor introspection here to check.
        if hasattr(window, "get_viewport_size"):
            viewport_size = window.get_viewport_size()
            self.io.display_framebuffer_scale = compute_fb_scale(
                window_size, viewport_size
            )
        elif hasattr(window, "get_pixel_ratio"):
            self.io.display_framebuffer_scale = (
                window.get_pixel_ratio(),
                window.get_pixel_ratio(),
            )
        else:
            # Default to 1.0 in this unlikely circumstance
            self.io.display_fb_scale = (1.0, 1.0)

    def _attach_callbacks(self, window):
        self._window = window
        window.push_handlers(
            self.on_mouse_motion,
            self.on_key_press,
            self.on_key_release,
            self.on_text,
            self.on_mouse_drag,
            self.on_mouse_press,
            self.on_mouse_release,
            self.on_mouse_scroll,
            self.on_resize,
        )

    def _handle_mouse_cursor(self):
        if self.io.config_flags & imgui.ConfigFlags_.no_mouse_cursor_change.value:
            return

        mouse_cursor = imgui.get_mouse_cursor()
        window = self._window
        if self._cursor != mouse_cursor:
            self._cursor = mouse_cursor
            if mouse_cursor == imgui.MouseCursor_.none:
                window.set_mouse_visible(False)
            else:
                cursor = self.MOUSE_CURSORS.get(mouse_cursor)
                window.set_mouse_cursor(window.get_system_mouse_cursor(cursor))

    def _on_key(self, key_pressed, down):
        if key_pressed in self.key_map:
            imgui_key = self.key_map[key_pressed]
            self.io.add_key_event(imgui_key, down=down)

            # Handle modifiers, since ImGui has an additional mod_ctrl / shift / etc
            if imgui_key == imgui.Key.left_ctrl or imgui_key == imgui.Key.right_ctrl:
                self.io.add_key_event(imgui.Key.mod_ctrl, down)
            if imgui_key == imgui.Key.left_shift or imgui_key == imgui.Key.right_shift:
                self.io.add_key_event(imgui.Key.mod_shift, down)
            if imgui_key == imgui.Key.left_alt or imgui_key == imgui.Key.right_alt:
                self.io.add_key_event(imgui.Key.mod_alt, down)
            if imgui_key == imgui.Key.left_super or imgui_key == imgui.Key.right_super:
                self.io.add_key_event(imgui.Key.mod_super, down)

    def on_key_press(self, key_pressed, mods):
        self._on_key(key_pressed, True)

    def on_key_release(self, key_released, mods):
        self._on_key(key_released, False)

    def on_text(self, text):
        io = imgui.get_io()

        for char in text:
            io.add_input_character(ord(char))

    def on_mouse_motion(self, x, y, dx, dy):
        self.io.add_mouse_pos_event(x, self.io.display_size.y - y)

    def _on_mouse_button(self, pyglet_button, down):
        imgui_button = 0
        if pyglet_button == mouse.LEFT:
            imgui_button = 0
        elif pyglet_button == mouse.RIGHT:
            imgui_button = 1
        elif pyglet_button == mouse.MIDDLE:
            imgui_button = 2
        self.io.add_mouse_button_event(imgui_button, down)
    def on_mouse_press(self, x, y, button, modifiers):
        self._on_mouse_button(button, True)

    def on_mouse_release(self, x, y, button, modifiers):
        self._on_mouse_button(button, False)

    def on_mouse_drag(self, x, y, dx, dy, button, modifiers):
        self._on_mouse_button(button, True)
        self.on_mouse_motion(x, y, dx, dy)

    def on_mouse_scroll(self, x, y, mods, scroll):
        self.io.add_mouse_wheel_event(0, -scroll)

    def on_resize(self, width, height):
        self.io.display_size = width, height

    def process_inputs(self):
        io = imgui.get_io()

        current_time = pyglet.clock.tick()

        if self._gui_time:
            io.delta_time = current_time - self._gui_time
        else:
            io.delta_time = 1.0 / 60.0
        if io.delta_time <= 0.0:
            io.delta_time = 1.0 / 1000.0
        self._gui_time = current_time


class PygletFixedPipelineRenderer(PygletMixin, FixedPipelineRenderer):
    def __init__(self, window, attach_callbacks=True):
        super(PygletFixedPipelineRenderer, self).__init__()
        self._set_pixel_ratio(window)
        if attach_callbacks:
            self._attach_callbacks(window)

    def render(self, draw_data):
        super(PygletFixedPipelineRenderer, self).render(draw_data)
        self._handle_mouse_cursor()


class PygletProgrammablePipelineRenderer(PygletMixin, ProgrammablePipelineRenderer):
    def __init__(self, window, attach_callbacks=True):
        super(PygletProgrammablePipelineRenderer, self).__init__()
        self._set_pixel_ratio(window)
        if attach_callbacks:
            self._attach_callbacks(window)

    def render(self, draw_data):
        super(PygletProgrammablePipelineRenderer, self).render(draw_data)
        self._handle_mouse_cursor()


class PygletRenderer(PygletFixedPipelineRenderer):
    def __init__(self, window, attach_callbacks=True):
        warnings.warn(
            "PygletRenderer is deprecated; please use either "
            "PygletFixedPipelineRenderer (for OpenGL 2.1, pyglet < 2.0) or "
            "PygletProgrammablePipelineRenderer (for later versions) or "
            "create_renderer(window) to auto-detect.",
            DeprecationWarning,
            stacklevel=2,
        )
        super(PygletRenderer, self).__init__(window, attach_callbacks)


def create_renderer(window, attach_callbacks=True):
    """
    This is a helper function that wraps the appropriate version of the Pyglet
    renderer class, based on the version of pyglet being used.
    """
    # Determine the context version
    # Pyglet < 2.0 has issues with ProgrammablePipeline even when the context
    # is OpenGL 3, so we need to check the pyglet version rather than looking
    # at window.config.major_version to see if we want to use programmable.
    if int(pyglet.version.split('.')[0]) < 2:
        return PygletFixedPipelineRenderer(window, attach_callbacks)
    else:
        return PygletProgrammablePipelineRenderer(window, attach_callbacks)
