
_G.buffer = require("doubleBuffering")
buffer.start()
_G.unicode = require("unicode")
_G.event = require("event")
_G.ecs = require("ECSAPI")

local xGraph, yGraph = math.floor(buffer.width / 2), buffer.height
local yDependencyString = "math.sin(x) * 5"
local graphScale = 4
local graphResizeSpeed = 0.4
local renderRange = 40
local renderAccuracy = 0.4
local axisColor = 0x333333
local graphColor = 0x88FF88
local selectionPointLineColor = 0x555555
local selectionPointColor = 0x5555FF
local selectionTooltipTextColor = 0xFFFFFF
local buttonColor = 0xEEEEEE
local backgroundColor = 0x1b1b1b
local buttonTextColor = 0x1b1b1b
local buttonWidth = 20
local selectedPoints = {{x = -5, y = 2}}
local showCornerPoints = false

------------------------------------------------------------------------------------------------------------------------------------------
local buttons = {}

local function assertString(x, yDependencyString)
	local stro4ka = "local x = " .. x .. "; local y = " .. yDependencyString .. "; return y"
	return pcall(load(stro4ka))
end

local function drawButtons()
	buttons = {}
	local buttonNames = {"Функция", "Масштаб", "Очистить точки", "Выход"}
	local x, y = math.floor(buffer.width / 2 - (#buttonNames * (buttonWidth + 2) - 2) / 2), buffer.height - 4

	for i = 1, #buttonNames do
		buttons[buttonNames[i]] = { buffer.button(x, y, buttonWidth, 3, buttonColor, buttonTextColor, buttonNames[i]) }
		x = x + buttonWidth + 2
	end
end

local function drawHorizontalLine(x, y, x2, color)
	for i = x, x2 do buffer.semiPixelSet(i, y, color) end
end

local function drawVerticalLine(x, y, y2, color)
	for i = y, y2 do buffer.semiPixelSet(x, i, color) end
end

local function drawAxis()
	drawHorizontalLine(1, yGraph, buffer.width, axisColor)
	drawVerticalLine(xGraph, 1, buffer.height * 2, axisColor)
end

local function limit(n)
	if n > -500 and n < 500 then return true end
end

local keyPoints = {}
local function calculateKeyPoints()
	keyPoints = {}
	local xOld, yOld, xNew, yNew = math.huge, math.huge
	for x = -renderRange, renderRange, renderAccuracy do
		local success, result = assertString(x, yDependencyString)
		if success then
			if not (result ~= result) then
				xNew, yNew = math.floor(x * graphScale), math.floor(result * graphScale)
				
				if limit(xOld) and limit(yOld) and limit(xNew) and limit(yNew) then
					table.insert(keyPoints, {x = xOld, y = yOld, x2 = xNew, y2 = yNew})
					-- buffer.semiPixelLine(xOld, yOld, xNew, yNew, graphColor)
				end
				
				xOld, yOld = xNew, yNew
			end
		-- else
		-- 	error(result)
		end
	end
end

local function drawGraph()
	for i = 1, #keyPoints do
		buffer.semiPixelLine(xGraph + keyPoints[i].x, yGraph - keyPoints[i].y, xGraph + keyPoints[i].x2, yGraph - keyPoints[i].y2, graphColor)
		if showCornerPoints then buffer.semiPixelSet(xGraph + keyPoints[i].x, yGraph - keyPoints[i].y, 0x00A8FF) end
	end
end

local function tooltip(x, y, tooltipColor, textColor, ...)
	local stringArray = {...}
	local maxTextLength = 0; for i = 1, #stringArray do maxTextLength = math.max(maxTextLength, unicode.len(stringArray[i])) end

	buffer.square(x, y, maxTextLength + 2, #stringArray, tooltipColor, textColor, " ")
	x = x + 1
	for i = 1, #stringArray do
		buffer.text(x, y, textColor, stringArray[i])
		y = y + 1
	end
end

local function drawSelectedPoint(x, y, pointNumber)
	local xOnScreen, yOnScreen = math.floor(xGraph + x * graphScale), math.floor(yGraph - y * graphScale)

	if xOnScreen <= xGraph then drawHorizontalLine(xOnScreen, yOnScreen, xGraph - 1, selectionPointLineColor) else drawHorizontalLine(xGraph + 1, yOnScreen, xOnScreen, selectionPointLineColor) end
	if yOnScreen <= yGraph then drawVerticalLine(xOnScreen, yOnScreen, yGraph - 1, selectionPointLineColor) else drawVerticalLine(xOnScreen, yGraph + 1, yOnScreen, selectionPointLineColor) end

	buffer.semiPixelSet(xOnScreen, yOnScreen, selectionPointColor)

	yOnScreen = math.ceil(yOnScreen / 2)

	tooltip(xOnScreen + 3, yOnScreen + 2, selectionPointLineColor, selectionTooltipTextColor, "Точка #" .. pointNumber, "x: " .. x, "y: " .. y)
end

local function drawSelectedPoints()
	if selectedPoints then
		for i = 1, #selectedPoints do
			drawSelectedPoint(selectedPoints[i].x, selectedPoints[i].y, i)
		end
	end
end

local function drawAll()
	buffer.clear(backgroundColor)
	drawAxis()
	drawGraph()
	drawSelectedPoints()
	drawButtons()
	buffer.draw()
end

local function clicked(x, y, object)
	if x >= object[1] and y >= object[2] and x <= object[3] and y <= object[4] then return true end
end

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

calculateKeyPoints()
drawAll()

local xMove, yMove
while true do
	local e = {event.pull()}
	if e[1] == "touch" then
		if e[5] == 1 then
			selectedPoints = selectedPoints or {}
			table.insert(selectedPoints, { x = math.floor((e[3] - xGraph) / graphScale), y = math.floor((yGraph - e[4] * 2) / graphScale) })
			drawAll()	
		else
			xMove, yMove = e[3], e[4]

			for key in pairs(buttons) do
				if clicked(e[3], e[4], buttons[key]) then
					buffer.button(buttons[key][1], buttons[key][2], buttonWidth, 3, graphColor, backgroundColor, key)
					buffer.draw()
					os.sleep(0.2)
					drawAll()

					if key == "Функция" then
						local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true,
							{"EmptyLine"},
							{"CenterText", ecs.colors.orange, "Функция"},
							{"EmptyLine"},
							{"Input", 0xFFFFFF, ecs.colors.orange, yDependencyString},
							{"EmptyLine"},
							{"CenterText", ecs.colors.orange, "Параметры рендера"},
							{"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, renderRange, "Диапазон: ", ""},
							{"Slider", 0xFFFFFF, ecs.colors.orange, 1, 100, 101 - renderAccuracy * 100, "Точность: ", ""},
							{"EmptyLine"},
							{"Switch", ecs.colors.orange, 0xffffff, 0xFFFFFF, "Показывать квант-точки", showCornerPoints},
							{"EmptyLine"},
							{"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}}
						)
						if data[5] == "OK" then
							yDependencyString = data[1]
							renderRange = tonumber(data[2])
							renderAccuracy = (101 - tonumber(data[3])) / 100
							showCornerPoints = data[4]
							calculateKeyPoints()
							drawAll()
						end
					elseif key == "Масштаб" then
						local data = ecs.universalWindow("auto", "auto", 36, 0x262626, true,
							{"EmptyLine"},
							{"CenterText", ecs.colors.orange, "Масштаб"},
							{"EmptyLine"},
							{"Slider", 0xFFFFFF, ecs.colors.orange, 1, 3000, math.floor(graphScale * 100), "", "%"},
							{"EmptyLine"},
							{"Button", {ecs.colors.orange, 0xffffff, "OK"}, {0x999999, 0xffffff, "Отмена"}}
						)

						if data[2] == "OK" then
							graphScale = data[1] / 100
							calculateKeyPoints()
							drawAll()
						end
					elseif key == "Очистить точки" then
						selectedPoints = nil
						drawAll()
					elseif key == "Выход" then
						buffer.clear(0x262626)
						buffer.draw()
						return
					end

					break
				end
			end
		end
	elseif e[1] == "drag" then
		if e[5] ~= 1 then
			local xDifference, yDifference = e[3] - xMove, e[4] - yMove
			xGraph, yGraph = xGraph + xDifference, yGraph + yDifference * 2
			xMove, yMove = e[3], e[4]
			drawAll()
		end
	elseif e[1] == "scroll" then
		if e[5] == 1 then
			graphScale = graphScale + graphResizeSpeed
			calculateKeyPoints()
		else
			graphScale = graphScale - graphResizeSpeed
			if graphScale < graphResizeSpeed then graphScale = graphResizeSpeed end
			calculateKeyPoints()
		end
		drawAll()
	elseif e[1] == "key_down" then
		if e[4] == 28 then
			selectedPoints = nil
			drawAll()
		end
	end
end









