-- Paint created by nitrogenfingers (edited by dan200)
-- http://www.youtube.com/user/NitrogenFingers
-- Modified into a level editor for Redirection by trollbreeder

------------
-- Fields --
------------

-- The width and height of the terminal
local w, h = term.getSize()

-- The selected colours on the left and right mouse button, and the colour of the canvas
local leftColour, rightColour
local canvasColour = colours.black

-- The values stored in the canvas
local canvas = {}

-- The menu options
local mChoices = { "Save", "Exit" }

-- The message displayed in the footer bar
local fMessage = "Press Ctrl or click here to access menu"

-------------------------
-- Initialisation --
-------------------------

-- Determine if we can even run this
if not term.isColour() then
	print("Requires an Advanced Computer")
	return
end

-- Determines if the file exists, and can be edited on this computer
local tArgs = { ... }
if #tArgs == 0 then
	local programName = arg[0] or fs.getName(shell.getRunningProgram())
	print("Usage: " .. programName .. " <path>")
	return
end
local sPath = shell.resolve(tArgs[1])
local bReadOnly = fs.isReadOnly(sPath)
if fs.exists(sPath) and fs.isDir(sPath) then
	print("Cannot edit a directory.")
	return
end

-- Dat files
if not fs.exists(sPath) and not string.find(sPath, "%.") then sPath = sPath .. ".dat" end


---------------
-- Functions --
---------------

local function getCanvasPixel(x, y)
	if canvas[y] then
		return canvas[y][x]
	end
	return nil
end

--[[
	Converts a colour value to a text character
	params: colour = the number to convert to a hex value
	returns: a string representing the chosen colour
]]
local function getCharOf(colour)
	-- Incorrect values always convert to nil
	if type(colour) == "number" then
		local value = math.floor(math.log(colour) / math.log(2)) + 1
		if value >= 1 and value <= 16 then
			return string.sub("0123456789abcdef", value, value)
		end
	end
	return " "
end

--[[
	Converts a text character to colour value
	params: char = the char (from string.byte) to convert to number
	returns: the colour number of the hex value
]]
local tColourLookup = {}
for n = 1, 16 do
	tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
end
local function getColourOf(char)
	-- Values not in the hex table are transparent (canvas coloured)
	return tColourLookup[char]
end

--[[
	Loads the file into the canvas
	params: path = the path of the file to open
	returns: nil
]]
local function load(path)
	-- Load the file
	if fs.exists(path) then
		local file = fs.open(sPath, "r")
		local sLine = file.readLine()
		while sLine do
			local line = {}
			for x = 1, w - 2 do
				line[x] = getColourOf(string.byte(sLine, x, x))
			end
			table.insert(canvas, line)
			sLine = file.readLine()
		end
		file.close()
	end
end

