
local GUI = require("GUI")
local system = require("System")
local color = require("Color")
local screen = require("Screen")
local paths = require("Paths")

--------------------------------------------------------------------

local buttonWidth = 7
local buttonHeight = 3
local displayHeight = 5
local binaryHeight = 8

local value = 0
local memory = 0
local absValue
local oldValue
local action

local binaryButtons = {}
local digitHexadecimalButtons = {}
local digitOctalButtons = {}
local digitDecimalButtons = {}

--------------------------------------------------------------------

local workspace, window, menu = system.addWindow(GUI.window(1, 1, buttonWidth * 12, buttonHeight * 5 + displayHeight + binaryHeight))

local displayContainer = window:addChild(GUI.container(1, 1, window.width, displayHeight + binaryHeight))

local displayPanel = displayContainer:addChild(GUI.panel(1, 1, window.width, displayHeight, 0x1E1E1E, 0.1))

local actionButtons = window:addChild(GUI.actionButtons(3, 3, true))

actionButtons.close.onTouch = function()
	window:remove()
end

actionButtons.minimize.onTouch = function()
	window:minimize()
end

local binaryPanel = displayContainer:addChild(GUI.panel(1, displayHeight + 1, window.width, binaryHeight, 0x2D2D2D))

local binaryLayout = displayContainer:addChild(GUI.layout(1, displayHeight + 2, window.width, binaryHeight, 1, 1))
binaryLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
binaryLayout:setSpacing(1, 1, 0)

local binaryListsLayout = binaryLayout:addChild(GUI.layout(1, 1, binaryLayout.width, 1, 1, 1))
binaryListsLayout:setSpacing(1, 1, 2)
binaryListsLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
binaryListsLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL)

binaryLayout:addChild(GUI.object(1, 1, 1, 1))

local function addBinaryLayoutList(width)
	local list = binaryListsLayout:addChild(GUI.list(1, 1, width, 1, 2, 0, 0x5A5A5A, 0xE1E1E1, 0x5A5A5A, 0xE1E1E1, 0xE1E1E1, 0x5A5A5A, true))
	list:setDirection(GUI.DIRECTION_HORIZONTAL)

	return list
end

-- local binaryPanelButton = binaryListsLayout:addChild(GUI.adaptiveButton(1, 1, 2, 0, 0x5A5A5A, 0xD2D2D2, 0xE1E1E1, 0x5A5A5A, "Binary panel"))
-- binaryPanelButton.switchMode = true

local charList = addBinaryLayoutList(18)
charList:addItem("UTF-8")
charList:addItem("ASCII")

local degereeList = addBinaryLayoutList(14)
degereeList:addItem("RAD")
degereeList:addItem("DEG")

local floatingList = addBinaryLayoutList(16)
floatingList:addItem("INT")
floatingList:addItem("FRACT")
table.insert(digitDecimalButtons, floatingList)

local modeList = addBinaryLayoutList(17)

modeList.selectedItem = 2

local displayWidget = displayContainer:addChild(GUI.object(10, 1, window.width - 12, displayHeight))

local function parseFloat(v)
	v = tostring(v)

	local integer, fractional = v:match("(.+)%.(.+)")
	if integer then
		return integer, fractional
	else
		return v, "0"
	end
end

local function isNumber(v)
	return v ~= "inf" and v ~= "-inf" and v ~= "nan"
end

local function format(v)
	local integer, fractional = parseFloat(v)

	if not isNumber(integer) then
		return integer
	elseif modeList.selectedItem == 1 then
		return string.format("%o", integer)
	elseif modeList.selectedItem == 2 then
		return integer .. "." .. fractional, #integer
	else
		return string.format("0x%X", integer)
	end
end

