local gears_obj = require("gears.object")
local grect = require("gears.geometry").rectangle

local clients = {}

local client, meta = awesome._shim_fake_class()

rawset(client, "_autotags", true)

-- Make sure `awful.client` properties like `floating` get handled by the
-- miss handler rather than set directly on the object. The reason it's done
-- than way is ancient and convoluted.
local native_property_defaults = {
    -- Layers.
    ontop     = false,
    below     = false,
    above     = false,
    sticky    = false,
    urgent    = false,
    modal     = false,
    focusable = true,
    hidden    = false,

    -- Other properties.
    name             = "",
    transient_for    = nil,
    skip_taskbar     = false,
    type             = "normal",
    class            = "",
    instance         = "",
    role             = "",
    pid              = 1,
    leader_window    = nil,
    machine          = "",
    icon_name        = "",
    screen           = nil,
    minimized        = false,
    motif_wm_hints   = {},
    group_window     = nil,
    icon             = nil,
    icon_sizes       = {},
    size_hints_honor = true,
    size_hints       = {},
    startup_id       = nil,
    valid            = true,

    -- The shims can't really handle those properly.
    shape_bounding        = nil,
    content               = nil,
    shape_clip            = nil,
    shape_input           = nil,
    client_shape_bounding = nil,
    client_shape_clip     = nil,

    -- Those have special code.
    maximized            = nil,
    fullscreen           = nil,
    maximized_horizontal = nil,
    maximized_vertical   = nil,

    -- Those are not really native, but it simplify the shims a lot.
    x      = 0,
    y      = 0,
    width  = 1,
    height = 1,
}

-- Keep an history of the geometry for validation and images
local function push_geometry(c)
    table.insert(c._old_geo, c:geometry())
end

-- Undo the last move, but keep it in history
-- local function pop_geometry(c)
--     CURRENTLY UNUSED
-- end

local function titlebar_meta(c)
    for _, position in ipairs {"top", "bottom", "left", "right" } do
        c["titlebar_"..position] = function(_, size)
            local prop = "titlebar_"..position.."_size"
            c._private[prop] = c._private[prop] or size
            return drawin{}, c._private[prop] or 0
        end
    end
end

local properties = {}

-- Emit the request::geometry signal to make immobilized and awful.ewmh work.
for _, prop in ipairs {
  "maximized", "maximized_horizontal", "maximized_vertical", "fullscreen" } do
    properties["get_"..prop] = function(self)
        return self._private[prop] or false
    end

    properties["set_"..prop] = function(self, value)
        self._private[prop] = value or false

        if value then
            self:emit_signal("request::geometry", prop, nil)
        end

        self:emit_signal("property::"..prop, value)
    end
end

function properties.get_screen(self)
    return self._private.screen or screen[1]
end

function properties.set_screen(self, s)
    s = screen[s]
    self._private.screen = s

    if self.x < s.geometry.x or self.x > s.geometry.x+s.geometry.width then
        self:geometry { x = s.geometry.x }
    end

    if self.y < s.geometry.y or self.y > s.geometry.y+s.geometry.height then
        self:geometry { y = s.geometry.y }
    end

    self:emit_signal("property::screen")
end

function properties:get_first_tag()
    return self:tags()[1]
end