--[[
	Saves the current canvas to file
	params: path = the path of the file to save
	returns: true if save was successful, false otherwise
]]
local function save(path)
	-- Open file
	local sDir = string.sub(sPath, 1, #sPath - #fs.getName(sPath))
	if not fs.exists(sDir) then
		fs.makeDir(sDir)
	end

	local file, err = fs.open(path, "w")
	if not file then
		return false, err
	end

	-- Encode (and trim)
	local tLines = {}
	local nLastLine = 0
	for y = 1, h - 1 do
		local sLine = ""
		local nLastChar = 0
		for x = 1, w - 2 do
			local c = getCharOf(getCanvasPixel(x, y))
			if ((x == 1) and y == 1) and getCanvasPixel(x, y) == nil then c = "0" end
			sLine = sLine .. c
			if c ~= " " then
				nLastChar = x
			end
		end
		sLine = string.sub(sLine, 1, nLastChar)
		tLines[y] = sLine
		if #sLine > 0 then
			nLastLine = y
		end
	end

	-- Save out
	for n = 1, nLastLine do
		   file.writeLine(tLines[n])
	end
	file.close()
	return true
end

local color_hex_lookup = {}
for i = 0, 15 do
	color_hex_lookup[2 ^ i] = string.format("%x", i)
end

--[[
	Converts a single pixel of a single line of the canvas and draws it
	returns: nil
]]

local function drawCanvasPixel(x, y, col)
	local pixel = getCanvasPixel(x, y)
	if (x == 1) and y == 1 then
		if pixel == nil then pixel = 1 end
		term.setTextColour(colours.white)
		term.setBackgroundColour(colours.black)
		term.setCursorPos(1, 1)
		term.write(color_hex_lookup[pixel])
		return
	end
	if col then pixel = col end
	if pixel then
		-- Start up blue 0
		if pixel == 1 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.blue)
			term.setCursorPos(x, y)
			term.write("^")
		-- Start right blue 1
		elseif pixel == 2 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.blue)
			term.setCursorPos(x, y)
			term.write(">")
		-- Start down blue 2
		elseif pixel == 4 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.blue)
			term.setCursorPos(x, y)
			term.write("v")
		-- Start left blue 3
		elseif pixel == 8 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.blue)
			term.setCursorPos(x, y)
			term.write("<")
		-- Start up red 4
		elseif pixel == 16 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.red)
			term.setCursorPos(x, y)
			term.write("^")
		-- Start right red 5
		elseif pixel == 32 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.red)
			term.setCursorPos(x, y)
			term.write(">")
		-- Start down red 6
		elseif pixel == 64 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.red)
			term.setCursorPos(x, y)
			term.write("v")
		-- Wall/block 7
		elseif pixel == 128 then
			term.setBackgroundColour(colours.grey)
			term.setCursorPos(x, y)
			term.write(" ")
		-- Road/path/floor 8
		elseif pixel == 256 then
			term.setBackgroundColour(colours.lightGrey)
			term.setCursorPos(x, y)
			term.write(" ")
		-- Start left red 9
		elseif pixel == 512 then
			term.setTextColour(colours.white)
			term.setBackgroundColour(colours.red)
			term.setCursorPos(x, y)
			term.write("<")
		-- Unused (hole) a
		--elseif pixel == 1024 then
		--	term.setBackgroundColour(pixel or canvasColour)
		--	term.setCursorPos(x, y)
		--	term.write(" ")
		-- End blue b
		elseif pixel == 2048 then
			term.setTextColour(colours.blue)
			term.setBackgroundColour(colours.lightGrey)
			term.setCursorPos(x, y)
			term.write("x")
		-- Unused (hole) c
		--elseif pixel == 4096 then
		--	term.setBackgroundColour(pixel or canvasColour)
		--	term.setCursorPos(x, y)
		--	term.write(" ")
		-- Unused (hole) d
		--elseif pixel == 8192 then
		--	term.setBackgroundColour(pixel or canvasColour)
		--	term.setCursorPos(x, y)
		--	term.write(" ")
		-- End red e
		elseif pixel == 16384 then
			term.setTextColour(colours.red)
			term.setBackgroundColour(colours.lightGrey)
			term.setCursorPos(x, y)
			term.write("x")
		-- Unused (hole) f
		--elseif pixel == 32768 then
		--	term.setBackgroundColour(pixel or canvasColour)
		--	term.setCursorPos(x, y)
		--	term.write(" ")
		-- Hole/empty/space
		else
			term.setBackgroundColour(colours.black)
			term.setCursorPos(x, y)
			term.write(" ")
		end
	else
		term.setBackgroundColour(colours.black)
		term.setTextColour(colours.black)
		term.setCursorPos(x, y)
		term.write(" ")
	end
end

--[[
	Draws colour picker sidebar, the pallette and the footer
	returns: nil
]]
local function drawInterface()
	-- Footer
	term.setCursorPos(1, h)
	term.setBackgroundColour(colours.black)
	term.setTextColour(colours.yellow)
	term.clearLine()
	term.write(fMessage)

	-- Colour Picker
	for i = 1, 16 do
		term.setTextColor(colours.white)
		term.setBackgroundColour(colours.black)
		term.setCursorPos(w - 1, i)
		term.write(color_hex_lookup[2 ^ (i - 1)])
		drawCanvasPixel(w, i, 2 ^ (i - 1))
	end

	term.setCursorPos(w - 1, 17)
	term.setBackgroundColour(colours.black)
	term.write("  ")

	-- Left and Right Selected Colours
	if leftColour == nil then
		leftColour = 256
	end
	drawCanvasPixel(w - 1, 18, leftColour)
	if rightColour == nil then
		rightColour = 128
	end
	drawCanvasPixel(w, 18, rightColour)
	
	-- Padding
	term.setBackgroundColour(canvasColour)
	for i = 20, h - 1 do
		term.setCursorPos(w - 1, i)
		term.write("  ")
	end
end

--[[
	Converts each colour in a single line of the canvas and draws it
	returns: nil
]]
local function drawCanvasLine(y)
	local text, fg, bg = "", "", ""
	for x = 1, w - 2 do
		drawCanvasPixel(x, y)
	end

	term.setCursorPos(1, y)
end

