text: local ffi = require( "ffi" )
  local sdl = require( "ffi/sdl" )
  local wm  = require( "lib/wm/sdl" )
  local random, floor, pi = math.random, math.floor, math.pi
  
  local lines = {{ x=0, y=0, solid = false }}
  
  -- Preallocate certain ffi structures, to prevent allocations
  local gfx = {}
  local ui = {}
  
  local function gencode()
     local constructors = {
        "image_surface_create",
        "create",
     }
     local context_functions = {
        "move_to", "line_to", "curve_to", "rel_move_to", "rel_line_to", "rel_curve_to",
        "arc", "new_path", "rectangle", "close_path", "set_source_rgb", "set_source_rgba",
        "fill_preserve", "stroke_preserve", "stroke", "set_line_width", "show_text", "clip",
        "reset_clip", "text_extents", "save", "restore", "set_font_size", "set_operator",
        "paint", "select_font_face", "surface_destroy", "destroy"
     }
     local surface_functions = {
     }
     local c = { "return {\n" }
     for _,n in ipairs(constructors) do
        assert( c[#c+1] == nil )
        c[#c+1]='["'..n..'"]=function(self,...)return self.library.cairo_' .. n .. "(...)end,\n"
     end
     for _,n in ipairs(context_functions) do
        assert( c[#c+1] == nil )
        c[#c+1]='["'..n..'"]=function(self,...)return self.library.cairo_' .. n .. "(self.context,...)end,\n"
     end
     for _,n in ipairs(surface_functions) do
        assert( c[#c+1] == nil )
        c[#c+1]='["'..n..'"]=function(self,...)return self.library.cairo_' .. n .. "(self.surface,...)end,\n"
     end
     c[#c+1] = "}\n"
     return table.concat(c)
  end
  
  local function fillconstants(m)
     local constants = {
        "FORMAT_RGB24", "OPERATOR_SOURCE",
     }
     for _,n in ipairs(constants) do
        assert( m[n] == nil )
        m[n] = m.library["CAIRO_" .. n]
     end
  end
  
  --print(gencode())
  
  local cairo = loadstring(gencode())()
  cairo.library = require( "ffi/cairo" )
  fillconstants(cairo)
  cairo.context = {}
  cairo.surface = {}
  
  -- From the cairo cookbook
  -- http://cairographics.org/cookbook/roundedrectangles/
  function gfx:round_rect_a(x,y,width,height,radius)
     local radius = radius or 5 -- an approximate curvature radius
     local half_radius = radius * 0.5
     local double_radius = radius + radius
     local x0, y0 = x, y -- + half_radius, y + half_radius
     local x1, y1 = x0+width, y0+height
  
     if width < double_radius then 
        if height < double_radius then
  	 cairo:move_to(  x0, (y0 + y1) * 0.5 )
  	 cairo:curve_to( x0, y0, x0, y0, (x0 + x1) * 0.5, y0 )
  	 cairo:curve_to( x1, y0, x1, y0, x1, (y0 + y1) * 0.5 )
  	 cairo:curve_to( x1, y1, x1, y1, (x1 + x0) * 0.5, y1 )
  	 cairo:curve_to( x0, y1, x0, y1, x0, (y0 + y1) * 0.5 )
        else 
  	 cairo:move_to(  x0, y0 + radius )
  	 cairo:curve_to( x0 ,y0, x0, y0, (x0 + x1)/2, y0 )
  	 cairo:curve_to( x1, y0, x1, y0, x1, y0 + radius )
  	 cairo:line_to(  x1, y1 - radius )
  	 cairo:curve_to( x1, y1, x1, y1, (x1 + x0)/2, y1 )
  	 cairo:curve_to( x0, y1, x0, y1, x0, y1- radius )
        end
     else
        if height < double_radius then
  	 cairo:move_to(  x0, (y0 + y1)/2 )
  	 cairo:curve_to( x0 , y0, x0, y0, x0 + radius, y0 )
  	 cairo:line_to(  x1 - radius, y0 )
  	 cairo:curve_to( x1, y0, x1, y0, x1, (y0 + y1)/2 )
  	 cairo:curve_to( x1, y1, x1, y1, x1 - radius, y1 )
  	 cairo:line_to(  x0 + radius, y1 )
  	 cairo:curve_to( x0, y1, x0, y1, x0, (y0 + y1)/2 )
        else
  	 cairo:move_to(  x0, y0 + radius)
  	 cairo:curve_to( x0 , y0, x0 , y0, x0 + radius, y0)
  	 cairo:line_to(  x1 - radius, y0)
  	 cairo:curve_to( x1, y0, x1, y0, x1, y0 + radius)
  	 cairo:line_to(  x1, y1 - radius)
  	 cairo:curve_to( x1, y1, x1, y1, x1 - radius, y1)
  	 cairo:line_to(  x0 + radius, y1)
  	 cairo:curve_to( x0, y1, x0, y1, x0, y1- radius)
        end
     end
  end
  
  function gfx:round_rect_b(x,y,w,h,radius_x,radius_y)
     -- http://graphics.stanford.edu/courses/cs248-98-fall/Final/q1.html
     local ARC_TO_BEZIER = 0.55228475
     local radius_x = radius_x or 5
     local radius_y = radius_y or 5
  
     if radius_x > w - radius_x then
        radius_x = w * 0.5
     end
  
     if radius_y > h - radius_y then
        radius_y = h * 0.5
     end
  
     -- approximate (quite close) the arc using a bezier curve
     local c1 = ARC_TO_BEZIER * radius_x
     local c2 = ARC_TO_BEZIER * radius_y
  
     cairo:move_to(      x + radius_x, y )
     cairo:rel_line_to(  w - 2 * radius_x, 0.0 )
     cairo:rel_curve_to( c1, 0.0, radius_x, c2, radius_x, radius_y )
     cairo:rel_line_to(  0, h - 2 * radius_y )
     cairo:rel_curve_to( 0, c2, c1 - radius_x, radius_y, -radius_x, radius_y )
     cairo:rel_line_to(  -w + 2 * radius_x, 0 )
     cairo:rel_curve_to( -c1, 0, -radius_x, -c2, -radius_x, -radius_y )
     cairo:rel_line_to(  0, -h + 2 * radius_y )
     cairo:rel_curve_to( 0, -c2, radius_x - c1, -radius_y, radius_x, -radius_y )
  end
  
  function gfx:round_rect_c(x, y, w, h, r)
     local r = r or 5
     cairo:move_to(  x  +r, y                            )                  
     cairo:line_to(  x+w-r, y                            )                
     cairo:curve_to( x+w,   y,    x+w, y,   x+w,   y  +r ) 
     cairo:line_to(  x+w,   y+h-r                        )           
     cairo:curve_to( x+w,   y+h,  x+w, y+h, x+w-r, y+h   )
     cairo:line_to(  x  +r, y+h                          )                   
     cairo:curve_to( x,     y+h,  x,   y+h, x,     y+h-r )      
     cairo:line_to(  x,     y  +r                        )                     
     cairo:curve_to( x,     y,    x,   y,   x  +r, y     )            
  end
  
  function gfx:round_rect_d(x, y, w, h, r)
     local r, half_pi = r or 5, pi * 0.5
     cairo:new_path()
     cairo:arc( x  +r, y  +r, r, 2*half_pi, 3*half_pi )
     cairo:arc( x+w-r, y  +r, r, 3*half_pi, 4*half_pi )
     cairo:arc( x+w-r, y+h-r, r, 0*half_pi, 1*half_pi )
     cairo:arc( x  +r, y+h-r, r, 1*half_pi, 2*half_pi )
  end
  
  local function take_string( table_or_string, table_key )
     local s = table_or_string or "<nil>"
     local t, k = type(s), table_key or 1
     if t == "string" then
        return s
     elseif t == "number" or t == "boolean" then
        return tostring(s)
     elseif t == "table" then
        -- The name is either the first element: [1], or .name, or .id
        return take_string( s[k] or s.label or s.text or s.name or s.id )
     elseif t == "function" then
        return take_string( s(k), table_key )
     else
        -- userdata
        -- thread
        -- luajit has more I think
        --   cdata
        --
        return "<"..t..">"
     end
  end
  
  -- items is meta structure
  function gfx:list_box(items, x, y, w, h)
     local eh = 24 -- element height
     local h = h or eh * #items
     local ev = floor( h / eh ) -- elements visible
     local h = ev * eh
     local top = items.top or 1
     local bottom = items.bottom or #items
     local current = items.current or 1
  
     if top < current - ev + 1 then
        top = current - ev + 1
     end
  
     bottom = math.min(#items, top + ev - 1)
  
     items.top, items.bottom = top, bottom
  
     -- Make the rectangle path
     cairo:rectangle( x, y, w, h )
  
     -- Fill it first, but preserve the path
     cairo:set_source_rgb( 1, 1, 1 ) -- listbox background color
     cairo:fill_preserve()
  
     -- Next stroke it, and preserve the path
     cairo:set_source_rgb( 0, 0, 0 ) -- listbox "wire" color
     cairo:set_line_width( 0.25 )
     cairo:stroke_preserve()
  
     -- Now set the clip, do not keep the path
     cairo:clip()
     for i=top, bottom do
        local y = y + (i - top) * eh
        local current = (i == current)
        if current then
  	 cairo:move_to(      x,  y )
  	 cairo:rel_line_to(  w,  0 )
  	 cairo:rel_line_to(  0, eh )
  	 cairo:rel_line_to( -w,  0 )
  
  	 -- background
  	 cairo:set_source_rgb( 0, 0, 1 )
  	 cairo:fill_preserve()
  	 -- wiring
  	 cairo:set_source_rgb( 1, 1, 1 )
  	 cairo:stroke()
        end
        cairo:move_to(        x + 8, y + eh * 0.8 )
        cairo:set_source_rgb( current and 1 or 0, current and 1 or 0, current and 1 or 0 )
        cairo:show_text(      take_string( items[i] ) )
     end
     cairo:reset_clip()
  end
  
  local gfx_text_extents = ffi.new( "cairo_text_extents_t" )
  function gfx:text_extents( text )
     cairo:text_extents( text, gfx_text_extents )
     return gfx_text_extents
  end
  
  function gfx:main_menu(items, x, y, w, h)
     local eh = 24 -- element height
     local ev = floor( h / eh ) -- elements visible
     local h = ev * eh
     local current = items.current or 1
  
     local hgap = 8
     local vgap = 5
     local width = 0
     local height = 0
     for i=1, #items do
        local item = take_string( items[i] )
        local te = gfx:text_extents( item )
        width = width + te.width + hgap*2
        height = math.max(height, te.height)
     end
  
     -- Make the rectangle path
     cairo:rectangle( x, y, width, height + vgap*2 )
  
     -- Fill it first, but preserve the path
     cairo:set_source_rgb( 1, 1, 1 ) -- listbox background color
     cairo:fill_preserve( )
  
     -- Next stroke it, and preserve the path
     cairo:set_source_rgb( 0, 0, 0 ) -- listbox "wire" color
     cairo:set_line_width( 0.25 )
     cairo:stroke_preserve()
  
     -- Now set the clip, do not keep the path
     cairo:clip()
     local cx = hgap
     for i=1, #items do
        local iw = gfx:text_extents( take_string(items[i]) ).width + hgap*2
        local y = 0
        local current = (i == current)
        local opened = items.opened
        if current then
  	 if items.opened then
  	 else
  	    cairo:move_to( cx-hgap,  y )
  	    cairo:rel_line_to(  iw,  0 )
  	    cairo:rel_line_to(   0,  height + vgap*2 )
  	    cairo:rel_line_to( -iw,  0 )
  
  	    -- background
  	    cairo:set_source_rgb( 0, 0, 1 )
  	    cairo:fill_preserve()
  	    -- wiring
  	    cairo:set_source_rgb( 1, 1, 1 )
  	    cairo:stroke()
  	 end
        end
        cairo:move_to( cx, y + height*1.2  )
        if current and not opened then
  	 cairo:set_source_rgb( 1, 1, 1 )
        else
  	 if current and opened then
  	    cairo:set_source_rgb( 0.5, 0.5, 0.5 )
  	 else
  	    cairo:set_source_rgb( 0, 0, 0 )
  	 end
        end
        local item = take_string( items[i] )
        cairo:show_text( item )
        if current and opened then
  	 local items = items[i]
  	 if type(items)=="function" then
  	    items = items()
  	 end
  	 local items = items[2]
  	 if items then
  	    cairo:save()
  	    cairo:reset_clip()
  	    gfx:list_box( items, cx, y + height+hgap, 100 )
  	    cairo:restore()
  	 end
        end
        cx = cx + iw
     end
     cairo:reset_clip()
  end
  
  function ui:list_box(state)
     -- Draws a list_box
  end
  
  local items = {
     "First Item",
     "Secod Item",
     "Third Item",
     "Fourth Item",
     "Fifth Item",
     "Third Item 1",
     "Fourth Item 2",
     "Fifth Item 3",
     "Third Item 4",
     "Fourth Item 5",
     "Fifth Item 6",
     "Third Item 7",
     "Fourth Item 8",
     "Fifth Item 9",
     selected = { 3, 5 },
     current = 2,
  }
  
  -- Table is a magic construct :)
  -- The menu would be defined in it
  -- All "array" elements define order of display
  -- All "dictionary" elements define actions
  
  local edit_menu = { 
     "Edit", { 
        "Undo", { 
  	 "Cut" 
        },
        "Paste", { 
  	 "Paste from Kill Menu", { 
  	    "Clip1",
  	    "Clip2",
  	    "Clip3" 
  	 },
        },
     },
  }
  
  local edit_menu2 = { 
     "Edit", { 
        "Undo2", { 
  	 "Cut2" 
        },
        "Paste2", { 
  	 "Paste2 from Kill Menu", { 
  	    "Clip4",
  	    "Clip5",
  	    "Clip6" 
  	 },
        },
     },
  }
  
  local which_edit_menu
  
  local test_menu = {
     "(Apple)", {
        "About This Mac", function() end,
        "Software Update...", function() end,
        "App Store...", function() end,
        "", {},
     },
     "File", {
        
     },
     "Edit", {
     },
     "Options", {
        "Line Wrapping", {
        },
        { true, "Smart Word Spacing in Text Modes" },
        "", {},
        ""
     },
  }
  
  --[[
    lua types: 
      nil, boolean, number, string, function, userdata, thread, and table
    First item
      if function, then eval it, and put result back here
      if string then that's the textual contents
      if number
      if boolean
    Second item
  
    First item -- Visual representation
      nil      -> ? bad idea to rely on it
      boolean  -> ? Maybe way to display checked menu items?
      number   -> ? 
      string   -> The text of the item
      function -> Evals the function, then feedback the result back to "First item"
      userdata -> ?
      thread   -> Like function, but actually resumes the thread coroutine, and expects value to be yielded back to "First item"
      table    -> Special cases - like Check boxed item, or image item, builtin-slider, etc... Maybe widget or whole OO style widgets
      cdata    -> ?
  
    Second item -- Action to be taken
      nil      -> ? bad idea to rely on it
      boolean  -> ? Maybe not active, or disabled or it could be a way to specify checked menu items
      number   -> This can keep some kind of number identifier - like 513
      string   -> This can keep some kind of string identifier - like "ID_MENU_ITEM_BLAH123"
      function -> Action to be taken when pressed
      userdata -> ?
      thread   -> Like function, but actually resumes the thread coroutine with the action. Could be the only way to implement interactive dialogs in the menu, like in OSX Help ->Search
      table    -> Sub-items are listed here
      cdata    -> ?
  --]]
  
  -- Menu is defined in pairs of 2
  -- First item is the textual representation
  --       If it's a string, then it's the text itself
  --       If it's a function, then the function returns the representation
  --       If it's a table, number, userdata, etc. - undefined for now
  -- Second item is either menu expansion or function
  --       If it's a table, then it's an expansion
  --       Otherwise if it's a function then it acts on it's value.
  
  local main_menu = {
     {
        "File", {
  	 { "Visit New File | C-x C-f" },
  	 { "Open File...", "Test" },
        },
     },
     function()
        which_edit_menu = not(which_edit_menu)
        return which_edit_menu and edit_menu or edit_menu2
     end,
     { 
        "Options", {
  	 "Line Wrapping",
  	 "Smart Word Spacing",
  	 "-------------",
  	 "Show Tabs",
  	 "Show Buffers in new",
  	 "----"
        },
     },
     { 
        "Buffers", { 
  	 { name1 = "One" },
  	 { name = "Two" },
  	 { label = "Treeh" }
        },
     },
     { "Tools" },
     { "Lua" },
     { 
        "Help", {
  	 function() return "Search [|   ]" end,
  	 "Help on Word",
  	 "Documentation",
  	 "About",
        },
     },
     {
        function() return os.date( "%X" ) end,
        {
  	 function() return os.date( "%A" ) end,
  	 function() return os.date( "%d %b %Y" ) end,
        }
     },
     current = 3,
  }
  
  local function make_colors(n)
     n = n or 16
     local colors = {}
     for i=1, n do
        local r = random(0.1,0.9)
        local g = random(0.1,0.9)
        local b = random(0.1,0.9)
        local mag = r*r + g*g + b*b
        local oolen = 1 / math.sqrt(mag)
        r, g, b = r*oolen, g*oolen, b*oolen
        v = { random(), random(), random() }
        v[random(1,#v)] = 1 --0.50
        v[random(1,#v)] = 1 --0.75
        colors[#colors+1] = { r*v[1], g*v[1], b*v[2], 1 }
     end
     return colors
  end
  
  local key2cmd = {
     --sdl.SDLK_UP    = "up",
     --sdl.SDLK_DOWN  = "down",
     --sdl.SDLK_LEFT  = "left",
     --sdl.SDLK_RIGHT = "right",
     --sdl.SDLK_SPACE = "select",
     --sdl.SDLK_ENTER = "accept",
  }
  
  do
     local sdl_surface
  
     function wm:resized()
        local format = cairo.FORMAT_RGB24
        cairo.surface = ffi.gc(
  	 cairo:image_surface_create( format, self.width, self.height ),
  	 cairo.library.cairo_surface_destroy 
        )
        cairo.context = ffi.gc(
  	 cairo:create( cairo.surface ),
  	 cairo.library.cairo_destroy
        )
        sdl_surface = ffi.gc(
  	 sdl.SDL_CreateRGBSurfaceFrom(
  	    cairo.library.cairo_image_surface_get_data( cairo.surface ),
  	    self.width, self.height, 32,
  	    cairo.library.cairo_format_stride_for_width( format, self.width ),
  	    0, 0, 0, 0
  	 ),
  	 sdl.SDL_FreeSurface
        )
        sdl.SDL_WM_SetCaption( "GUI Demo", nil )
     end
  
     function wm:key_pressed()
        if self.kb == 27 then
  	 wm:exit()
        end
     end
  
     local colors = make_colors()
  
     local frame = 0 
  
     while wm:update(false) do
        cairo:save()
        cairo:set_operator( cairo.OPERATOR_SOURCE )
        cairo:set_source_rgba( 1, 0.75, 0.15, 1 )
        cairo:paint()
        cairo:restore()
  
        cairo:select_font_face( "Tahoma", 0, 0 )
        cairo:set_font_size( 16 )
        cairo:move_to( wm.width - 50, wm.height - 16 )
        cairo:set_source_rgba( 1, 1, 1, 1 )
        cairo:show_text( tostring( #lines ))
  
        cairo:set_line_width( 1+math.sqrt(math.max(1,wm.wheel/16)))
  
        local color = 0
        for i=1,#lines do
  	 local point = lines[i]
  	 if i > 1 and lines[i-1].solid then
  	    cairo:line_to( point.x, point.y )
  	 else
  	    cairo:move_to( point.x, point.y )
  	    cairo:stroke()
  --	    cairo:set_line_width( random()*3 + math.sqrt(math.max(1,wm.wheel/16)))
  	    color = color + 1
  	    local color = colors[ color % #colors + 1 ]
  	    cairo:set_source_rgba( color[1], color[2], color[3], color[4] )
  	 end
        end
        cairo:stroke()
  
        if wm.mb[1] then
  	 local dx = lines[ #lines ].x - wm.mx
  	 local dy = lines[ #lines ].y - wm.my
  	 if math.abs(dx) > 1 or math.abs(dy) > 1 then
  	    lines[ #lines + 1 ] = { x = wm.mx, y = wm.my, solid=true, color = random() }
  	 end
        elseif lines[ #lines ].solid then
  	 lines[ #lines + 1 ] = { x=wm.mx, y=wm.my, solid=false, color = random() }
        end
  
        if wm.mb[3] then
  	 lines = {{ x=wm.mx, y = wm.my, solid=true }}
  	 colors = make_colors()
        end
  
        cairo:set_line_width( 0.5 )
  
        local bw, bh = 128 + 16 + frame,32 + frame
        local cols, rows = floor(wm.width / bw) + 1, floor(wm.height / bh) + 1
        cols, rows = 4, 4
        for i=0, cols*rows - 1 do
  	 local col, row = floor(i / rows), (i % rows)
  	 local x, y = col * bw, row * bh + 50
  	 local inset = 10
  	 local method = (i % 4 + 1) --random(1,4)
  	 cairo:set_source_rgba( 1, 1, 1, 1 )
  
  	 gfx["round_rect_"..string.char(32+64+method)](gfx, x+inset, y+inset, bw-inset, bh-inset, bh / 3 )
  
  	 local a, b, c, d = random(#colors), random(#colors),random(#colors),random(#colors)
  	 if i ~= os.time() % (cols*rows) then
  	    cairo:set_source_rgba( 1, 1, 1, 1 )
  	 else
  	    cairo:set_source_rgba( 0.25, 0.5, 1, 1 )
  	 end
  
  	 cairo:fill_preserve()
  	 cairo:set_source_rgba( 0, 0, 0, 1 )
  	 cairo:stroke()
  	 cairo:move_to( x + 22, y + bh * 0.8 )
  	 cairo:set_font_size( bh * 0.5 )
  	 cairo:show_text( "Aa" )
        end
  
        local current = items.current - 1
        
        if     wm.kb == sdl.SDLK_UP     then items.current = (current - 1) % #items + 1
        elseif wm.kb == sdl.SDLK_DOWN   then items.current = (current + 1) % #items + 1
        elseif wm.kb == sdl.SDLK_LEFT   then main_menu.current = (main_menu.current - 2) % #main_menu + 1
        elseif wm.kb == sdl.SDLK_RIGHT  then main_menu.current = main_menu.current % #main_menu + 1
        elseif wm.kb == sdl.SDLK_RETURN then main_menu.opened = not main_menu.opened end
        wm.kb = 0
        
        gfx:list_box( items, 10, wm.height/2-50, 100, 200 )
        gfx:list_box( items, 170, wm.height/2-25, 175, 300 )
        gfx:main_menu( main_menu, 0, 0, wm.width, 100 )
        
        sdl.SDL_UpperBlit( sdl_surface, nil, wm.window, nil )
     end
  end
  
  
  