if not vim then error('needs to be run with neovim') end
if not pcall(vim.treesitter.language.inspect,'c') then error('treesitter-c parser not found') end

local queries={
    type_definition=[[;; query
    (type_definition
      declarator:[(type_identifier) (primitive_type)] @name)
    (pointer_declarator
      declarator:[(type_identifier) (primitive_type)] @name)
    (type_definition
      type: [(type_identifier) (primitive_type) (sized_type_specifier)] @type)
    (struct_specifier
      !body) @type
    (type_definition
      (struct_specifier
        "struct"
        name:(type_identifier) @name (#set! pre "struct")
        body:(_)
        ))
    (type_definition
      (union_specifier
        "union"
        name:(type_identifier) @name (#set! pre "union")
        body:(_)
        ))
      (struct_specifier
        body:
        (field_declaration_list
          (field_declaration
            type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)))
    (enumerator
      name:(identifier) @type)
    (parameter_declaration
      type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)
    (union_specifier
      body:
        (field_declaration_list
          (field_declaration
            type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)))
    ]],
    declaration=[[;; query
    (declaration
      declarator:
      [
       (function_declarator
         declarator:(identifier) @name)
       (pointer_declarator
         (function_declarator
           declarator:(identifier) @name))
       (pointer_declarator
         (pointer_declarator
           (function_declarator
             declarator:(identifier) @name)))
       (function_declarator
         (parenthesized_declarator
           (pointer_declarator
             (function_declarator
               declarator:(identifier) @name))))
       (identifier) @name
       ])

    (declaration
      type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)
    (struct_specifier
      !body) @type
    (parameter_declaration
      type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)
    ]],
    struct_specifier=[[;; query
    (struct_specifier
      "struct"
      name:(type_identifier) @name (#set! pre "struct"))
    (struct_specifier
      body:
      (field_declaration_list
        (field_declaration
          type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)))
    ]],
    union_specifier=[[;; query
    (union_specifier
      "union"
      name:(type_identifier) @name (#set! pre "union"))

    (union_specifier
      body:
      (field_declaration_list
        (field_declaration
          type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type)))
    ]]
}

local function get_symbols(libpath)
    local systemlist=vim.fn.systemlist({'gcc','-E',libpath})
    local str=require'string.buffer'.new(#systemlist*5)
    local ret={}
    for _,line in ipairs(systemlist) do
        if vim.startswith(line,'#') then goto continue end
        if vim.trim(line)=='' then goto continue end
        str:put(vim.trim(line)..' ')
        ::continue::
    end
    for line in io.lines(libpath) do
        for _,pattern in ipairs{
            {'^#define%s+([%w_]+)%s+0x(%x+)',16},
            {'^#define%s+([%w_]+)%s+(%d+)'},
            {'^#define%s+([%w_]+)%s+%((%d)L?<<(%d+)%)'},
        } do
            local name,number,expr=string.match(line,pattern[1])
            if name then
                ret[name]={const=tonumber(number,pattern[2])*(2^(expr or 0))}
                break
            end
        end
    end
    local buf=str:tostring()
    local parser=vim.treesitter.get_string_parser(buf,'c')
    local root=parser:parse()[1]:root()
    local text=function (node)
        return vim.treesitter.get_node_text(node,buf)
    end
    local outfile='/tmp/user/out.c'
    vim.fn.writefile({},outfile)
    for node in root:iter_children() do
        local query
        if queries[node:type()] then
            query=vim.treesitter.query.parse('c',queries[node:type()])
        elseif vim.tbl_contains({';','function_definition'},node:type()) then
            goto continue
        else
            error''
        end
        local names={}
        local types={}
        for id,n,metadata in query:iter_captures(node,buf,0,-1) do
            if query.captures[id]=='name' then
                table.insert(names,(metadata.pre and metadata.pre..' ' or '')..text(n))
            elseif query.captures[id]=='type' then
                table.insert(types,text(n))
            end
        end
        local name=table.remove(names,#names)
        ret[name]={req=types,source=text(node)}
        for _,altname in ipairs(names) do
            ret[altname]={link=name}
        end
        ::continue::
    end
    return ret
end
local function gen_lib(outfile,libpaths,libname,symbols)
    local source=vim.split([=[
--[[
DO NOT EDIT
This file is autogenerated from gen_lib.lua
To regenerate this file, run `nvim -l gen_lib.lua`
--]]
]=],'\n')
    table.insert(source,'local ffi=require"ffi"')
    table.insert(source,'ffi.cdef[=[')
    local symbol={
        int={},
        char={},
        long={},
        short={},
        void={},
        ['unsigned long']={},
        ['unsigned short']={},
        ['unsigned short int']={},
        ['unsigned long int']={},
        ['unsigned int']={},
        ['unsigned char']={},
        ['struct _XGC']={},
    }
    for _,lib in ipairs(libpaths) do
        symbol=vim.tbl_extend('force',symbol,get_symbols(lib))
    end
    local function simplify(i)
        local s=assert(symbol[i],i)
        if s.const then
            table.insert(source,('enum {%s=%s};'):format(i,s.const))
            return
        elseif s.done then return end
        s.done=true
        for _,v in ipairs(s.req or {}) do
            simplify(v)
        end
        if s.source then table.insert(source,s.source..';') end
    end
    for _,i in ipairs(symbols) do
        simplify(i)
    end
    table.insert(source,']=]')
    table.insert(source,('return ffi.load"%s" --[[@as table]]'):format(libname))
    vim.fn.writefile(source,outfile)
end
gen_lib('xlib.lua',{
    '/usr/include/X11/Xlib.h',
    '/usr/include/X11/X.h',
    '/usr/include/X11/keysymdef.h',
},'X11',{
        'AnyButton',
        'AnyModifier',
        'ButtonPress',
        'ButtonPressMask',
        'ButtonRelease',
        'CirculateNotify',
        'CirculateRequest',
        'ClientMessage',
        'ColormapNotify',
        'ConfigureNotify',
        'ConfigureRequest',
        'ControlMask',
        'CreateNotify',
        'CurrentTime',
        'DestroyNotify',
        'EnterNotify',
        'Expose',
        'FocusIn',
        'FocusOut',
        'GenericEvent',
        'GrabModeAsync',
        'GrabModeSync',
        'GraphicsExpose',
        'GravityNotify',
        'IsViewable',
        'KeyPress',
        'KeyRelease',
        'KeymapNotify',
        'LASTEvent',
        'LeaveNotify',
        'LockMask',
        'MapNotify',
        'MapRequest',
        'MappingNotify',
        'Mod1Mask',
        'Mod2Mask',
        'Mod3Mask',
        'Mod4Mask',
        'Mod5Mask',
        'MotionNotify',
        'NoExpose',
        'PropertyNotify',
        'ReparentNotify',
        'ReplayPointer',
        'ResizeRequest',
        'RevertToParent',
        'SelectionClear',
        'SelectionNotify',
        'SelectionRequest',
        'ShiftMask',
        'StructureNotifyMask',
        'SubstructureNotifyMask',
        'SubstructureRedirectMask',
        'UnmapNotify',
        'VisibilityNotify',
        'Window',
        'XAllowEvents',
        'XCloseDisplay',
        'XConfigureWindow',
        'XDefaultScreen',
        'XDefaultScreenOfDisplay',
        'XEvent',
        'XFree',
        'XGetInputFocus',
        'XGetWindowAttributes',
        'XGrabButton',
        'XGrabKey',
        'XInternAtom',
        'XKeysymToKeycode',
        'XMapWindow',
        'XMoveResizeWindow',
        'XNextEvent',
        'XOpenDisplay',
        'XPending',
        'XQueryTree',
        'XRectangle',
        'XRootWindow',
        'XSelectInput',
        'XSendEvent',
        'XSetInputFocus',
        'XStringToKeysym',
        'XSync',
        'XUnmapWindow',
        'XWindowAttributes',
        'XWindowChanges',
    })
gen_lib('clib.lua',{
'/usr/include/sys/ioctl.h',
'/usr/include/asm-generic/ioctls.h',
},'c',{
        'struct winsize',
        'TIOCGWINSZ',
        'ioctl',
    })
gen_lib('xfixlib.lua',{
    '/usr/include/X11/extensions/Xfixes.h',
    '/usr/include/X11/extensions/shapeconst.h',
},'Xfixes',{
        'ShapeInput',
        'ShapeBounding',
        'XFixesCreateRegion',
        'XFixesDestroyRegion',
        'XFixesSetWindowShapeRegion',
        'XFixesInvertRegion',
    })