--[[
	Converts each colour in the canvas and draws it
	returns: nil
]]
local function drawCanvas()
	for y = 1, h - 1 do
		drawCanvasLine(y)
	end
end

local menu_choices = {
	Save = function()
		if bReadOnly then
			fMessage = "Access denied"
			return false
		end
		local success, err = save(sPath)
		if success then
			fMessage = "Saved to " .. sPath
		else
			if err then
				fMessage = "Error saving to " .. err
			else
				fMessage = "Error saving to " .. sPath
			end
		end
		return false
	end,
	Exit = function()
		return true
	end,
}

--[[
	Draws menu options and handles input from within the menu.
	returns: true if the program is to be exited; false otherwise
]]
local function accessMenu()
	-- Selected menu option
	local selection = 1

	term.setBackgroundColour(colours.black)

	while true do
		-- Draw the menu
		term.setCursorPos(1, h)
		term.clearLine()
		term.setTextColour(colours.white)
		for k, v in pairs(mChoices) do
			if selection == k then
				term.setTextColour(colours.yellow)
				term.write("[")
				term.setTextColour(colours.white)
				term.write(v)
				term.setTextColour(colours.yellow)
				term.write("]")
				term.setTextColour(colours.white)
			else
				term.write(" " .. v .. " ")
			end
		end

		-- Handle input in the menu
		local id, param1, param2, param3 = os.pullEvent()
		if id == "key" then
			local key = param1

			-- Handle menu shortcuts.
			for _, menu_item in ipairs(mChoices) do
				local k = keys[menu_item:sub(1, 1):lower()]
				if k and k == key then
					return menu_choices[menu_item]()
				end
			end

			if key == keys.right then
				-- Move right
				selection = selection + 1
				if selection > #mChoices then
					selection = 1
				end

			elseif key == keys.left and selection > 1 then
				-- Move left
				selection = selection - 1
				if selection < 1 then
					selection = #mChoices
				end

			elseif key == keys.enter then
				-- Select an option
				return menu_choices[mChoices[selection]]()
			elseif key == keys.leftCtrl or keys == keys.rightCtrl then
				-- Cancel the menu
				return false
			end
		elseif id == "mouse_click" then
			local cx, cy = param2, param3
			if cy ~= h then return false end -- Exit the menu

			local nMenuPosEnd = 1
			local nMenuPosStart = 1
			for _, sMenuItem in ipairs(mChoices) do
				nMenuPosEnd = nMenuPosEnd + #sMenuItem + 1
				if cx > nMenuPosStart and cx < nMenuPosEnd then
					return menu_choices[sMenuItem]()
				end
				nMenuPosEnd = nMenuPosEnd + 1
				nMenuPosStart = nMenuPosEnd
			end
		end
	end
end

--[[
	Runs the main thread of execution. Draws the canvas and interface, and handles
	mouse and key events.
	returns: nil
]]
local function handleEvents()
	local programActive = true
	while programActive do
		local id, p1, p2, p3 = os.pullEvent()
		if id == "mouse_click" or id == "mouse_drag" then
			if p2 >= w - 1 and p3 >= 1 and p3 <= 17 then
				if id ~= "mouse_drag" then
					-- Selecting an items in the colour picker
					if p3 <= 16 then
						if p1 == 1 then
							leftColour = 2 ^ (p3 - 1)
						else
							rightColour = 2 ^ (p3 - 1)
						end
					else
						if p1 == 1 then
							leftColour = nil
						else
							rightColour = nil
						end
					end
					--drawCanvas()
					drawInterface()
				end
			elseif p2 < w - 1 and p3 <= h - 1 then
				-- Clicking on the canvas
				local paintColour = nil
				if p1 == 1 then
					paintColour = leftColour
				elseif p1 == 2 then
					paintColour = rightColour
				end
				if not canvas[p3] then
					canvas[p3] = {}
				end
				canvas[p3][p2] = paintColour

				drawCanvasPixel(p2, p3)
			elseif p3 == h and id == "mouse_click" then
				-- Open menu
				programActive = not accessMenu()
				drawInterface()
			end
		elseif id == "key" then
			if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
				programActive = not accessMenu()
				drawInterface()
			end
		elseif id == "term_resize" then
			w, h = term.getSize()
			drawCanvas()
			drawInterface()
		end
	end
end

-- Init
load(sPath)
drawCanvas()
drawInterface()

-- Main loop
handleEvents()

-- Shutdown
term.setBackgroundColour(colours.black)
term.setTextColour(colours.white)
term.clear()
term.setCursorPos(1, 1)