-- Paint created by nitrogenfingers (edited by dan200)
-- http://www.youtube.com/user/NitrogenFingers

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

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

-- Cursor position
local curx, cury = 1, 1

term.setCursorBlink(true)

-- The selected color, to draw in a line or not, and the color of the canvas
local selectedcolor = colors.white
local canvascolor = colors.black
local keepDrawing = false

-- 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 to access menu"

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

-- Determines if the file exists, and can be edited on this computer
local tArgs = { ... }
if #tArgs == 0 then
	if arg then
		local programName = arg[0] or fs.getName(shell.getRunningProgram())
		print("Usage: " .. programName .. " <path>")
		return
	else
		local i = 0
		repeat
			tArgs[1] = fs.getDir(shell.getRunningProgram()).."image"..i..".nfp"
			i = i + 1
		until not fs.exists(tArgs[1])
	end
    
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

-- Create .nfp files by default
if not fs.exists(sPath) and not string.find(sPath, "%.") then
    local sExtension = settings.get("paint.default_extension")
    if sExtension ~= "" and type(sExtension) == "string" then
        sPath = sPath .. "." .. sExtension
    end
end


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

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

--[[
    Converts a color value to a text character
    params: color = the number to convert to a hex value
    returns: a string representing the chosen color
]]
local function getCharOf(color)
    -- Incorrect values always convert to nil
    if type(color) == "number" then
        local value = math.floor(math.log(color) / 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 color value
    params: char = the char (from string.byte) to convert to number
    returns: the color number of the hex value
]]
local tcolorLookup = {}
for n = 1, 16 do
    tcolorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
end
local function getcolorOf(char)
    -- Values not in the hex table are transparent (canvas colored)
    return tcolorLookup[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] = getcolorOf(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))
			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

