import Carbon

final class HotKeysController {

// MARK: - Types

final class HotKeyBox {
        let identifier: UUID
        weak var hotKey: HotKey?
        let carbonHotKeyID: UInt32
        var carbonEventHotKey: EventHotKeyRef?

        init(hotKey: HotKey, carbonHotKeyID: UInt32) {
                self.identifier = hotKey.identifier
                self.hotKey = hotKey
                self.carbonHotKeyID = carbonHotKeyID
        }
}

// MARK: - Properties

static var hotKeys = [UInt32: HotKeyBox]()
static private var hotKeysCount: UInt32 = 0

static let eventHotKeySignature: UInt32 = {
        let string = "SSHk"
        var result: FourCharCode = 0
        for char in string.utf16 {
                result = (result << 8) + FourCharCode(char)
        }
        return result
}()

private static let eventSpec = [
        EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
        EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
]

private static var eventHandler: EventHandlerRef?

// MARK: - Registration

static func register(_ hotKey: HotKey) {
// Don't register an already registered HotKey
if hotKeys.values.first(where: { $0.identifier == hotKey.identifier }) != nil {
    return
}

        // Increment the count which will become out next ID
        hotKeysCount += 1

        // Create a box for our metadata and weak HotKey
        let box = HotKeyBox(hotKey: hotKey, carbonHotKeyID: hotKeysCount)
        hotKeys[box.carbonHotKeyID] = box

        // Register with the system
        var eventHotKey: EventHotKeyRef?
        let hotKeyID = EventHotKeyID(signature: eventHotKeySignature, id: box.carbonHotKeyID)
        let registerError = RegisterEventHotKey(
                hotKey.keyCombo.carbonKeyCode,
                hotKey.keyCombo.carbonModifiers,
                hotKeyID,
                GetEventDispatcherTarget(),
                0,
                &eventHotKey
        )

        // Ensure registration worked
        guard registerError == noErr, eventHotKey != nil else {
                return
        }

        // Store the event so we can unregister it later
        box.carbonEventHotKey = eventHotKey

        // Setup the event handler if needed
        updateEventHandler()
}

static func unregister(_ hotKey: HotKey) {
        // Find the box
        guard let box = self.box(for: hotKey) else {
                return
        }

        // Unregister the hot key
        UnregisterEventHotKey(box.carbonEventHotKey)

        // Destroy the box
        box.hotKey = nil
        hotKeys.removeValue(forKey: box.carbonHotKeyID)
}

// MARK: - Events

static func handleCarbonEvent(_ event: EventRef?) -> OSStatus {
        // Ensure we have an event
        guard let event = event else {
                return OSStatus(eventNotHandledErr)
        }

        // Get the hot key ID from the event
        var hotKeyID = EventHotKeyID()
        let error = GetEventParameter(
                event,
                UInt32(kEventParamDirectObject),
                UInt32(typeEventHotKeyID),
                nil,
                MemoryLayout<EventHotKeyID>.size,
                nil,
                &hotKeyID
        )

        if error != noErr {
                return error
        }

        // Ensure we have a HotKey registered for this ID
        guard hotKeyID.signature == eventHotKeySignature,
                let hotKey = self.hotKey(for: hotKeyID.id)
        else {
                return OSStatus(eventNotHandledErr)
        }

        // Call the handler
        switch GetEventKind(event) {
        case UInt32(kEventHotKeyPressed):
                if !hotKey.isPaused, let handler = hotKey.keyDownHandler {
                        handler()
                        return noErr
                }
        case UInt32(kEventHotKeyReleased):
                if !hotKey.isPaused, let handler = hotKey.keyUpHandler {
                        handler()
                        return noErr
                }
        default:
                break
        }

        return OSStatus(eventNotHandledErr)
}

private static func updateEventHandler() {
        if hotKeysCount == 0 || eventHandler != nil {
                return
        }

        // Register for key down and key up
        let eventSpec = [
                EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
                EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
        ]

        // Install the handler
        InstallEventHandler(GetEventDispatcherTarget(), hotKeyEventHandler, 2, eventSpec, nil, &eventHandler)
}

// MARK: - Querying

private static func hotKey(for carbonHotKeyID: UInt32) -> HotKey? {
        if let hotKey = hotKeys[carbonHotKeyID]?.hotKey {
                return hotKey
        }

        hotKeys.removeValue(forKey: carbonHotKeyID)
        return nil
}

private static func box(for hotKey: HotKey) -> HotKeyBox? {
        for box in hotKeys.values {
                if box.identifier == hotKey.identifier {
                        return box
                }
        }

        return nil
}

}

private func hotKeyEventHandler(eventHandlerCall: EventHandlerCallRef?, event: EventRef?, userData: UnsafeMutableRawPointer?) -> OSStatus {

return HotKeysController.handleCarbonEvent(event)

}