require "font"
require "rendertext"
require "keys"
require "graphics"

MODE_CALC = 1
MODE_TERM = 2

----------------------------------------------------
-- General inputbox
----------------------------------------------------
InputBox = {
	-- Class vars:
	h = 100,
	input_slot_w = nil,
	input_start_x = nil,
	input_start_y = nil,
	input_cur_x = nil, -- points to the start of next input pos

	input_bg = 0,
	input_string = "",
	cursor = nil,

	-- font for displaying input content
	-- we have to use mono here for better distance controlling
	face = Font:getFace("infont", 25),
	fheight = 25,
	fwidth = 15,
	commands = nil,

	vk_bg = 3,
	charlist = {}, -- table to store input string
	charpos = 1,
	INPUT_KEYS = {}, -- table to store layouts
	-- values to control layouts: min & max
	min_layout = 2,
	max_layout = 9,
	layout = 3,
	-- now bits to toggle the layout mode
	shiftmode = true,	-- toggle chars <-> capitals,	lowest bit in (layout-2)
	symbolmode = false,	-- toggle chars <-> symbols,	middle bit in (layout-2)
	utf8mode = false,	-- toggle english <-> national,	highest bit in (layout-2)
	inputmode,		-- define mode: input <> calculator <> terminal
	calcfunctions = nil, -- math functions for calculator helppage
}

function InputBox:new(o)
	o = o or {}
	setmetatable(o, self)
	self.__index = self
	return o
end

function InputBox:refreshText()
	-- clear previous painted text
	fb.bb:paintRect(self.input_start_x-5, self.input_start_y-19, self.input_slot_w, self.fheight, self.input_bg)
	-- paint new text
	renderUtf8Text(fb.bb, self.input_start_x, self.input_start_y, self.face, self.input_string, true)
end

function InputBox:addChar(char)
	self.cursor:clear()
	-- draw new text
	local cur_index = (self.cursor.x_pos + 3 - self.input_start_x) / self.fwidth
	table.insert(self.charlist, self.charpos, char)
	self.charpos = self.charpos + 1
	self.input_string = self:CharlistToString()
	self:refreshText()
	self.input_cur_x = self.input_cur_x + self.fwidth
	-- draw new cursor
	self.cursor:moveHorizontal(self.fwidth)
	self.cursor:draw()
	Screen:refresh(1, nil, self.input_start_x-5, self.input_start_y-25, self.input_slot_w, self.h-25)
end

function InputBox:delChar()
	if self.input_start_x == self.input_cur_x then return end
	local cur_index = (self.cursor.x_pos + 3 - self.input_start_x) / self.fwidth
	if cur_index == 0 then return end
	self.cursor:clear()
	-- draw new text
	self.charpos = self.charpos - 1
	table.remove(self.charlist, self.charpos)
	self.input_string = self:CharlistToString()
	self:refreshText()
	self.input_cur_x = self.input_cur_x - self.fwidth
	-- fill last character with blank rectangle
	fb.bb:paintRect(self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight, self.input_bg)
	Screen:refresh(1, nil, self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight)
	-- draw new cursor
	self.cursor:moveHorizontal(-self.fwidth)
	self.cursor:draw()
	Screen:refresh(1, nil, self.input_start_x-5, self.input_start_y-25, self.input_slot_w, self.h-25)
end

function InputBox:clearText()
	self.cursor:clear()
	self.input_string = ""
	self.charlist = {}
	self.charpos = 1
	self:refreshText()
	self.cursor.x_pos = self.input_start_x - 3
	self.cursor:draw()
	Screen:refresh(1, nil, self.input_start_x-5, self.input_start_y-25, self.input_slot_w, self.h-25)
end

function InputBox:drawBox(ypos, w, h, title)
	-- draw input border
	local r = 6 -- round corners
	fb.bb:paintRect(1, ypos+r, w, h - r, self.vk_bg)
	blitbuffer.paintBorder(fb.bb, 1, ypos, fb.bb:getWidth() - 2, r, r, self.vk_bg, r)
	-- draw input title
	self.input_start_y = ypos + 37
	-- draw the box title > estimate the start point for future text & the text slot width
	self.input_start_x = 25 + renderUtf8Text(fb.bb, 15, self.input_start_y, self.face, title, true)
	self.input_slot_w = fb.bb:getWidth() - self.input_start_x - 5
	-- draw input slot
	fb.bb:paintRect(self.input_start_x - 5, ypos + 10, self.input_slot_w, h - 20, self.input_bg)
