from pyglet.window import key, mouse
from pyglet.libs.darwin.quartzkey import keymap, charmap

from pyglet.libs.darwin import cocoapy, NSPasteboardURLReadingFileURLsOnlyKey, NSLeftShiftKeyMask, NSRightShiftKeyMask, \
    NSLeftControlKeyMask, NSRightControlKeyMask, NSLeftAlternateKeyMask, NSRightAlternateKeyMask, NSLeftCommandKeyMask, \
    NSRightCommandKeyMask, NSFunctionKeyMask, NSAlphaShiftKeyMask
from .pyglet_textview import PygletTextView


NSTrackingArea = cocoapy.ObjCClass('NSTrackingArea')
NSURL = cocoapy.ObjCClass('NSURL')
NSArray = cocoapy.ObjCClass('NSArray')
NSDictionary = cocoapy.ObjCClass('NSDictionary')
NSNumber = cocoapy.ObjCClass('NSNumber')

# Key to mask mapping.
maskForKey = {
    key.LSHIFT: NSLeftShiftKeyMask,
    key.RSHIFT: NSRightShiftKeyMask,
    key.LCTRL: NSLeftControlKeyMask,
    key.RCTRL: NSRightControlKeyMask,
    key.LOPTION: NSLeftAlternateKeyMask,
    key.ROPTION: NSRightAlternateKeyMask,
    key.LCOMMAND: NSLeftCommandKeyMask,
    key.RCOMMAND: NSRightCommandKeyMask,
    key.CAPSLOCK: NSAlphaShiftKeyMask,
    key.FUNCTION: NSFunctionKeyMask
}

# Event data helper functions.


def getMouseDelta(nsevent):
    dx = nsevent.deltaX()
    dy = -nsevent.deltaY()
    return dx, dy


def getMousePosition(self, nsevent):
    in_window = nsevent.locationInWindow()
    in_window = self.convertPoint_fromView_(in_window, None)
    x = int(in_window.x)
    y = int(in_window.y)
    # Must record mouse position for BaseWindow.draw_mouse_cursor to work.
    self._window._mouse_x = x
    self._window._mouse_y = y
    return x, y


def getModifiers(nsevent):
    modifiers = 0
    modifierFlags = nsevent.modifierFlags()
    if modifierFlags & cocoapy.NSAlphaShiftKeyMask:
        modifiers |= key.MOD_CAPSLOCK
    if modifierFlags & cocoapy.NSShiftKeyMask:
        modifiers |= key.MOD_SHIFT
    if modifierFlags & cocoapy.NSControlKeyMask:
        modifiers |= key.MOD_CTRL
    if modifierFlags & cocoapy.NSAlternateKeyMask:
        modifiers |= key.MOD_ALT
        modifiers |= key.MOD_OPTION
    if modifierFlags & cocoapy.NSCommandKeyMask:
        modifiers |= key.MOD_COMMAND
    if modifierFlags & cocoapy.NSFunctionKeyMask:
        modifiers |= key.MOD_FUNCTION
    return modifiers


def getSymbol(nsevent):
    symbol = keymap.get(nsevent.keyCode(), None)
    if symbol is not None:
        return symbol

    chars = cocoapy.cfstring_to_string(nsevent.charactersIgnoringModifiers())
    if chars:
        return charmap.get(chars[0].upper(), None)

    return None