-- Create fake clients to move around
function client.gen_fake(args)
    local ret = gears_obj()
    awesome._forward_class(ret, client)

    ret._private = {}
    ret.type = native_property_defaults.type
    ret.valid = native_property_defaults.valid
    ret.size_hints = native_property_defaults.size_hints
    ret._border_width = 1
    ret._tags = args and args.tags or nil
    ret.icon_sizes = {{16,16}}
    ret.name = "Example Client"
    ret._private._struts = { top = 0, right = 0, left = 0, bottom = 0 }

    --TODO v5: remove this. This was a private API and thus doesn't need to be
    -- officially deprecated.
    ret.data = ret._private

    -- This is a hack because there's a `:is_transient_for(c2)` method
    -- and a `transient_for` property. It will cause a stack overflow
    -- since the auto-alias will kick in if the property is allowed to
    -- be `nil`.
    ret.transient_for = false

    -- Apply the native (capi) properties.
    for k,v in pairs(args or {}) do
        if native_property_defaults[k] then
            ret[k] = v
        end
    end

    -- Tests should always set a geometry, but just in case
    for _, v in ipairs{"x","y","width","height"} do
        ret[v] = ret[v] or 1
        assert((not args[v]) or ret[v] == args[v])
    end

    -- Emulate capi.client.geometry
    function ret:geometry(new)
        local new_full = new and {
            x      = new.x or ret.x,
            y      = new.y or ret.y,
            width  = new.width  or ret.width,
            height = new.height or ret.height,
        } or nil

        if new and not grect.are_equal(ret, new_full) then
            for k,v in pairs(new) do
                ret[k] = v
                ret:emit_signal("property::"..k, v)
            end
            ret:emit_signal("property::geometry", ret:geometry())
            push_geometry(ret)
        end

        return {
            x      = ret.x,
            y      = ret.y,
            width  = ret.width,
            height = ret.height,
            label  = ret._label,
        }
    end

    function ret:isvisible()
        if ret.minimized or ret.hidden then return false end

        local vis = false

        for _, tag in ipairs(ret:tags()) do
            vis = vis or tag.selected
        end

        return vis
    end

    -- Used for screenshots
    function ret:set_label(text)
        ret._old_geo[#ret._old_geo]._label = text
    end

    -- Used for screenshots, hide the current client position
    function ret:_hide()
        ret._old_geo[#ret._old_geo]._hide = true
    end

    function ret:_hide_all()
        for _, geo in ipairs(ret._old_geo) do
            geo._hide = true
        end
    end

    function ret:get_xproperty()
        return nil
    end

    function ret:set_xproperty(prop, value)
        ret[prop] = value
    end

    function ret:get_icon(_)
        return require("beautiful").awesome_icon
    end

    function ret:raise()
        --TODO
    end

    function ret:lower()
        --TODO
    end

    function ret:apply_size_hints(w, h)
        return w or ret.width, h or ret.height
    end

    function ret:kill()
        local old_tags = ret:tags() or {}

        for k, c in ipairs(clients) do
            if c == ret then
                ret:emit_signal("unmanaged", c)
                ret.valid = false
                table.remove(clients, k)
                break
            end
        end

        ret._tags = {}

        for _, t in ipairs(old_tags) do
            ret:emit_signal("untagged", t)
            t:emit_signal("untagged", ret)
            t:emit_signal("property::tags")
        end

        assert(not ret.valid)
    end

    function ret:swap(other)
        local idx1, idx2 = nil, nil
        for k, c in ipairs(clients) do
            if c == ret then
                idx1 = k
            elseif c == other then
                idx2 = k
            end
        end

        if not (idx1 and idx2) then return end

        clients[idx1], clients[idx2] = other, ret
        ret:emit_signal("swapped", other, true)
        other:emit_signal("swapped", ret, false)
        client.emit_signal("list")
    end

    titlebar_meta(ret)

    function ret:tags(new) --FIXME
        if new then
            ret._tags = new
            ret:emit_signal("property::tags")
        end

        if ret._tags then
            return ret._tags
        end

        if client._autotags then
            for _, t in ipairs(root._tags) do
                if t.screen == ret.screen then
                    return {t}
                end
            end
        end

        return {}
    end

    function ret:struts(new)
        for k, v in pairs(new or {}) do
            ret._private._struts[k] = v
        end

        return ret._private._struts
    end

    -- Set a dummy one for now since set_screen will corrupt it.
    ret._old_geo = {}

    -- Set the screen attributes.
    local s = args.screen

    -- Pick the screen from the geometry.
    if not s then
        for s2 in screen do
            local geo = s2.geometry
            if geo.x >= ret.x and geo.y >= ret.y and ret.x < geo.x+geo.width
              and ret.y < geo.y+geo.height then
                s = s2
            end
        end
    end

    -- This will happen if the screen coords are not {0,0} and the client had
    -- an unspecified position.
    s = s or screen[1]

    properties.set_screen(ret, s)

    -- Set the geometry *again* because the screen possibly overwrote it.
    for _, v in ipairs{"x","y","width","height"} do
        ret[v] = args[v] or ret[v]
    end

    -- Record the geometry
    ret._old_geo = {}
    push_geometry(ret)
    ret._old_geo[1]._hide = args.hide_first

    -- Good enough for the geometry and border
    ret.drawin = ret
    ret.drawable = ret

    -- Make sure the layer properties are not `nil`
    local defaults = setmetatable({}, {__index = native_property_defaults})

    -- Declare the deprecated buttons and keys methods.
    function ret:_keys(new)
        if new then
            ret._private.keys = new
        end

        return ret._private.keys or {}
    end

    function ret:_buttons(new)
        if new then
            ret._private.buttons = new
        end

        return ret._private.buttons or {}
    end

    -- Add to the client list
    table.insert(clients, ret)

    client.focus = ret

    setmetatable(ret, {
        __index     = function(self, key)
            if properties["get_"..key] then
                return properties["get_"..key](self)
            elseif defaults[key] ~= nil then
                return defaults[key]
            end

            return meta.__index(self, key)
        end,
        __newindex = function(self, key, value)
            if properties["set_"..key] then
                return properties["set_"..key](self, value)
            end

            if defaults[key] ~= nil then
                defaults[key] = value
            else
                meta.__newindex(self, key, value)
            end

            ret:emit_signal("property::"..key, value)
            --client.emit_signal("property::"..key, ret, value)
        end
    })

    -- Apply non-native (`awful.client`) properties.
    for k,v in pairs(args or {}) do
        if (not native_property_defaults[k]) and (not rawget(ret, k)) then
            ret[k] = v
        end
    end

    client.emit_signal("request::manage", ret)

    --TODO v6 remove this.
    client.emit_signal("manage", ret)

    assert(not args.screen or (args.screen == ret.screen))

    return ret
end

function client.get(s)
    if not s then return clients end

    local s2 = screen[s]

    local ret = {}

    for _,c in ipairs(clients) do
        if c.screen == s2 then
            table.insert(ret, c)
        end
    end

    return ret
end

return client

-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
