--
-- Event-driven GUI library
--
--

gui = {

--
-- colors
--
black = 0,
dark  = 1,
light = 2,
white = 3,

-- "do nothing" function. Used as default callback
donothing = function(self)
end,

-- Metatable that hides the field "value" behind a property,
-- and calls render() after it's set.
propvalue = {
  __newindex = function(self, index, value)
    if index == "value" then
      self._value = value
      -- extra processing
      self:render()
      else
        rawset(self, index, value)
      end
  end,
  __index = function(self, index )
    if index == "value" then
      return self._value
    else
      return rawget( self, index )
    end
  end
},

--
-- dialog()
--
dialog = function(args)
  local dia = {
    title = args.title,
    w = args.w,
    h = args.h,
    -- 
    widgets = {},
    -- an indexed array, starting at 1. Used for calling the relevant
    -- callback when a numbered control is clicked.  
    callbacks = {},
    
    --
    -- dialog.run() --
    --
    run = function(self)
      windowopen(self.w,self.h, self.title or "");
      -- examine all elements
      for _,widget in ipairs(self.widgets) do
        widget:create()
      end
      
      repeat
       local button, button2, key = windowdodialog();
      
        if button > 0 then
          local c = self.callbacks[button]
          if c ~= nil then
            -- run the callback
            local retvalue = c:click()
            -- stop the form if it returns non-nil
            if retvalue ~= nil then
              windowclose();
              return retvalue;
            end
          end
        end
      until key == 27;
      windowclose();
    end
  }
  local id = 1;
  -- examine all elements
  for _,value in ipairs(args) do
    -- all arguments that are tables are assumed to be widgets
    if type(value)=="table" then
      table.insert(dia.widgets, value)
      -- clickable widgets take up an auto-numbered id
      if (value.click) then
        dia.callbacks[id] = value
        id=id+1
      end
    end
  end
  return dia;
end,

--
-- button()
--
button = function(args)
  local but = {
    x = args.x,
    y = args.y,
    w = args.w,
    h = args.h,
    key = args.key,
    label = args.label,
    click = args.click or gui.donothing,
    create = args.repeatable and function(self)
      windowrepeatbutton(self.x, self.y, self.w, self.h, self.label, self.key or -1);
    end
    or function(self)
      windowbutton(self.x, self.y, self.w, self.h, self.label, self.key or -1);
    end
  }
  return but;
end,

--
-- label()
--
label = function(args)
  local lbl = {
    x = args.x,
    y = args.y,
    _value = args.value,
    format = args.format,
    fg = args.fg or gui.black,
    bg = args.bg or gui.light,
    render = function(self)
      if type(self.format) then
        windowprint(self.x, self.y, string.format(self.format, self._value), self.fg, self.bg);
      else
        windowprint(self.x, self.y, self._value, self.fg, self.bg);
      end
    end,
  }
  lbl.create = lbl.render
  setmetatable(lbl, gui.propvalue)
  return lbl;
end,

--
-- textbox
--
textbox = function(args)
  local txtbox = {
    x = args.x,
    y = args.y,
    nbchar = args.nbchar, -- visible size in characters
    --format = args.format, -- numeric, decimal, path 
    decimal = args.decimal or 0,
    min = args.min,
    max = args.max,
    maxchar = args.maxchar, -- internal size
    _value = args.value,
    change = args.change or gui.donothing,
    --fg = args.fg or gui.black,
    --bg = args.bg or gui.light,
    create = function(self)
      windowinput(self.x, self.y, self.nbchar)
      self:render()
    end,
    render = function(self)
      local val = tostring(self._value)
      if string.len(val) < self.nbchar then
        val = string.rep(" ",self.nbchar - string.len(val)) .. val; 
      elseif string.len(val) > self.nbchar then
        val = string.sub(val, 1, self.nbchar-1) .. gui.char.ellipsis
      end
      windowprint(self.x, self.y, val, gui.black, gui.light);
    end,
    click = function(self)
      local inputtype
      if (type(self._value) == "number" and ((self.min ~= nil and self.min<0) or self.decimal > 0)) then
        inputtype = 3 -- entry as double
      elseif (type(self._value) == "number") then
        inputtype = 1 -- entry as unsigned int
      else
        inputtype = 0 -- entry as string
      end
      local accept, val = windowreadline(self.x, self.y, self._value, self.nbchar, self.maxchar, self.decimal, inputtype);
      
      if accept then
        if (inputtype == 1 or inputtype == 3) then
          val = tonumber(val)
          -- round the decimal places
          val = gui.round(val, self.decimal)
        end
        if (self.min ~= nil and val < self.min) then
          val = self.min
        end
        if (self.max ~= nil and val > self.max) then
          val = self.max
        end
        
        self._value = val
      end
      self:render()
    end
  }
  setmetatable(txtbox, gui.propvalue)
  return txtbox;
end

}

gui.round = function(val, ipt)
  local mult = 10^ipt
  return math.floor(val * mult + 0.5) / mult
end

-- Character constants. May be useful in screens
gui.char = {
  ellipsis = string.char(133), -- ...
  arrowup = string.char(24),
  arrowdown = string.char(25),
  arrowleft = string.char(27),
  arrowright = string.char(26),
  vertical = string.char(18), -- double-ended arrow
  horizontal = string.char(29) -- double-ended arrow
}