local ROOT = (...):gsub('[^.]*$', '')

local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Shortcut = require(ROOT .. 'shortcut')

local Input = Base:extend()

local weakValueMeta = { __mode = 'v' }

function Input:constructor ()
    self.pressedWidgets = setmetatable({}, weakValueMeta)
    self.passedWidgets = setmetatable({}, weakValueMeta)
end

function Input:handleDisplay (layout)
    local root = layout.root
    if root then root:paint() end
    Event.Display:emit(layout)
end

function Input:handleKeyPress (layout, key, sc, x, y)
    local widget = layout.focusedWidget or layout.root
    local result = widget:bubbleEvent('KeyPress', {
        key = key,
        scanCode = sc,
        modifierFlags = Shortcut.getModifierFlags(),
        x = x, y = y
    })
    if result ~= nil then return result end
    if layout.root.modal then return false end
end

function Input:handleKeyRelease (layout, key, sc, x, y)
    local widget = layout.focusedWidget or layout.root
    local result = widget:bubbleEvent('KeyRelease', {
        key = key,
        scanCode = sc,
        modifierFlags = Shortcut.getModifierFlags(),
        x = x, y = y
    })
    if result ~= nil then return result end
    if layout.root.modal then return false end
end

function Input:handleTextInput (layout, text, x, y)
    local widget = layout.focusedWidget or layout.root
    local result = widget:bubbleEvent('TextInput', {
        text = text,
        x = x, y = y
    })
    if result ~= nil then return result end
    if layout.root.modal then return false end
end

local function checkHit (widget, layout)
    local root = layout.root
    return widget and widget.solid or root.modal, widget or root
end

function Input:handleMove (layout, x, y)
    local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
    local previousWidget = self.previousMoveWidget
    if widget ~= previousWidget then
        if previousWidget then
            for ancestor in previousWidget:eachAncestor(true) do
                ancestor.hovered = nil
            end
        end
        for ancestor in widget:eachAncestor(true) do
            ancestor.hovered = true
        end
    end
    widget:bubbleEvent('Move', {
        hit = hit,
        oldTarget = previousWidget,
        x = x, y = y
    })
    if widget ~= previousWidget then
        if previousWidget then
            previousWidget:bubbleEvent('Leave', {
                hit = hit,
                newTarget = widget,
                x = x, y = y
            })
        end
        widget:bubbleEvent('Enter', {
            hit = hit,
            oldTarget = previousWidget,
            x = x, y = y
        })
        if widget.cursor then
            Backend.setCursor(Backend.getSystemCursor(widget.cursor))
        else
            Backend.setCursor()
        end
        self.previousMoveWidget = widget
    end
    return hit
end

function Input:handlePressedMove (layout, x, y)
    local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
    for _, button in ipairs { 'left', 'middle', 'right' } do
        local originWidget = self.pressedWidgets[button]
        if originWidget then
            local passedWidget = self.passedWidgets[button]
            originWidget:bubbleEvent('PressDrag', {
                hit = hit,
                newTarget = widget,
                button = button,
                x = x, y = y
            })
            if (widget == passedWidget) then
                widget:bubbleEvent('PressMove', {
                    hit = hit,
                    origin = originWidget,
                    button = button,
                    x = x, y = y
                })
            else
                originWidget.pressed[button] = (widget == originWidget) or nil
                if passedWidget then
                    passedWidget:bubbleEvent('PressLeave', {
                        hit = hit,
                        newTarget = widget,
                        origin = originWidget,
                        button = button,
                        x = x, y = y
                    })
                end
                widget:bubbleEvent('PressEnter', {
                    hit = hit,
                    oldTarget = passedWidget,
                    origin = originWidget,
                    button = button,
                    x = x, y = y
                })
                self.passedWidgets[button] = widget
            end
        end -- if originWidget
    end -- mouse buttons
    return hit
end

function Input:handlePressStart (layout, button, x, y, widget, shortcut)
    local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
    -- if hit then
        self.pressedWidgets[button] = widget
        self.passedWidgets[button] = widget
        widget.pressed[button] = true
        if button == 'left' then
            widget:focus()
        end
    -- end
    widget:bubbleEvent('PressStart', {
        hit = hit,
        button = button,
        shortcut = shortcut,
        x = x, y = y
    })
    return hit
end

function Input:handlePressEnd (layout, button, x, y, widget, shortcut)
    local originWidget = widget or self.pressedWidgets[button]
    if not originWidget then return end
    local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
    local wasPressed = originWidget.pressed[button]
    if hit then
        originWidget.pressed[button] = nil
    end
    widget:bubbleEvent('PressEnd', {
        hit = hit,
        origin = originWidget,
        shortcut = shortcut,
        button = button,
        x = x, y = y
    })
    if (widget == originWidget and wasPressed) then
        widget:bubbleEvent('Press', {
            hit = hit,
            button = button,
            shortcut = shortcut,
            x = x, y = y
        })
    end
    if hit then
        self.pressedWidgets[button] = nil
        self.passedWidgets[button] = nil
    end
    return hit
end

function Input:handleReshape (layout, width, height)
    local root = layout.root

    root:reshape()

    if root.type ~= 'window' then -- FIXME: move stuff below to a Widget method
        if not root.width then
            root.dimensions.width = width
        end
        if not root.height then
            root.dimensions.height = height
        end
    end

    Event.Reshape:emit(layout, {
        target = layout,
        width = width,
        height = height
    })
end

function Input:handleWheelMove (layout, scrollX, scrollY)
    local x, y = Backend.getMousePosition()
    local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)

    widget:bubbleEvent('WheelMove', {
        hit = hit,
        x = x, y = y,
        scrollX = scrollX, scrollY = scrollY
    })

    return hit
end

Input.default = Input()

return Input
