require("advancedLua")
local unicode = require("unicode")
local buffer = require("doubleBuffering")
local event = require("event")
local fs = require("filesystem")
local context = require("context")
local ecs = require("ECSAPI")

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

local xSize, ySize = buffer.width, buffer.height
local file = {}

local config = {
	x = 1,
	y = 1,
	width = 98,
	height = 25,
	heightOfTopBar = 3,
	fromString = 1,
	xCurrentByte = 1,
	yCurrentByte = 1,
	pathToFile = "bin/resolution.lua",
	sizeOfFile = 1,
	transparency = 30,
	colors = {
		background = 0xdddddd,
		topBar = 0xdddddd,
		topBarText = 0x262626,
		topBarButton = 0x444444,
		hexText = 0x262626,
		hexSelection = 0x880000,
		hexSelectionText = 0xffffff,
		numberBar = 0x262626,
		numberBarText = 0xcccccc,
		infoPanel = 0x880000,
		infoPanelText = 0xffffff,
	}
}

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

local obj = {}
local function newObj(class, name, ...)
	obj[class] = obj[class] or {}
	obj[class][name] = {...}
end

local function convertIndexToCoords(index)
	local ostatok = index % 16
	local x = (ostatok == 0) and 16 or ostatok
	local y = math.ceil(index / 16)
	return x, y
end

local function convertCoordsToIndex(xByte, yByte)
	return (yByte - 1) * 16 + xByte
end

local function readFile()
	config.fromString = 1
	config.CurrentByte = 1
	config.CurrentByte = 1

	file = {}
	local fileStream = io.open(config.pathToFile, "rb")

	while true do
		local readedByte = fileStream:read(1)
		if not readedByte then break end
		table.insert(file, string.format("%02X", string.byte(readedByte)))
	end

	fileStream:close()
	config.sizeOfFile = math.ceil(fs.size(config.pathToFile) / 1024)
end

local function drawInfoPanel()
	local x = config.x
	local y = config.y

	local width = 30
	local xPos = x + math.floor(config.width / 2 - width / 2) - 1
	buffer.square(xPos, y, width, config.heightOfTopBar, config.colors.infoPanel, 0x000000, " ")
	
	local text = fs.name(config.pathToFile)
	xPos = x + math.floor(config.width / 2 - unicode.len(text) / 2) - 1
	buffer.text(xPos, y, config.colors.infoPanelText, unicode.sub(text, 1, width - 2))

	text = "Размер файла: " .. config.sizeOfFile .. " КБ"
	xPos = x + math.floor(config.width / 2 - unicode.len(text) / 2) - 1
	buffer.text(xPos, y + 1, 0xffaaaa, unicode.sub(text, 1, width - 2))

	text = "Текущий байт: " .. convertCoordsToIndex(config.xCurrentByte, config.yCurrentByte)
	xPos = x + math.floor(config.width / 2 - unicode.len(text) / 2) - 1
	buffer.text(xPos, y + 2, 0xffaaaa, unicode.sub(text, 1, width - 2))
end

local function drawTopBar()
	local x = config.x
	local y = config.y

	buffer.square(x, y, config.width, 3, 0xffffff, 0xffffff, " ", config.transparency)
	newObj("Buttons", 1, buffer.button(x, y, 10, 3, 0xdddddd, 0x000000, "Файл"))

	drawInfoPanel()
end

local function printDebug(line, text)
	if debug then
		ecs.square(1, line, buffer.width, 1, 0x262626)
		ecs.colorText(2, line, 0xFFFFFF, text)
	end
end

