# coding=utf-8
###############################################################################
#
#  This file is part of pyglfw project which is subject to zlib license.
#  See the LICENSE file for details.
#
#  Copyright (c) 2013   Roman Valov <roman.valov@gmail.com>
#
###############################################################################

from . import _wrapapi as api
from .common import _str, _utf, _unichr
from .hint import Hints
from .inputs import Keys, Mice
from .monitor import _monitor_obj


class Window(object):
    _instance_ = {}
    _contexts_ = []

    def __init__(self, width, height, title, monitor=None, shared=None):
        mon_handle = monitor and monitor.handle or None
        shr_handle = shared and shared.handle or None
        win_handle = api.glfwCreateWindow(width, height, _utf(title),
                                          mon_handle, shr_handle)

        self.handle = win_handle.get_void_p()
        self.__class__._instance_[self.handle.value] = self

        self.mice = Mice(self.handle)
        self.keys = Keys(self.handle)

        self._key_callback = None
        self._char_callback = None
        self._scroll_callback = None
        self._cursor_enter_callback = None
        self._cursor_pos_callback = None
        self._mouse_button_callback = None

        self._window_pos_callback = None
        self._window_size_callback = None
        self._window_close_callback = None
        self._window_refresh_callback = None
        self._window_focus_callback = None
        self._window_iconify_callback = None
        self._framebuffer_size_callback = None

    def __enter__(self):
        api.glfwMakeContextCurrent(self.handle)
        self.__class__._contexts_ += [self]
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self.__class__._contexts_:
            _ctx = self.__class__._contexts_.pop().handle
            api.glfwMakeContextCurrent(_ctx)
        else:
            api.glfwMakeContextCurrent(None)
        return False

    def make_current(self):
        api.glfwMakeContextCurrent(self.handle)

    @classmethod
    def find_current(cls):
        find_handle = api.glfwGetCurrentContext().get_void_p()
        if bool(find_handle):
            return cls._instance_.get(find_handle.value)
        else:
            return None

    def close(self):
        api.glfwDestroyWindow(self.handle)

    @property
    def should_close(self):
        return bool(api.glfwWindowShouldClose(self.handle))

    @should_close.setter
    def should_close(self, flag):
        api.glfwSetWindowShouldClose(self.handle, flag)

    def swap_buffers(self):
        api.glfwSwapBuffers(self.handle)

    def swap_interval(self, interval):
        with self:
            api.glfwSwapInterval(interval)

    def set_title(self, title):
        api.glfwSetWindowTitle(self.handle, title)

    @property
    def framebuffer_size(self):
        return api.glfwGetFramebufferSize(self.handle)

    @property
    def pos(self):
        return api.glfwGetWindowPos(self.handle)

    @pos.setter
    def pos(self, x_y):
        api.glfwSetWindowPos(self.handle, *x_y)

    @property
    def size(self):
        return api.glfwGetWindowSize(self.handle)

    @size.setter
    def size(self, x_y):
        api.glfwSetWindowSize(self.handle, *x_y)

    def iconify(self):
        api.glfwIconifyWindow(self.handle)

    def restore(self):
        api.glfwRestoreWindow(self.handle)

    def _get_attrib(self, attrib):
        return api.glfwGetWindowAttrib(self.handle, attrib)

    @property
    def iconified(self):
        return bool(self._get_attrib(api.GLFW_ICONIFIED))

    @iconified.setter
    def iconified(self, flag):
        if flag:
            self.iconify()
        else:
            self.restore()

    def hide(self):
        api.glfwHideWindow(self.handle)

    def show(self):
        api.glfwShowWindow(self.handle)

    @property
    def visible(self):
        return bool(self._get_attrib(api.GLFW_VISIBLE))

    @visible.setter
    def visible(self, flag):
        if flag:
            self.show()
        else:
            self.hide()

    @property
    def has_focus(self):
        return bool(self._get_attrib(api.GLFW_FOCUSED))

    @property
    def resizable(self):
        return bool(self._get_attrib(api.GLFW_RESIZABLE))

    @property
    def decorated(self):
        return bool(self._get_attrib(api.GLFW_DECORATED))

    @property
    def context_version(self):
        return (self._get_attrib(api.GLFW_CONTEXT_VERSION_MAJOR),
                self._get_attrib(api.GLFW_CONTEXT_VERSION_MINOR),
                self._get_attrib(api.GLFW_CONTEXT_REVISION))

    @property
    def debug_context(self):
        return bool(self._get_attrib(api.GLFW_OPENGL_DEBUG_CONTEXT))

    @property
    def forward_compat(self):
        return bool(self._get_attrib(api.GLFW_OPENGL_FORWARD_COMPAT))

    OPENGL_API = api.GLFW_OPENGL_API
    OPENGL_ES_API = api.GLFW_OPENGL_ES_API

    @property
    def client_api(self):
        return self._get_attrib(api.GLFW_CLIENT_API)

    CORE_PROFILE = api.GLFW_OPENGL_CORE_PROFILE
    COMPAT_PROFILE = api.GLFW_OPENGL_COMPAT_PROFILE
    ANY_PROFILE = api.GLFW_OPENGL_ANY_PROFILE

    @property
    def opengl_profile(self):
        return self._get_attrib(api.GLFW_OPENGL_PROFILE)

    NO_ROBUSTNESS = api.GLFW_NO_ROBUSTNESS
    NO_RESET_NOTIFICATION = api.GLFW_NO_RESET_NOTIFICATION
    LOSE_CONTEXT_ON_RESET = api.GLFW_LOSE_CONTEXT_ON_RESET

    @property
    def context_robustness(self):
        return self._get_attrib(api.GLFW_CONTEXT_ROBUSTNESS)

    @staticmethod
    def hint(hints=None, **kwargs):
        if hints and kwargs:
            raise ValueError("Hints should be passed via object or via kwargs")

        if not hints:
            hints = Hints(**kwargs)

        if not hints._hints:
            api.glfwDefaultWindowHints()

        for hint, value in hints._hints.items():
            api.glfwWindowHint(hint, value)

    @property
    def monitor(self):
        moni = api.glfwGetWindowMonitor(self.handle)
        if bool(moni):
            return _monitor_obj(moni)
        else:
            return None

    @property
    def clipboard(self):
        return _str(api.glfwGetClipboardString(self.handle))

    @clipboard.setter
    def clipboard(self, buffer):
        api.glfwSetClipboardString(self.handler, _utf(buffer))

    _cursor_modes_get = {
        api.GLFW_CURSOR_DISABLED: None,
        api.GLFW_CURSOR_HIDDEN: False,
        api.GLFW_CURSOR_NORMAL: True,
    }

    _cursor_modes_set = {
        None: api.GLFW_CURSOR_DISABLED,
        False: api.GLFW_CURSOR_HIDDEN,
        True: api.GLFW_CURSOR_NORMAL,
    }

    @property
    def cursor_mode(self):
        libapi_cm = api.glfwGetInputMode(self.handle, api.GLFW_CURSOR)
        return self._cursor_modes_get.get(libapi_cm, None)

    @cursor_mode.setter
    def cursor_mode(self, mode):
        pyglfw_cm = self._cursor_modes_set.get(mode, None)
        api.glfwSetInputMode(self.handle, api.GLFW_CURSOR, pyglfw_cm)

    @property
    def sticky_keys(self):
        return bool(api.glfwGetInputMode(self.handle, api.GLFW_STICKY_KEYS))

    @sticky_keys.setter
    def sticky_keys(self, flag):
        api.glfwSetInputMode(self.handle, api.GLFW_STICKY_KEYS, flag)

    @property
    def sticky_mice(self):
        return bool(api.glfwGetInputMode(self.handle,
                                         api.GLFW_STICKY_MOUSE_BUTTONS))

    @sticky_mice.setter
    def sticky_mice(self, flag):
        api.glfwSetInputMode(self.handle, api.GLFW_STICKY_MOUSE_BUTTONS, flag)

    @property
    def cursor_pos(self):
        return api.glfwGetCursorPos(self.handle)

    @cursor_pos.setter
    def cursor_pos(self, x_y):
        api.glfwSetCursorPos(self.handle, *x_y)

    PRESS = api.GLFW_PRESS
    RELEASE = api.GLFW_RELEASE
    REPEAT = api.GLFW_REPEAT

    MOD_SHIFT = api.GLFW_MOD_SHIFT
    MOD_CONTROL = api.GLFW_MOD_CONTROL
    MOD_ALT = api.GLFW_MOD_ALT
    MOD_SUPER = api.GLFW_MOD_SUPER

    @classmethod
    def _wcb(cls, functype, func):
        if not func:
            return None

        def wrap(handle, *args, **kwargs):
            window = cls._instance_.get(handle.get_void_p().value, None)
            func(window, *args, **kwargs)
        return functype(wrap)

    def set_key_callback(self, callback):
        self._key_callback = self._wcb(api.GLFWkeyfun, callback)
        api.glfwSetKeyCallback(self.handle, self._key_callback)

    def set_char_callback(self, callback):
        def wrap(self, char):
            char = _unichr(char)
            callback(self, char)
        self._char_callback = self._wcb(api.GLFWcharfun, wrap)
        api.glfwSetCharCallback(self.handle, self._char_callback)

    def set_scroll_callback(self, callback):
        self._scroll_callback = self._wcb(api.GLFWscrollfun,
                                          callback)
        api.glfwSetScrollCallback(self.handle,
                                  self._scroll_callback)

    def set_cursor_enter_callback(self, callback):
        def wrap(self, flag):
            flag = bool(flag)
            callback(self, flag)
        self._cursor_enter_callback = self._wcb(api.GLFWcursorenterfun,
                                                wrap)
        api.glfwSetCursorEnterCallback(self.handle,
                                       self._cursor_enter_callback)

    def set_cursor_pos_callback(self, callback):
        self._cursor_pos_callback = self._wcb(api.GLFWcursorposfun,
                                              callback)
        api.glfwSetCursorPosCallback(self.handle,
                                     self._cursor_pos_callback)

    def set_mouse_button_callback(self, callback):
        self._mouse_button_callback = self._wcb(api.GLFWmousebuttonfun,
                                                callback)
        api.glfwSetMouseButtonCallback(self.handle,
                                       self._mouse_button_callback)

    def set_window_pos_callback(self, callback):
        self._window_pos_callback = self._wcb(api.GLFWwindowposfun,
                                              callback)
        api.glfwSetWindowPosCallback(self.handle,
                                     self._window_pos_callback)

    def set_window_size_callback(self, callback):
        self._window_size_callback = self._wcb(api.GLFWwindowsizefun,
                                               callback)
        api.glfwSetWindowSizeCallback(self.handle,
                                      self._window_size_callback)

    def set_window_close_callback(self, callback):
        self._window_close_callback = self._wcb(api.GLFWwindowclosefun,
                                                callback)
        api.glfwSetWindowCloseCallback(self.handle,
                                       self._window_close_callback)

    def set_window_refresh_callback(self, callback):
        self._window_refresh_callback = self._wcb(api.GLFWwindowrefreshfun,
                                                  callback)
        api.glfwSetWindowRefreshCallback(self.handle,
                                         self._window_refresh_callback)

    def set_window_focus_callback(self, callback):
        def wrap(self, flag):
            flag = bool(flag)
            callback(self, flag)
        self._window_focus_callback = self._wcb(api.GLFWwindowfocusfun,
                                                wrap)
        api.glfwSetWindowFocusCallback(self.handle,
                                       self._window_focus_callback)

    def set_window_iconify_callback(self, callback):
        def wrap(self, flag):
            flag = bool(flag)
            callback(self, flag)
        self._window_iconify_callback = self._wcb(api.GLFWwindowiconifyfun,
                                                  wrap)
        api.glfwSetWindowIconifyCallback(self.handle,
                                         self._window_iconify_callback)

    def set_framebuffer_size_callback(self, callback):
        self._framebuffer_size_callback = self._wcb(api.GLFWframebuffersizefun,
                                                    callback)
        api.glfwSetFramebufferSizeCallback(self.handle,
                                           self._framebuffer_size_callback)