displayWidget.draw = function()
	local x, y = displayWidget.x + displayWidget.width, displayWidget.y + 2
	local result, integerLength = format(value)
	
	-- Result
	x = x - #result
	screen.drawText(x, y, 0xFFFFFF, result)
		
	-- Digit mode
	if modeList.selectedItem == 2 and integerLength then
		if floatingList.selectedItem == 1 then
			screen.drawText(x, y + 1, 0x696969, string.rep("─", integerLength))
		else
			screen.drawText(x + integerLength + 1, y + 1, 0x696969, string.rep("─", #result - integerLength - 1))
		end
	end

	-- Action and old value
	if oldValue then
		local oldValueText = format(oldValue) .. " " .. action.button.text .. " "
		screen.drawText(x - #oldValueText, y, 0xA5A5A5, oldValueText)
	end

	-- Char
	local char = charList.selectedItem == 1
		and (absValue <= 0x10FFFF and unicode.char(absValue) or "?") 
		or  (absValue <= 0xFF     and string.char (absValue) or "?")

	screen.drawText(displayWidget.x, y, 0x696969, "\"")
	screen.drawText(displayWidget.x + 1, y, 0xFFFFFF, char)
	screen.drawText(displayWidget.x + 2, y, 0x696969, "\"")
end

local function setValueRaw(v)
	value, absValue = v, math.floor(math.abs(v))
end

local function numberToBinary()
	for i = 1, #binaryButtons do
		binaryButtons[i].text = "0"
	end

	local i, copy = #binaryButtons, absValue
	while copy > 0 do
		binaryButtons[i].text = tostring(bit32.band(copy, 1))
		copy = bit32.rshift(copy, 1)

		i = i - 1
	end
end

local function setValue(v)
	setValueRaw(v)
	numberToBinary()
end

local function setButtonsDisabled(array, state)
	for i = 1, #array do
		array[i].disabled = state
	end
end

modeList:addItem("8").onTouch = function()
	setButtonsDisabled(digitOctalButtons, true)
	setButtonsDisabled(digitDecimalButtons, true)
	setButtonsDisabled(digitHexadecimalButtons, true)

	floatingList.selectedItem = 1
	setValue(math.floor(value))

	workspace:draw()
end

modeList:addItem("10").onTouch = function()
	setButtonsDisabled(digitOctalButtons, false)
	setButtonsDisabled(digitDecimalButtons, false)
	setButtonsDisabled(digitHexadecimalButtons, true)

	workspace:draw()
end

modeList:addItem("16").onTouch = function()
	setButtonsDisabled(digitOctalButtons, false)
	setButtonsDisabled(digitDecimalButtons, true)
	setButtonsDisabled(digitHexadecimalButtons, false)

	floatingList.selectedItem = 1
	setValue(math.floor(value))

	workspace:draw()
end

local function binaryToNumber()
	local firstNotNullBitIndex = 0

	for i = 1, #binaryButtons do
		if binaryButtons[i].text == "1" then
			firstNotNullBitIndex = i
			break
		end
	end

	local number = 1
	for i = firstNotNullBitIndex + 1, #binaryButtons do
		number = bit32.bor(bit32.lshift(number, 1), binaryButtons[i].text == "1" and 1 or 0)
	end

	setValueRaw(number)
end

local function addBinaryButtonsRowLayout()
	local layout = binaryLayout:addChild(GUI.layout(1, 1, binaryLayout.width, 2, 1, 1))
	layout:setSpacing(1, 1, 3)
	layout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_CENTER, GUI.ALIGNMENT_VERTICAL_TOP)
	layout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL)

	return layout
end

local function addBinaryButtonsContainer(row, leftText, rightText)
	local container = row:addChild(GUI.container(1, 1, 15, 2))
	local x = 1

	for i = 1, 8 do
		local button = container:addChild(GUI.button(x, 1, 1, 1, nil, 0x696969, nil, 0xFFFFFF, "0"))
	
		button.onTouch = function()
			button.text = button.text == "0" and "1" or "0"
			binaryToNumber()
		end

		table.insert(binaryButtons, button)

		x = x + 2
	end

	container:addChild(GUI.text(1, 2, 0xE1E1E1, leftText))
	container:addChild(GUI.text(container.width - #rightText + 1, 2, 0xE1E1E1, rightText))
end

local row1 = addBinaryButtonsRowLayout()
addBinaryButtonsContainer(row1, "64", "57")
addBinaryButtonsContainer(row1, "56", "49")
addBinaryButtonsContainer(row1, "48", "41")
addBinaryButtonsContainer(row1, "40", "33")

local row2 = addBinaryButtonsRowLayout()
addBinaryButtonsContainer(row2, "32", "25")
addBinaryButtonsContainer(row2, "24", "17")
addBinaryButtonsContainer(row2, "16", "9")
addBinaryButtonsContainer(row2, "8", "1")

local buttonsContainer = window:addChild(GUI.container(1, displayContainer.height + 1, window.width, window.height - displayContainer.height))

local function addButton(x, y, c1, c2, c3, c4, text, onTouch, width, height)
	local button = buttonsContainer:addChild(GUI.button(
		(x - 1) * buttonWidth + 1,
		(y - 1) * buttonHeight + 1,
		(width or 1) * buttonWidth,
		(height or 1) * buttonHeight,
		c1, c2, c3, c4, text
	))

	button.onTouch = onTouch
	button.colors.disabled.background = c1
	button.colors.disabled.text = 0xB4B4B4

	return button
end

local function addEngineerButton(x, y, ...)
	return addButton(x, y, 0xD2D2D2, 0x2D2D2D, 0xB4B4B4, 0x2D2D2D, ...)
end

local function addBinaryButton(x, y, ...)
	return addButton(x, y, 0xE1E1E1, 0x2D2D2D, 0xB4B4B4, 0x2D2D2D, ...)
end

local function addRegularButton(x, y, ...)
	return addButton(x, y, 0xF0F0F0, 0x2D2D2D, 0xB4B4B4, 0x2D2D2D, ...)
end

local function addMathButton(x, y, ...)
	return addButton(x, y, 0xFF9240, 0xFFFFFF, 0xCC6D00, 0xFFFFFF, ...)
end

local function onDigitPressed(digit)
	if modeList.selectedItem == 1 then
		setValue(tonumber(string.format("%o", value) .. digit, 8))
	elseif modeList.selectedItem == 2 then
		local integer, fractional = parseFloat(value)

		if isNumber(integer) then
			if floatingList.selectedItem == 1 then
				setValue(tonumber(integer .. digit .. "." .. fractional))
			else
				setValue(tonumber(integer .. "." .. (fractional == "0" and "" or fractional) .. digit))
			end
		end
	else
		setValue(math.floor(value) * 16 + digit)
	end
end

local function reset()
	oldValue = nil
	floatingList.selectedItem = 1
	if action then
		action.button.pressed = false
		action = nil
	end
end

local function getActionResult()
	setValue(action.getResult())
end

local function addAction(button, getResult)
	button.switchMode = true
	button.onTouch = function()
		-- Если уже имеется какое-то действие
		if action then
			-- Если мы повторно жмякаем на ранее нажатую кнопку
			if action.button == button then
				button.pressed = true
			-- Отжимаем кнопку другого действия, если она была ранее нажата
			else
				action.button.pressed = false
			end

			getActionResult()
		end

		oldValue = value
		setValue(0)
		floatingList.selectedItem = 1

		action = {
			button = button,
			getResult = getResult
		}
	end
end

local function getDegree(v)
	return degereeList.selectedItem == 1 and v or math.rad(v)
end

--------------------------------------------------------------------

-- Digits row 1
addRegularButton(9, 1, "C", function()
	setValue(0)
	reset()
end)

addRegularButton(10, 1, "CE", function()
	setValue(0)
end)

addRegularButton(11, 1, "%", function()
	value = value / 100
	
	if action then
		getActionResult()
		reset()
	else
		setValue(value)
	end
end)

addAction(addMathButton(12, 1, "/"), function()
	return oldValue / value
end)

-- Digits row 2
addRegularButton(9, 2, "7", function()
	onDigitPressed(7)
end)

table.insert(digitOctalButtons, addRegularButton(10, 2, "8", function()
	onDigitPressed(8)
end))

table.insert(digitOctalButtons, addRegularButton(11, 2, "9", function()
	onDigitPressed(9)
end))

addAction(addMathButton(12, 2, "*"), function()
	return oldValue * value
end)

-- Digits row 3
for i = 4, 6 do
	addRegularButton(i + 5, 3, tostring(i), function()
		onDigitPressed(i)
	end)
end

addAction(addMathButton(12, 3, "-"), function()
	return oldValue - value
end)

-- Digits row 4
for i = 1, 3 do
	addRegularButton(i + 8, 4, tostring(i), function()
		onDigitPressed(i)
	end)
end

addAction(addMathButton(12, 4, "+"), function()
	return oldValue + value
end)

-- Digits row 5
addRegularButton(9, 5, "0", function()
	onDigitPressed(0)
end)

table.insert(digitDecimalButtons, addRegularButton(10, 5, ".", function()
	floatingList.selectedItem = floatingList.selectedItem == 1 and 2 or 1
end))

addRegularButton(11, 5, "+-", function()
	setValue(-value)
end)

addMathButton(12, 5, "=", function()
	if action then
		getActionResult()
		reset()
	end
end)

--------------------------------------------------------------------

-- Engineer row 1
addEngineerButton(1, 1, "mc", function()
	memory = 0
end)

addEngineerButton(2, 1, "m+", function()
	memory = memory + value
end)

addEngineerButton(3, 1, "m-", function()
	memory = memory - value
end)

addEngineerButton(4, 1, "mr", function()
	setValue(memory)
	reset()
end)

addEngineerButton(5, 1, "rnd", function()
	setValue(math.random())
	reset()
end)

-- Engineer row 2
addEngineerButton(1, 2, "x^2", function()
	setValue(value ^ 2)
	reset()
end)

addAction(addEngineerButton(2, 2, "x^y"), function()
	return oldValue ^ value
end)

addEngineerButton(3, 2, "√x", function()
	setValue(math.sqrt(value))
	reset()
end)

addAction(addEngineerButton(4, 2, "y√x"), function()
	return oldValue ^ (1 / value)
end)

addEngineerButton(5, 2, "x!", function()
	local fact = 1
	for i = 2, absValue do
		fact = fact * i
	end

	setValue(fact)
	reset()
end)

-- Engineer row 3
addEngineerButton(1, 3, "sin", function()
	setValue(math.sin(getDegree(value)))
	reset()
end)

addEngineerButton(2, 3, "cos", function()
	setValue(math.cos(getDegree(value)))
	reset()
end)

addEngineerButton(3, 3, "tan", function()
	setValue(math.tan(getDegree(value)))
	reset()
end)

addEngineerButton(4, 3, "1/x", function()
	setValue(1 / value)
	reset()
end)

addAction(addEngineerButton(5, 3, "e^x"), function()
	return math.exp(value)
end)

-- Engineer row 4
addEngineerButton(1, 4, "asin", function()
	setValue(math.asin(getDegree(value)))
	reset()
end)

addEngineerButton(2, 4, "acos", function()
	setValue(math.acos(getDegree(value)))
	reset()
end)

addEngineerButton(3, 4, "atan", function()
	setValue(math.atan(getDegree(value)))
	reset()
end)

addAction(addEngineerButton(4, 4, "log"), function()
	return math.log(oldValue, value)
end)

addAction(addEngineerButton(5, 4, "mod"), function()
	return oldValue % value
end)

-- Engineer row 5
addEngineerButton(1, 5, "sinh", function()
	setValue(math.sinh(getDegree(value)))
	reset()
end)

addEngineerButton(2, 5, "cosh", function()
	setValue(math.cosh(getDegree(value)))
	reset()
end)

addEngineerButton(3, 5, "tanh", function()
	setValue(math.tanh(getDegree(value)))
	reset()
end)

addEngineerButton(4, 5, "e", function()
	setValue(2.718281828459045)
	reset()
end)

addEngineerButton(5, 5, "π", function()
	setValue(math.pi)
	reset()
end)

--------------------------------------------------------------------

-- Binary row 1
table.insert(digitHexadecimalButtons, addBinaryButton(6, 1, "D", function()
	onDigitPressed(0xD)
end))

table.insert(digitHexadecimalButtons, addBinaryButton(7, 1, "E", function()
	onDigitPressed(0xE)
end))

table.insert(digitHexadecimalButtons, addBinaryButton(8, 1, "F", function()
	onDigitPressed(0xF)
end))

-- Binary row 2
table.insert(digitHexadecimalButtons, addBinaryButton(6, 2, "A", function()
	onDigitPressed(0xA)
end))

table.insert(digitHexadecimalButtons, addBinaryButton(7, 2, "B", function()
	onDigitPressed(0xB)
end))

table.insert(digitHexadecimalButtons, addBinaryButton(8, 2, "C", function()
	onDigitPressed(0xC)
end))

-- Binary row 3
addAction(addBinaryButton(6, 3, "AND"), function()
	return bit32.band(math.floor(oldValue), math.floor(value))
end)

addAction(addBinaryButton(7, 3, "OR"), function()
	return bit32.bor(math.floor(oldValue), math.floor(value))
end)

addAction(addBinaryButton(8, 3, "XOR"), function()
	return bit32.bxor(math.floor(oldValue), math.floor(value))
end)


-- Binary row 4
addAction(addBinaryButton(6, 4, "NOR"), function()
	return bit32.bnot(bit32.bor(math.floor(oldValue), math.floor(value)))
end)

addAction(addBinaryButton(7, 4, "ROL"), function()
	return bit32.lrotate(math.floor(oldValue), math.floor(value))
end)

addAction(addBinaryButton(8, 4, "ROR"), function()
	return bit32.rrotate(math.floor(oldValue), math.floor(value))
end)

-- Binary row 5
addAction(addBinaryButton(6, 5, "x<<y"), function()
	return bit32.lshift(math.floor(oldValue), math.floor(value))
end)

addAction(addBinaryButton(7, 5, "NOT"), function()
	return bit32.bnot(math.floor(oldValue), math.floor(value))
end)

addAction(addBinaryButton(8, 5, "x>>y"), function()
	return bit32.rshift(math.floor(oldValue), math.floor(value))
end)

--------------------------------------------------------------------

setValue(0)
modeList:getItem(2).onTouch()