--[[
    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 col then pixel = col end
	if pixel then
		term.setCursorPos(x, y)
		term.setBackgroundColour(pixel)
		term.write(" ")
		term.setCursorPos(x, y)
	else
		term.setBackgroundColour(colors.black)
		term.setTextColour(colors.gray)
		term.setCursorPos(x, y)
		term.write("\127")
	end
end

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

--[[
	Draws color picker sidebar, the pallette and the footer
	returns: nil
]]
local function drawInterface()
	-- Footer
	term.setCursorPos(1, h)
	term.setBackgroundColor(colors.black)
	term.setTextColor(colors.yellow)
	term.clearLine()
	term.write(fMessage)

	-- color Picker
	for i = 1, 16 do
		term.setTextColor(colors.white)
		term.setBackgroundColor(colors.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.blit("n\127", "07", "ff")
	term.setBackgroundColor(canvascolor)
	term.setCursorPos(w-1, 18)
	term.write("!")
	drawCanvasPixel(w, 18, selectedcolor)
	term.setTextColor(colors.white)
	term.setBackgroundColor(canvascolor)
	term.setCursorPos(w-1, 19)
	term.write("  \n  \n  \n  ")
	term.setCursorPos(w-1, 19)
	term.write(curx)
	term.setCursorPos(w-1, 20)
	term.write(cury)
	term.setCursorPos(w-1, 21)
	term.write("L")
	term.setBackgroundColor((keepDrawing and colors.green or colors.red))
	term.write((keepDrawing and "Y" or "N"))
	
	-- Padding
	term.setBackgroundColor(canvascolor)
	for i = 22, h - 1 do
		term.setCursorPos(w - 1, i)
		term.write("  ")
	end
	-- return cursor to previous position
	term.setCursorPos(curx, cury)
end

--[[
	Converts each color 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

end

--[[
	Converts each color 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.setBackgroundColor(colors.black)

    while true do
        -- Draw the menu
        term.setCursorPos(1, h)
        term.clearLine()
        term.setTextColor(colors.white)
        for k, v in pairs(mChoices) do
            if selection == k then
                term.setTextColor(colors.yellow)
                term.write("[")
                term.setTextColor(colors.white)
                term.write(v)
                term.setTextColor(colors.yellow)
                term.write("]")
                term.setTextColor(colors.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
        end
    end
end

--[[
    Runs the main thread of execution. Draws the canvas and interface, and handles key events.
    returns: nil
]]

local function handleEvents()
    local programActive = true
    while programActive do
		local id, p1, p2, p3 = os.pullEvent()
        if id == "key" then
            if p1 == keys.leftCtrl or p1 == keys.rightCtrl then
                programActive = not accessMenu()
                drawInterface()
            end
			if p1 == keys.space then
				drawCanvasPixel(curx, cury, selectedcolor)
				if not canvas[cury] then
					canvas[cury] = {}
				end
				canvas[cury][curx] = selectedcolor
			end
			if p1 == keys.capsLock then
				keepDrawing = not keepDrawing
			end
			if p1 == keys.left then
				curx = math.max(curx - 1, 1)
			elseif p1 == keys.right then
				curx = math.min(curx + 1, w)
			end
			if p1 == keys.up then
				cury = math.max(cury - 1, 1)
			elseif p1 == keys.down then
				cury = math.min(cury + 1, h)
			end
			-- COLORS
			if p1 == keys.a then
				selectedcolor = 2^10
			elseif p1 == keys.b then
				selectedcolor = 2^11
			elseif p1 == keys.c then
				selectedcolor = 2^12
			elseif p1 == keys.d then
				selectedcolor = 2^13
			elseif p1 == keys.e then
				selectedcolor = 2^14
			elseif p1 == keys.f then
				selectedcolor = 2^15
			elseif p1 == keys.n then
					selectedcolor = nil
			elseif p1 == keys.zero  or p1 == keys.numPad0 then
				selectedcolor = 2^0
			elseif p1 == keys.one   or p1 == keys.numPad1 then
				selectedcolor = 2^1
			elseif p1 == keys.two   or p1 == keys.numPad2 then
				selectedcolor = 2^2
			elseif p1 == keys.three or p1 == keys.numPad3 then
				selectedcolor = 2^3
			elseif p1 == keys.four  or p1 == keys.numPad4 then
				selectedcolor = 2^4
			elseif p1 == keys.five  or p1 == keys.numPad5 then
				selectedcolor = 2^5
			elseif p1 == keys.six   or p1 == keys.numPad6 then
				selectedcolor = 2^6
			elseif p1 == keys.seven or p1 == keys.numPad7 then
				selectedcolor = 2^7
			elseif p1 == keys.eight or p1 == keys.numPad8 then
				selectedcolor = 2^8
			elseif p1 == keys.nine  or p1 == keys.numPad9 then
				selectedcolor = 2^9
			end
			term.setCursorPos(curx, cury)
        elseif id == "term_resize" then
            w, h = term.getSize()
            drawCanvas()
            drawInterface()
        end
		if keepDrawing then drawCanvasPixel(curx, cury, selectedcolor); if not canvas[cury] then canvas[cury] = {} end; canvas[cury][curx] = selectedcolor end
		drawCanvasPixel(w, 18, selectedcolor)
		term.setTextColor(colors.white)
		term.setBackgroundColor(canvascolor)
		term.setCursorPos(w-1, 19)
		term.write("  \n  \n  \n  ")
		term.setCursorPos(w-1, 19)
		term.write(curx)
		term.setCursorPos(w-1, 20)
		term.write(cury)
		term.setCursorPos(w, 21)
		term.setBackgroundColor((keepDrawing and colors.green or colors.red))
		term.write((keepDrawing and "Y" or "N"))
		term.setCursorPos(curx, cury)
    end
end

-- Init
load(sPath)
drawCanvas()
drawInterface()
term.setCursorPos(curx, cury)

-- Main loop
handleEvents()

-- Shutdown
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
term.setCursorPos(1, 1)