local function drawHexAndText()
	local x, y = config.x, config.y + 3
	local textOffset = 67
	local hexOffset = 12
	local xHex, yHex = x + hexOffset, y + 2
	local xText, yText = xHex + textOffset, y + 2

	obj["hex"] = {}
	obj["text"] = {}

	--Главный белый
	buffer.square(config.x, config.y + 3, config.width, config.height - 3, config.colors.background, 0x000000, " ")
	--Левый серый
	buffer.square(x, y, 10, config.height - 3, config.colors.numberBar, 0xffffff, " ")
	--Верхний серый
	buffer.square(x, y, config.width, 1, config.colors.numberBar, 0xffffff, " ")
	--Вертикальная полоска
	buffer.square(xText - 3, y + 1, 1, config.height - 4, config.colors.background, 0xaaaaaa, "│")
	--Скроллбар
	buffer.scrollBar(x + config.width - 1, y + 1, 1, config.height - 4, math.ceil(#file / 16), config.fromString, 0x262626, ecs.colors.lightBlue)

	--Рисуем верхние номерки
	local xCyka = xHex
	for i = 1, 16 do
		if i == config.xCurrentByte then
			buffer.square(xCyka - 1, y, 4, 1, config.colors.hexSelection, 0xffffff, " ")
			buffer.text(xCyka, y, config.colors.hexSelectionText, string.format("%02X", i - 1))
		else
			buffer.text(xCyka, y, config.colors.numberBarText, string.format("%02X", i - 1))
		end
		
		xCyka = xCyka + 4
	end

	--Рисуем хекс и текст
	local xByte, yByte, text
	local byteCounter = 1
	local fromByte = config.fromString * 16 - 15
	for byte = fromByte, fromByte + 10 * 16 - 1 do

		if not file[byte] then break end
		
		xByte, yByte = convertIndexToCoords(byte)

		text = unicode.char(tonumber("0x" .. file[byte]))
		if unicode.isWide(text) then text = "." end

		if config.xCurrentByte == xByte and config.yCurrentByte == yByte then
			buffer.square(xHex - 1, yHex, 4, 1, config.colors.hexSelection, 0xffffff, " ")
			buffer.set(xText, yText, config.colors.hexSelection, 0xffffff, " ")

			buffer.text(xHex, yHex, config.colors.hexSelectionText, file[byte])
			buffer.text(xText, yText, config.colors.hexSelectionText, text)
		else
			buffer.text(xHex, yHex, config.colors.hexText, file[byte])
			buffer.text(xText, yText, config.colors.hexText, text)
		end

		--Рисуем левые номерки
		if yByte == config.yCurrentByte then
			buffer.square(x, yHex, 10, 1, config.colors.hexSelection, 0xffffff, " ")
			buffer.text(x + 1, yHex, config.colors.hexSelectionText, string.format("%07X", yByte - 1) .. "0")
		else
			buffer.text(x + 1, yHex, config.colors.numberBarText, string.format("%07X", yByte - 1) .. "0")
		end

		--Обжектыыы!! Ы!
		newObj("hex", byteCounter, xHex, yHex, xByte, yByte)
		newObj("text", byteCounter, xText, yText, xByte, yByte)
		byteCounter = byteCounter + 1

		--Коорды!
		if xByte == 16 then
			xHex = x + hexOffset
			xText = xHex + textOffset
			yHex = yHex + 2
			yText = yText + 2
		else
			xHex = xHex + 4
			xText = xText + 1
		end
	end
end


local function drawAll(force)
	drawTopBar()
	drawHexAndText()
	--Тень
	buffer.square(config.x + config.width, config.y + 1, 2, config.height, 0x000000, 0xffffff, " ", 50)
	buffer.square(config.x + 2, config.y + config.height, config.width - 2, 1, 0x000000, 0xffffff, " ", 50)
	buffer.draw(force)
end

local function getCenterOfScreen()
	config.x = math.floor(xSize / 2 - config.width / 2)
	config.y = math.floor(ySize / 2 - config.height / 2)
end

local function checkInput(text, pattern)
	if string.find(text, pattern) then
		return true
	else
		ecs.error("Что за говно ты сюда ввел? Переделывай на хуй!")
	end
end

local function editByte(xByte, yByte)
	local index = convertCoordsToIndex(xByte, yByte)
	local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", 0xffffff, "Редактировать байт"}, {"EmptyLine"}, {"Input", 0xffffff, 0xff5555, "Введите значение HEX"}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "Принять"}, {0xaaaaaa, 0xffffff, "Отмена"}})
	if data[2] == "Принять" and checkInput(data[1], "^[1234567890abcdefABCDEF][1234567890abcdefABCDEF]$") then
		file[index] = data[1]
	end
end

local function editText(xByte, yByte)
	local index = convertCoordsToIndex(xByte, yByte)
	local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", 0xffffff, "Редактировать байт"}, {"EmptyLine"}, {"Input", 0xffffff, 0xff5555, "Введите значение CHAR"}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "Принять"}, {0xaaaaaa, 0xffffff, "Отмена"}})
	if data[2] == "Принять" and checkInput(data[1], "^.$") then
		file[index] = string.format("%02X", string.byte(byteValue))
	end
end

local function insertByte(xByte, yByte)
	local index = convertCoordsToIndex(xByte, yByte)
	local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", 0xffffff, "Вставить байт"}, {"EmptyLine"}, {"Input", 0xffffff, 0xff5555, "Введите значение HEX"}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "Вставить"}, {0xaaaaaa, 0xffffff, "Отмена"}})
	if data[2] == "Вставить" and checkInput(data[1], "^[1234567890abcdefABCDEF][1234567890abcdefABCDEF]$") then
		table.insert(file, index, data[1])
	end
end

local function invertByte(xByte, yByte)
	local index = convertCoordsToIndex(xByte, yByte)
	file[index] = bit32.band( bit32.bnot(tonumber("0x" .. file[index])), 0xff )
	file[index] = string.format("%02X", string.byte(file[index]))
end

local function askForWhatToDoWithByte(x, y, xByte, yByte, asByte)
	local action = context.menu(x, y, {"Редактировать байт"}, {"Инвертировать байт"}, {"Вставить байт"}, "-", {"Удалить байт"})

	if action == "Редактировать байт" then
		if asByte then
			editByte(xByte, yByte)
		else
			editText(xByte, yByte)
		end
	elseif action == "Инвертировать байт" then
		local index = convertCoordsToIndex(xByte, yByte)
		invertByte(xByte, yByte)
	elseif action == "Вставить байт" then
		insertByte(xByte, yByte)
	elseif action == "Удалить байт" then
		local index = convertCoordsToIndex(xByte, yByte)
		table.remove(file, index)
	end
end

local function save(path)
	fs.makeDirectory(fs.path(path) or "")
	local fileStream = io.open(path, "w")
	for i = 1, #file do
		fileStream:write(unicode.char(tonumber(table.concat({"0x", file[i]}))))
	end
	fileStream:close()
end

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

readFile()
--buffer.square(1, 1, xSize, ySize, ecs.colors.red, 0x000000, " ")
getCenterOfScreen()

local oldPixels = buffer.copy(config.x, config.y, config.width + 2, config.height + 1)
drawAll(true)

while true do
	local e = {event.pull()}
	if e[1] == "touch" then
		for key in pairs(obj["hex"]) do
			if e[3] >= obj["hex"][key][1] - 1 and e[3] <= obj["hex"][key][1] + 2 and e[4] == obj["hex"][key][2] then
				if config.xCurrentByte == obj["hex"][key][3] and config.yCurrentByte == obj["hex"][key][4] then
					if e[5] == 0 then
						editByte(obj["hex"][key][3], obj["hex"][key][4])
					else
						askForWhatToDoWithByte(obj["hex"][key][1] - 1, obj["hex"][key][2] + 1, obj["hex"][key][3], obj["hex"][key][4], true)
					end
				else
					config.xCurrentByte = obj["hex"][key][3]
					config.yCurrentByte = obj["hex"][key][4]
				end
				
				drawHexAndText()
				drawInfoPanel()
				buffer.draw()

				break
			end
		end

		for key in pairs(obj["text"]) do
			if e[3] == obj["text"][key][1] and e[4] == obj["text"][key][2] then
				if config.xCurrentByte == obj["text"][key][3] and config.yCurrentByte == obj["text"][key][4] then
					if e[5] == 0 then
						editText(obj["text"][key][3], obj["text"][key][4])
					else
						askForWhatToDoWithByte(obj["text"][key][1], obj["text"][key][2] + 1, obj["text"][key][3], obj["text"][key][4], true)
					end
				else
					config.xCurrentByte = obj["text"][key][3]
					config.yCurrentByte = obj["text"][key][4]
				end
				
				drawHexAndText()
				drawInfoPanel()
				buffer.draw()

				break
			end
		end

		if ecs.clickedAtArea(e[3], e[4], obj["Buttons"][1][1], obj["Buttons"][1][2], obj["Buttons"][1][3], obj["Buttons"][1][4]) then
			buffer.button(obj["Buttons"][1][1], obj["Buttons"][1][2], 10, 3, 0x333333, 0xdddddd, "Файл")
			buffer.draw()

			local action = context.menu(obj["Buttons"][1][1], obj["Buttons"][1][2] + 3, {"Открыть"}, {"Сохранить"}, {"Сохранить как"}, "-", {"Выход"})

			buffer.button(obj["Buttons"][1][1], obj["Buttons"][1][2], 10, 3, 0xdddddd, 0x000000, "Файл")
			buffer.draw()

			if action == "Открыть" then
				local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", 0xffffff, "Открыть файл"}, {"EmptyLine"}, {"Input", 0xffffff, 0x880000, "Путь к файлу"}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "Открыть"}, {0xaaaaaa, 0xffffff, "Отмена"}})
				if data[2] == "Открыть" then
					if fs.exists(data[1]) then
						config.pathToFile = data[1]
						readFile()
						drawHexAndText()
						drawInfoPanel()
						buffer.draw()
					else
						ecs.error("Файл \"" .. data[1] .. "\" не существует!")
					end
				end
			elseif action == "Сохранить" then
				save(config.pathToFile)
			elseif action == "Сохранить как" then
				local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true, {"EmptyLine"}, {"CenterText", 0xffffff, "Сохранить как"}, {"EmptyLine"}, {"Input", 0xffffff, 0x880000, "Путь к файлу"}, {"EmptyLine"}, {"Button", {0x880000, 0xffffff, "Сохранить"}, {0xaaaaaa, 0xffffff, "Отмена"}})
				if data[2] == "Сохранить" then
					save(data[1])
				end
			elseif action == "Выход" then
				buffer.paste(config.x, config.y, oldPixels)
				buffer.draw()
				return
			end			
		end
	elseif e[1] == "scroll" then
		if e[5] == 1 then
			if config.fromString > 1 then
				config.fromString = config.fromString - 1
				drawHexAndText()
				buffer.draw()
			end
		else
			if config.fromString <= math.ceil(#file / 16) - 1 then
				config.fromString = config.fromString + 1
				drawHexAndText()
				buffer.draw()
			end
		end
	end
end
