end

----------------------------------------------------------------------
-- InputBox:input()
--
-- @title: input prompt for the box
-- @d_text: default to nil (used to set default text in input slot)
-- @is_hint: if this arg is true, default text will be used as hint
-- message for input
----------------------------------------------------------------------
function InputBox:input(ypos, height, title, d_text, is_hint)
	-- To avoid confusion with old ypos & height parameters, I'd better define
	-- my own position, at the bottom screen edge
	ypos = fb.bb:getHeight() - 165
	-- some corrections for calculator mode
	if self.inputmode == MODE_CALC then
		self:setCalcMode()
	end

	-- at first, draw titled box and content
	local h, w = 55, fb.bb:getWidth() - 2
	self:drawBox(ypos, w, h, title)
	-- do some initilization
	self.ypos = ypos
	self.h = 100
	self.input_cur_x = self.input_start_x
	self:addAllCommands()
	self.cursor = Cursor:new {
		x_pos = self.input_start_x - 3,
		y_pos = ypos + 13,
		h = 30,
	}

	if d_text then
		if is_hint then
			-- print hint text
			fb.bb:paintRect(self.input_start_x-5, self.input_start_y-19, self.input_slot_w, self.fheight, self.input_bg)
			renderUtf8Text(fb.bb, self.input_start_x+5, self.input_start_y, self.face, d_text, 0)
			fb.bb:dimRect(self.input_start_x-5, self.input_start_y-19, self.input_slot_w, self.fheight, self.input_bg)
		else
			-- add text to input_string
			self:StringToCharlist(d_text)
			self.input_cur_x = self.input_cur_x + (self.fwidth * #self.charlist)
			self.cursor.x_pos = self.cursor.x_pos + (self.fwidth * #self.charlist)
			self:refreshText()
		end
	end
	self.cursor:draw()
	Screen:refresh(1, nil, 1, ypos, w, h)

	local ev, keydef, command, ret_code
	while true do
		ev = input.saveWaitForEvent()
		ev.code = adjustKeyEvents(ev)
		if ev.type == EV_KEY and ev.value ~= EVENT_VALUE_KEY_RELEASE then
			keydef = Keydef:new(ev.code, getKeyModifier())
			Debug("key pressed: "..tostring(keydef))
			command = self.commands:getByKeydef(keydef)
			if command ~= nil then
				Debug("command to execute: "..tostring(command))
				ret_code = command.func(self, keydef)
			else
				Debug("command not found: "..tostring(command))
			end
			if ret_code == "break" then
				ret_code = nil
				break
			end
		end -- if
	end -- while

	local output = self.input_string
	self.input_string = ""
	self.charlist = {}
	self.charpos = 1
	return output
end

function InputBox:setLayoutsTable()
	-- trying to read the layout from the user-defined file
	local ok, stored = pcall(dofile, lfs.currentdir() .. "/mykeyboard.lua")
	if ok then
		self.INPUT_KEYS = stored
	else	-- if an error happens, we use the default layout
		self.INPUT_KEYS = {
			{ KEY_Q,	"Q",	"q",	"1",	"!",		"Я",	"я",	"1",	"!", },
			{ KEY_W,	"W",	"w",	"2",	"?",		"Ж",	"ж",	"2",	"?", },
			{ KEY_E,	"E",	"e",	"3",	"|",		"Е",	"е",	"3",	"«", },
			{ KEY_R,	"R",	"r",	"4",	"#",		"Р",	"р",	"4",	"»", },
			{ KEY_T,	"T",	"t",	"5",	"@",		"Т",	"т",	"5",	":", },
			{ KEY_Y,	"Y",	"y",	"6",	"‰",		"Ы",	"ы",	"6",	";", },
			{ KEY_U,	"U",	"u",	"7",	"'",		"У",	"у",	"7",	"~", },
			{ KEY_I,	"I",	"i",	"8",	"`",		"И",	"и",	"8",	"(",},
			{ KEY_O,	"O",	"o",	"9",	":",		"О",	"о",	"9",	")",},
			{ KEY_P,	"P",	"p",	"0",	";",		"П",	"п",	"0",	"=", },
			-- middle raw
			{ KEY_A,	"A",	"a",	"+",	"…",		"А",	"а",	"Ш",	"ш", },
			{ KEY_S,	"S",	"s",	"-",	"_",		"С",	"с",	"Ѕ",	"ѕ", },
			{ KEY_D,	"D",	"d",	"*",	"=",		"Д",	"д",	"Э",	"э", },
			{ KEY_F,	"F",	"f",	"/",	"\\",		"Ф",	"ф",	"Ю",	"ю", },
			{ KEY_G,	"G",	"g",	"%",	"„",		"Г",	"г",	"Ґ",	"ґ", },
			{ KEY_H,	"H",	"h",	"^",	"“",		"Ч",	"ч",	"Ј",	"ј", },
			{ KEY_J,	"J",	"j",	"<",	"”",		"Й",	"й",	"І",	"і", },
			{ KEY_K,	"K",	"k",	"=",	"\"",		"К",	"к",	"Ќ",	"ќ", },
			{ KEY_L,	"L",	"l",	">",	"~",		"Л",	"л",	"Љ",	"љ", },
			-- lowest raw
			{ KEY_Z,	"Z",	"z",	"(",	"$",		"З",	"з",	"Щ",	"щ", },
			{ KEY_X,	"X",	"x",	")",	"€",		"Х",	"х",	"№",	"@", },
			{ KEY_C,	"C",	"c",	"&",	"¥",		"Ц",	"ц",	"Џ",	"џ", },
			{ KEY_V,	"V",	"v",	":",	"£",		"В",	"в",	"Ў",	"ў", },
			{ KEY_B,	"B",	"b",	"π",	"‚",		"Б",	"б",	"Ћ",	"ћ", },
			{ KEY_N,	"N",	"n",	"е",	"‘",		"Н",	"н",	"Њ",	"њ", },
			{ KEY_M,	"M",	"m",	"~",	"’",		"М",	"м",	"Ї",	"ї", },
			{ KEY_DOT,	",",	".",	".",	",",		",",	".",	"Є",	"є", },
			-- Let us make key 'Space' the same for all layouts
			{ KEY_SPACE," ",	" ",	" ",	" ",		" ",	" ",	" ",	" ", },
			-- Simultaneous pressing Alt + Q..P should also work properly everywhere
			{ KEY_1,	"1",	"1",	"1",	"1",		"1",	"1",	"1",	"1", },
			{ KEY_2,	"2",	"2",	"2",	"2",		"2",	"2",	"2",	"2", },
			{ KEY_3,	"3",	"3",	"3",	"3",		"3",	"3",	"3",	"3", },
			{ KEY_4,	"4",	"4",	"4",	"4",		"4",	"4",	"4",	"4", },
			{ KEY_5,	"5",	"5",	"5",	"5",		"5",	"5",	"5",	"5", },
			{ KEY_6,	"6",	"6",	"6",	"6",		"6",	"6",	"6",	"6", },
			{ KEY_7,	"7",	"7",	"7",	"7",		"7",	"7",	"7",	"7", },
			{ KEY_8,	"8",	"8",	"8",	"8",		"8",	"8",	"8",	"8", },
			{ KEY_9,	"9",	"9",	"9",	"9",		"9",	"9",	"9",	"9", },
			{ KEY_0,	"0",	"0",	"0",	"0",		"0",	"0",	"0",	"0", },
			-- DXG keys
			{ KEY_SLASH,"/",	"\\",	"/",	"\\",		"/",	"\\",	"/",	"\\", },
		}
	end -- if ok
end

function InputBox:DrawVKey(key,x,y,face,rx,ry,t,c)
	blitbuffer.paintBorder(fb.bb, x-11, y-ry-8, rx, ry, t, c, ry)
	renderUtf8Text(fb.bb, x, y, face, key, true)
end

-- this function is designed for K3 keyboard, portrait mode
-- TODO: support for other Kindle models & orientations?

function InputBox:DrawVirtualKeyboard()
	local vy = fb.bb:getHeight()-15
	-- dx, dy = xy-distance between the button rows
	-- lx - position of left button column
	-- r, c, t = radius, color and thickness of circles around chars
	-- h = y-correction to adjust cicles & chars
	local dx, dy, lx, r, c, bg, t = 51, 36, 20, 17, 6, self.vk_bg, 2

	fb.bb:paintRect(1, fb.bb:getHeight()-120, fb.bb:getWidth()-2, 120, bg)
	-- font to draw characters - MUST have UTF8-support
	local vkfont = Font:getFace("infont", 22)
	for k,v in ipairs(self.INPUT_KEYS) do
		if v[1] >= KEY_Q and v[1] <= KEY_P then	-- upper raw
			self:DrawVKey(v[self.layout], lx+(v[1]-KEY_Q)*dx, vy-2*dy, vkfont, r, r, t, c)
		elseif v[1] >= KEY_A and v[1] <= KEY_L then	-- middle raw
			self:DrawVKey(v[self.layout], lx+(v[1]-KEY_A)*dx, vy-dy, vkfont, r, r, t, c)
		elseif v[1] >= KEY_Z and v[1] <= KEY_M then	-- lower raw
			self:DrawVKey(v[self.layout], lx+(v[1]-KEY_Z)*dx, vy, vkfont, r, r, t, c)
		elseif v[1] == KEY_DOT then
			self:DrawVKey(v[self.layout], lx + 7*dx, vy, vkfont, r, r, t, c)
		end
	end
	-- the rest symbols (manually)
	local smfont = Font:getFace("infont", 14)
	-- Del
	blitbuffer.paintBorder(fb.bb, lx+9*dx-10, vy-dy-r-8, r, r, t, c, r)
	renderUtf8Text(fb.bb, lx-5+9*dx, vy-dy-3, smfont, "Del", true)
	-- Sym
	blitbuffer.paintBorder(fb.bb, lx+8*dx-10, vy-r-8, r, r, t + (r-t)*self:num(self.symbolmode), c, r)
	renderUtf8Text(fb.bb, lx-5+8*dx, vy-3, smfont, "Sym", true)
	-- Enter
	blitbuffer.paintBorder(fb.bb, lx+9*dx-10, vy-r-8, r, r, t, c, r)
	renderUtf8Text(fb.bb, lx+9*dx, vy-2, vkfont, "«", true)
	-- Menu
	blitbuffer.paintBorder(fb.bb, lx+10*dx-8, vy-2*dy-r-8, r+50, r, t+(r-t)*self:num(self.utf8mode), c, r)
	renderUtf8Text(fb.bb, lx+10*dx+11, vy-2*dy-3, smfont, "Menu", true)
	-- fiveway
	local h=dy+2*r-2
	blitbuffer.paintBorder(fb.bb, lx+10*dx-8, vy-dy-r-6, h, h, 9, c, r)
	renderUtf8Text(fb.bb, lx+10*dx+22, vy-20, smfont, (self.layout-1), true)
	Screen:refresh(1, nil, 1, fb.bb:getHeight()-120, fb.bb:getWidth()-2, 120)
end

function InputBox:num(bool)
	return bool and 1 or 0
end

function InputBox:VKLayout(b1, b2, b3)
	return 2 + self:num(b1) + 2 * self:num(b2) + 4 * self:num(b3)
end

function InputBox:addCharCommands(layout)
	-- at first, let's define self.layout and extract separate bits as layout modes
	if layout then
		-- to be sure layout is selected properly
		layout = math.max(layout, self.min_layout)
		layout = math.min(layout, self.max_layout)
		self.layout = layout
		-- fill the layout modes
		layout = (layout - 2) % 4
		self.shiftmode  = (layout == 1 or layout == 3)
		self.symbolmode = (layout == 2 or layout == 4)
		self.utf8mode   = (self.layout > 5)
	else	-- or, without input parameter, restore layout from current layout modes
		self.layout = self:VKLayout(self.shiftmode, self.symbolmode, self.utf8mode)
	end
	-- let's define layout called by Shift+Key (to type capitalized chars being in low-case layout)
	local shift_layout = self:VKLayout(not self.shiftmode, self.symbolmode, self.utf8mode)
	-- adding the commands
	for k,v in ipairs(self.INPUT_KEYS) do
		-- just redefining existing
		self.commands:add(v[1], nil, "A..Z", "enter character from virtual keyboard (VK)",
			function(self)
				self:addChar(v[self.layout])
			end
		)
		-- and commands for chars pressed with Shift
		self.commands:add(v[1], MOD_SHIFT, "A..Z", "enter capitalized VK-character",
			function(self)
				self:addChar(v[shift_layout])
			end
		)
	end
	self:DrawVirtualKeyboard()
end

function InputBox:StringToCharlist(text)
	if text == nill then return end
	-- clear
	self.charlist = {}
	self.charpos = 1
	local prevcharcode, charcode = 0
	for uchar in string.gfind(text, "([%z\1-\127\194-\244][\128-\191]*)") do
		charcode = util.utf8charcode(uchar)
		if prevcharcode then -- utf8
			self.charlist[#self.charlist+1] = uchar
		end
		prevcharcode = charcode
	end
	self.input_string = self:CharlistToString()
	self.charpos = #self.charlist+1
end

function InputBox:CharlistToString()
	local s, i = ""
	for i=1, #self.charlist do
		s = s .. self.charlist[i]
	end
	return s
end

function InputBox:addAllCommands()
	-- if already initialized, we (re)define only inputmode-dependent commands
	if self.commands then
		self:ModeDependentCommands()
		self:DrawVirtualKeyboard()
		return
	end
	self:setLayoutsTable()
	self.commands = Commands:new{}
	-- adding character commands
	self:addCharCommands(self.layout)
	-- adding the rest commands (independent of the selected layout)
	self.commands:add(KEY_H, MOD_ALT, "H",
		"show help page",
		function(self)
			self:showHelpPage(self.commands)
		end
	)
	self.commands:add(KEY_FW_LEFT, nil, "joypad left",
		"move cursor left",
		function(self)
			if (self.cursor.x_pos + 3) > self.input_start_x then
				self.cursor:moveHorizontalAndDraw(-self.fwidth)
				self.charpos = self.charpos - 1
				Screen:refresh(1, nil, self.input_start_x-5, self.ypos, self.input_slot_w, self.h)
			end
		end
	)
	self.commands:add(KEY_FW_LEFT, MOD_SHIFT, "left",
		"move cursor to the first position",
		function(self)
			if (self.cursor.x_pos + 3) > self.input_start_x then
				self.cursor:moveHorizontalAndDraw(-self.fwidth*(self.charpos-1))
				self.charpos = 1
				Screen:refresh(1, nil, self.input_start_x-5, self.ypos, self.input_slot_w, self.h)
			end
		end
	)
	self.commands:add(KEY_FW_RIGHT, nil, "joypad right",
		"move cursor right",
		function(self)
			if (self.cursor.x_pos + 3) < self.input_cur_x then
				self.cursor:moveHorizontalAndDraw(self.fwidth)
				self.charpos = self.charpos + 1
				Screen:refresh(1, nil, self.input_start_x-5, self.ypos, self.input_slot_w, self.h)
			end
		end
	)
	self.commands:add(KEY_FW_RIGHT, MOD_SHIFT, "right",
		"move cursor to the last position",
		function(self)
			if (self.cursor.x_pos + 3) < self.input_cur_x then
				self.cursor:moveHorizontalAndDraw(self.fwidth*(#self.charlist+1-self.charpos))
				self.charpos = #self.charlist + 1
				Screen:refresh(1, nil, self.input_start_x-5, self.ypos, self.input_slot_w, self.h)
			end
		end
	)
	self.commands:add(KEY_DEL, nil, "Del",
		"delete one character",
		function(self)
			self:delChar()
		end
	)
	self.commands:add(KEY_DEL, MOD_SHIFT, "Del",
		"delete all characters (empty inputbox)",
		function(self)
			self:clearText()
		end
	)
	self.commands:addGroup("up/down", { Keydef:new(KEY_FW_DOWN, nil), Keydef:new(KEY_FW_UP, nil) },
		"previous/next VK-layout",
		function(self)
			if keydef.keycode == KEY_FW_DOWN then
				if self.layout == self.max_layout then self:addCharCommands(self.min_layout)
				else self:addCharCommands(self.layout+1) end
			else -- KEY_FW_UP
				if self.layout == self.min_layout then self:addCharCommands(self.max_layout)
				else self:addCharCommands(self.layout-1) end
			end
			
		end
	)
	self.commands:add(KEY_AA, nil, "Aa",
		"toggle VK-layout: chars <> CHARS",
		function(self)
			self.shiftmode = not self.shiftmode
			self:addCharCommands()
		end
	)
	self.commands:add(KEY_SYM, nil, "Sym",
		"toggle VK-layout: chars <> symbols",
		function(self)
			self.symbolmode = not self.symbolmode
			self:addCharCommands()
		end
	)
	self.commands:add(KEY_MENU, nil, "Menu",
		"toggle VK-layout: english <> national",
		function(self)
			self.utf8mode = not self.utf8mode
			self:addCharCommands()
		end
	)
	-- NuPogodi, 02.06.12: inputmode-dependent commands are collected
	self:ModeDependentCommands() -- here

	self.commands:add(KEY_BACK, nil, "Back",
		"back",
		function(self)
			self.input_string = nil
			return "break"
		end
	)
end
-----------------------------------------------------------------
-- NuPogodi, 02.06.12: Some Help- & Calculator-related functions
-----------------------------------------------------------------
function InputBox:defineCalcFunctions() -- for the calculator documentation
	-- to initialize only once
	if self.calcfunctions then return end

	self.calcfunctions = Commands:new{}
	-- remove initially added commands
	self.calcfunctions:del(KEY_INTO_SCREEN_SAVER, nil, "Slider") 
	self.calcfunctions:del(KEY_OUTOF_SCREEN_SAVER, nil, "Slider")
	self.calcfunctions:del(KEY_CHARGING, nil, "plugin/out usb")
	self.calcfunctions:del(KEY_NOT_CHARGING, nil, "plugin/out usb")
	self.calcfunctions:del(KEY_P, MOD_SHIFT, "P")

	local s = " " -- space for function groups
	local a = 100 -- arithmetic functions
	self.calcfunctions:add(a-1, nil,	s:rep(1),	string.upper("Ariphmetic operators"))
	self.calcfunctions:add(a,   nil,	"+ -",		"addition: 1+2=3; substraction: 3-2=1")
	self.calcfunctions:add(a+1, nil,	"* /",		"multiplication: 2*2=4; division: 4/2=2")
	self.calcfunctions:add(a+3, nil,	"%",		"modulo (remainder): 5.2%2=1.2, π-π%0.01=3.14")
	local r = 200 -- relations
	self.calcfunctions:add(r-1, nil,	s:rep(2),	string.upper("Relational operators"))
	self.calcfunctions:add(r,   nil,	"< >",		"less: (2<3)=true; more: (2>3)=false")
	self.calcfunctions:add(r+1, nil,	"<=",		"less or equal: (3≤3)=true, (2≤1)=false")
	self.calcfunctions:add(r+2, nil,	">=",		"more or equal: (3≥3)=true, (1≥2)=false")
	self.calcfunctions:add(r+3, nil,	"==",		"equal: (3==3)=true, (1==2)=false")
	self.calcfunctions:add(r+4, nil,	"~=",		"not equal: (6~=8)=true, (3~=3)=false")
	local l = 300 -- logical
	self.calcfunctions:add(l-1, nil,	s:rep(3),	string.upper("Logical operators"))
	self.calcfunctions:add(l+0, nil,	"and, &",	"= logical 'and': (4 and 5)=5, (nil & 5)=nil")
	self.calcfunctions:add(l+1, nil,	"or, |",	"= logical 'or': (4 or 5)=4, (false | 5)=5")
	local c = 400 -- constants
	self.calcfunctions:add(c-1, nil,	s:rep(4),	string.upper("Some constants"))
	self.calcfunctions:add(c,   nil,	"pi, π",	"= 3.14159…; sin(π/2)=1, cos(π/2)=0")
	self.calcfunctions:add(c+1, nil,	"е, exp(1)",	"= 2.71828…; log(е)=1")
	local m = 500 -- mathematical
	self.calcfunctions:add(m-1, nil,	s:rep(5),	string.upper("Mathematic functions"))
	self.calcfunctions:add(m,   nil,	"abs(x)",	"absolute value of x: abs(1)=1, abs(-2)=2")
	self.calcfunctions:add(m+1, nil,	"ceil(x)",	"round to integer no less than x: ceil(0.4)=1")
	self.calcfunctions:add(m+2, nil,	"floor(x)",	"round to integer no greater than x: floor(0.4)=0")
	self.calcfunctions:add(m+3, nil,	"^, pow(x,y)","= power: 2^10=1024, pow(4,0.5)=2")
	self.calcfunctions:add(m+4, nil,	"exp(x), e^x","= exponent: exp(1)=2.71828…")
	self.calcfunctions:add(m+5, nil,	"log(x)",	"the natural logarithm: log(e)=1")
	self.calcfunctions:add(m+6, nil,	"log10(x)",	"the base 10 logarithm: log10(10)=1")
	self.calcfunctions:add(m+7, nil,	"max(x,…)",	"return maximal value: max(0,-1,2,1)=2")
	self.calcfunctions:add(m+8, nil,	"min(x,…)",	"return minimal value: min(0,-1,2,1)=-1")
	self.calcfunctions:add(m+9, nil,	"sqrt(x)",	"return square root: sqrt(4)=2")
	local t = 600 -- trigonometrical
	self.calcfunctions:add(t,   nil,	s:rep(6),	string.upper("Trigonometric functions"))
	self.calcfunctions:add(t+1, nil,	"deg(x)",	"convert radians to degrees: deg(π/2)=90")
	self.calcfunctions:add(t+2, nil,	"rad(x)",	"convert degrees to radians: rad(180)=3.14159…")
	self.calcfunctions:add(t+3, nil,	"sin(x)",	"sine for x given in radians: sin(π/2)=1")
	self.calcfunctions:add(t+4, nil,	"cos(x)",	"cosine for x given in radians: cos(π)=-1")
	self.calcfunctions:add(t+5, nil,	"tan(x)",	"tangent for x given in radians: tan(π/4)=1")
	self.calcfunctions:add(t+6, nil,	"asin(x)",	"inverse sine (in radians): asin(1)/π=0.5")
	self.calcfunctions:add(t+7, nil,	"acos(x)",	"inverse cosine (in radians): acos(0)/π=0.5")
	self.calcfunctions:add(t+8, nil,	"atan(x)",	"inverse tangent (in radians): atan(1)/π=0.25")
	self.calcfunctions:add(t+9, nil,	"atan2(x,y)",	"inverse tangent of two args: = atan(x/y)")
	local h = 700 -- hyperbolical
	self.calcfunctions:add(h,   nil,	s:rep(7),	string.upper("Hyperbolic functions"))
	self.calcfunctions:add(h+1, nil,	"sinh(x)",	"hyperbolic sine, (exp(x)-exp(-x))/2")
	self.calcfunctions:add(h+2, nil,	"cosh(x)",	"hyperbolic cosine, (exp(x)+exp(-x))/2")
	self.calcfunctions:add(h+3, nil,	"tanh(x)",	"hyperbolic tangent, sinh(x)/cosh(x)")
-- not yet documented > "fmod", "frexp", "huge", "ldexp", "modf", "randomseed", "random"
end

function InputBox:showHelpPage(list, title)
	-- make inactive input slot
	self.cursor:clear() -- hide cursor
	fb.bb:dimRect(self.input_start_x-5, self.input_start_y-19, self.input_slot_w, self.fheight, self.input_bg)
	Screen:refresh(1, nil, self.input_start_x-5, self.ypos, self.input_slot_w, self.h)
	HelpPage:show(0, fb.bb:getHeight()-165, list, title)
	-- on the help page-exit, making inactive helpage
	fb.bb:dimRect(0, 40, fb.bb:getWidth(), fb.bb:getHeight()-205, self.input_bg)
	-- and active input slot
	self:refreshText()
	self.cursor:draw() -- show cursor = ready to input
	Screen:refresh(1)
end

function InputBox:setCalcMode()
	--clear previous input
	self.input_string = ""
	self.charlist = {}
	self.charpos = 1
	-- set proper layouts
	self.layout = 4 -- digits
	self.min_layout = 3
	self.max_layout = 4
end

function InputBox:PrepareStringToCalc()
	local s = string.lower(self.input_string)
	-- continue interpreting the input
	local mathe = {	"abs", "acos", "asin", "atan2", "atan", "ceil", "cosh", "cos",
				"deg", "exp", "floor", "fmod", "frexp", "huge", "ldexp", "log10", "log",
				"max", "min", "modf", "pi", "pow", "rad", "randomseed", "random",
				"sinh", "sin", "sqrt", "tanh", "tan", }
	-- to avoid any ambiguities (like sin & sinh), one has to replace by capitals
	for i=1, #mathe do
		s = string.gsub(s, mathe[i], string.upper("math."..mathe[i]))
	end
	-- some acronyms for constants & functions
	s = string.gsub(s, "π", " math.pi ")
	s = string.gsub(s, "е", " math.exp(1) ")
	s = string.gsub(s, "&", " and ")
	s = string.gsub(s, "|", " or ")
	-- return the whole string in lowercase and eventually replace double "math."
	return string.gsub(string.lower(s), "math.math.", "math.")
end

-- define whether we need to calculate the result or to return 'self.input_string'
function InputBox:ModeDependentCommands()
	if self.inputmode == MODE_CALC then
		-- define what to do with the input_string
		self.commands:add({KEY_FW_PRESS, KEY_ENTER}, nil, "Enter",
			"calculate the result",
			function(self)
				if #self.input_string == 0 then
					InfoMessage:inform("No user input ", DINFO_DELAY, 1, MSG_WARN)
				else
					local s = self:PrepareStringToCalc()
					if pcall(function () f = assert(loadstring("r = tostring("..s..")")) end) and pcall(f) then
						self:clearText()
						self.cursor:clear()
						for i=1, string.len(r) do
							table.insert(self.charlist, string.sub(r,i,i))
						end
						self.charpos = #self.charlist + 1
						self.input_cur_x = self.input_start_x + #self.charlist * self.fwidth
						self.input_string = r
						self:refreshText()
						self.cursor:moveHorizontal(#self.charlist*self.fwidth)
						self.cursor:draw()
						Screen:refresh(1, nil, self.input_start_x-5, self.input_start_y-25, self.input_slot_w, self.h-25)
					else
						InfoMessage:inform("Invalid user input ", DINFO_DELAY, 1, MSG_WARN)
					end -- if pcall
				end
			end -- function
			)
		-- add the calculator help (short list of available functions)
		-- or, might be better, to make some help document and open it in reader ??
		self.commands:add(KEY_M, MOD_ALT, "M",
			"math functions available in calculator",
			function(self)
				self:defineCalcFunctions()
				self:showHelpPage(self.calcfunctions, "Math Functions for Calculator")
			end
			)
	else 	-- return input_string & close input box
		self.commands:add({KEY_FW_PRESS, KEY_ENTER}, nil, "Enter",
			"submit input content",
			function(self)
				if self.input_string == "" then
					self.input_string = nil
				end
				return "break"
			end
			)
		-- delete calculator-specific help
		self.commands:del(KEY_M, MOD_ALT, "M")
	end -- if self.inputmode
end

----------------------------------------------------
-- Inputbox for numbers only
-- Designed by eLiNK
----------------------------------------------------

NumInputBox = InputBox:new{
	layout = 4,
	charlist = {},
}