class PygletView_Implementation:
    PygletView = cocoapy.ObjCSubclass('NSView', 'PygletView')

    @PygletView.method(b'@'+cocoapy.NSRectEncoding+cocoapy.PyObjectEncoding)
    def initWithFrame_cocoaWindow_(self, frame, window):

        # The tracking area is used to get mouseEntered, mouseExited, and cursorUpdate
        # events so that we can custom set the mouse cursor within the view.
        self._tracking_area = None

        self = cocoapy.ObjCInstance(cocoapy.send_super(self, 'initWithFrame:', frame, argtypes=[cocoapy.NSRect]))

        if not self:
            return None

        # CocoaWindow object.
        self._window = window
        self.updateTrackingAreas()

        # Create an instance of PygletTextView to handle text events.
        # We must do this because NSOpenGLView doesn't conform to the
        # NSTextInputClient protocol by default, and the insertText: method will
        # not do the right thing with respect to translating key sequences like
        # "Option-e", "e" if the protocol isn't implemented.  So the easiest
        # thing to do is to subclass NSTextView which *does* implement the
        # protocol and let it handle text input.
        self._textview = PygletTextView.alloc().initWithCocoaWindow_(window)
        # Add text view to the responder chain.
        self.addSubview_(self._textview)
        return self

    @PygletView.method('v')
    def dealloc(self):
        self._window = None
        # cocoapy.end_message(self.objc_self, 'removeFromSuperviewWithoutNeedingDisplay')
        self._textview.release()
        self._textview = None
        self._tracking_area.release()
        self._tracking_area = None
        cocoapy.send_super(self, 'dealloc')

    @PygletView.method('v')
    def updateTrackingAreas(self):
        # This method is called automatically whenever the tracking areas need to be
        # recreated, for example when window resizes.
        if self._tracking_area:
            self.removeTrackingArea_(self._tracking_area)
            self._tracking_area.release()
            self._tracking_area = None

        tracking_options = cocoapy.NSTrackingMouseEnteredAndExited | cocoapy.NSTrackingActiveInActiveApp | cocoapy.NSTrackingCursorUpdate
        frame = self.frame()

        self._tracking_area = NSTrackingArea.alloc().initWithRect_options_owner_userInfo_(
            frame,              # rect
            tracking_options,   # options
            self,     # owner
            None)               # userInfo

        self.addTrackingArea_(self._tracking_area)

    @PygletView.method('B')
    def acceptsFirstResponder (self):
        return True

    @PygletView.method('B')
    def canBecomeKeyView(self):
        return True

    @PygletView.method('B')
    def isOpaque(self):
        return True

    ## Event responders.

    # This method is called whenever the view changes size.
    @PygletView.method(b'v'+cocoapy.NSSizeEncoding)
    def setFrameSize_(self, size):
        cocoapy.send_super(self, 'setFrameSize:', size,
                           superclass_name='NSView',
                           argtypes=[cocoapy.NSSize])

        # This method is called when view is first installed as the
        # contentView of window.  Don't do anything on first call.
        # This also helps ensure correct window creation event ordering.
        if not self._window.context.canvas:
            return

        width, height = int(size.width), int(size.height)
        self._window.switch_to()
        self._window.context.update_geometry()
        self._window._width, self._window._height = width, height
        self._window.dispatch_event("on_resize", width, height)
        self._window.dispatch_event("on_expose")
        # Can't get app.event_loop.enter_blocking() working with Cocoa, because
        # when mouse clicks on the window's resize control, Cocoa enters into a
        # mini-event loop that only responds to mouseDragged and mouseUp events.
        # This means that using NSTimer to call idle() won't work.  Our kludge
        # is to override NSWindow's nextEventMatchingMask_etc method and call
        # idle() from there.
        if self.inLiveResize():
            from pyglet import app
            if app.event_loop is not None:
                app.event_loop.idle()

    @PygletView.method('v@')
    def keyDown_(self, nsevent):
        if not nsevent.isARepeat():
            symbol = getSymbol(nsevent)
            modifiers = getModifiers(nsevent)
            self._window.dispatch_event('on_key_press', symbol, modifiers)

    @PygletView.method('v@')
    def keyUp_(self, nsevent):
        symbol = getSymbol(nsevent)
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_key_release', symbol, modifiers)

    @PygletView.method('v@')
    def flagsChanged_(self, nsevent):
        # Handles on_key_press and on_key_release events for modifier keys.
        # Note that capslock is handled differently than other keys; it acts
        # as a toggle, so on_key_release is only sent when it's turned off.
        symbol = keymap.get(nsevent.keyCode(), None)

        # Ignore this event if symbol is not a modifier key.  We must check this
        # because e.g., we receive a flagsChanged message when using CMD-tab to
        # switch applications, with symbol == "a" when command key is released.
        if symbol is None or symbol not in maskForKey:
            return

        modifiers = getModifiers(nsevent)
        modifierFlags = nsevent.modifierFlags()

        if symbol and modifierFlags & maskForKey[symbol]:
            self._window.dispatch_event('on_key_press', symbol, modifiers)
        else:
            self._window.dispatch_event('on_key_release', symbol, modifiers)

    # Overriding this method helps prevent system beeps for unhandled events.
    @PygletView.method('B@')
    def performKeyEquivalent_(self, nsevent):
        # Let arrow keys and certain function keys pass through the responder
        # chain so that the textview can handle on_text_motion events.
        modifierFlags = nsevent.modifierFlags()
        if modifierFlags & cocoapy.NSNumericPadKeyMask:
            return False
        if modifierFlags & cocoapy.NSFunctionKeyMask:
            ch = cocoapy.cfstring_to_string(nsevent.charactersIgnoringModifiers())
            if ch in (cocoapy.NSHomeFunctionKey, cocoapy.NSEndFunctionKey,
                      cocoapy.NSPageUpFunctionKey, cocoapy.NSPageDownFunctionKey):
                return False
        # Send the key equivalent to the main menu to perform menu items.
        NSApp = cocoapy.ObjCClass('NSApplication').sharedApplication()
        NSApp.mainMenu().performKeyEquivalent_(nsevent)
        # Indicate that we've handled the event so system won't beep.
        return True

    @PygletView.method('v@')
    def mouseMoved_(self, nsevent):
        if self._window._mouse_ignore_motion:
            self._window._mouse_ignore_motion = False
            return
        # Don't send on_mouse_motion events if we're not inside the content rectangle.
        if not self._window._mouse_in_window:
            return
        x, y = getMousePosition(self, nsevent)
        dx, dy = getMouseDelta(nsevent)
        self._window.dispatch_event('on_mouse_motion', x, y, dx, dy)

    @PygletView.method('v@')
    def scrollWheel_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        scroll_x, scroll_y = getMouseDelta(nsevent)
        self._window.dispatch_event('on_mouse_scroll', x, y, scroll_x, scroll_y)

    @PygletView.method('v@')
    def mouseDown_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.LEFT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_press', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def mouseDragged_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        dx, dy = getMouseDelta(nsevent)
        buttons = mouse.LEFT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_drag', x, y, dx, dy, buttons, modifiers)

    @PygletView.method('v@')
    def mouseUp_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.LEFT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_release', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def rightMouseDown_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.RIGHT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_press', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def rightMouseDragged_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        dx, dy = getMouseDelta(nsevent)
        buttons = mouse.RIGHT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_drag', x, y, dx, dy, buttons, modifiers)

    @PygletView.method('v@')
    def rightMouseUp_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.RIGHT
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_release', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def otherMouseDown_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.MIDDLE
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_press', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def otherMouseDragged_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        dx, dy = getMouseDelta(nsevent)
        buttons = mouse.MIDDLE
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_drag', x, y, dx, dy, buttons, modifiers)

    @PygletView.method('v@')
    def otherMouseUp_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        buttons = mouse.MIDDLE
        modifiers = getModifiers(nsevent)
        self._window.dispatch_event('on_mouse_release', x, y, buttons, modifiers)

    @PygletView.method('v@')
    def mouseEntered_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        self._window._mouse_in_window = True
        # Don't call self._window.set_mouse_platform_visible() from here.
        # Better to do it from cursorUpdate:
        self._window.dispatch_event('on_mouse_enter', x, y)

    @PygletView.method('v@')
    def mouseExited_(self, nsevent):
        x, y = getMousePosition(self, nsevent)
        self._window._mouse_in_window = False
        if not self._window._mouse_exclusive:
            self._window.set_mouse_platform_visible()
        self._window.dispatch_event('on_mouse_leave', x, y)

    @PygletView.method('v@')
    def cursorUpdate_(self, nsevent):
        # Called when mouse cursor enters view.  Unlike mouseEntered:,
        # this method will be called if the view appears underneath a
        # motionless mouse cursor, as can happen during window creation,
        # or when switching into fullscreen mode.
        # BUG: If the mouse enters the window via the resize control at the
        # the bottom right corner, the resize control will set the cursor
        # to the default arrow and screw up our cursor tracking.
        self._window._mouse_in_window = True
        if not self._window._mouse_exclusive:
            self._window.set_mouse_platform_visible()

    @PygletView.method('Q@')
    def draggingEntered_(self, draginfo):
        return cocoapy.NSDragOperationGeneric

    @PygletView.method('B@')
    def performDragOperation_(self, sender):
        pos = sender.draggingLocation()

        pasteboard = sender.draggingPasteboard()

        classes = NSArray.arrayWithObject_(NSURL)

        options = NSDictionary.dictionaryWithObject_forKey_(
            NSNumber.numberWithBool_(True), NSPasteboardURLReadingFileURLsOnlyKey
        )

        urls = pasteboard.readObjectsForClasses_options_(classes, options)

        url_count = urls.count()
        paths = []
        for i in range(url_count):
            fpath = urls.objectAtIndex_(i).fileSystemRepresentation()
            paths.append(fpath.decode())

        self._window.dispatch_event('on_file_drop', pos.x, pos.y, paths)


PygletView = cocoapy.ObjCClass('PygletView')
