--[[
	PAIN image editor for ComputerCraft
	Get it with
	 wget https://raw.githubusercontent.com/LDDestroier/CC/master/pain.lua pain
	 pastebin get wJQ7jav0 pain
	 std ld pain pain
--]]

local plc = {} -- pain local, to avoid upvalue limit
plc.askToSerialize = false
plc.defaultSaveFormat = 4 -- will change if importing image, or making new file with extension in name
--[[
	plc.defaultSaveFormat possible parameters:
	1. NFP (paint)
	2. NFT (npaintpro)
	3. BLT (blittle)
	4. Native PAIN
	5. GIF
	6. UCG
--]]

plc.progname = fs.getName(shell.getRunningProgram())
plc.apipath = ".painapi"

local painconfig = {
	undoBufferSize = 8,			-- amount of times undo will save your neck
	readNonImageAsNFP = true,	-- reads non-image files as NFP images
	useFlattenGIF = true,		-- will flatten compressed GIFs
	gridBleedThrough = false,	-- will draw grid instead of character value of dots
	doFillDiagonal = false,		-- checks for diagonal dots when using fill tool
	doFillAnimation = false,	-- whether or not to animate the fill tool
	useSetVisible = false,		-- whether or not to use term.current().setVisible, if possible
}

local useConfig = function(mode)
	if mode == "save" then
		local file = fs.open(fs.combine(plc.apipath,"painconfig"), "w")
		file.write(textutils.serialize(painconfig))
		file.close()
	elseif mode == "load" then
		if fs.exists(fs.combine(plc.apipath,"painconfig")) then
			local file = fs.open(fs.combine(plc.apipath,"painconfig"), "r")
			painconfig = textutils.unserialize(file.readAll())
			file.close()
		end
	end
end

useConfig("load")
useConfig("save")

plc.displayHelp = function()
	print(plc.progname)
	print(plc.progname.." <filename>")
	print(plc.progname.." [-h/--help]")
	print("Press F1 in program for more.")
end

plc.tsv = function(visible)
	if term.current().setVisible and painconfig.useSetVisible then
		term.current().setVisible(visible)
	end
end

--local undoBuffer
plc.undoPos = 1
plc.pMode = 0
local scr_x, scr_y = term.getSize()
local screenEdges = {
	scr_x,
	scr_y,
}

plc.tArg = {...}
if (plc.tArg[1] == "--help" or plc.tArg[1] == "-h") and shell then
	return plc.displayHelp()
end

if plc.tArg[2] == "view" then
	plc.pMode = 1
elseif (plc.tArg[2] == "moo") and (not fs.exists("moo")) then
	return print("This PAIN does not have Super Cow Powers.")
end

-- plc.fileName
if (not term.isColor()) and (plc.pMode ~= 1) then
	error("PAIN only works with Advanced Computers.")
end

local barmsg = "Press F1 for help."
local paintEncoded
local lastPaintEncoded
local frame = 1
local doRender = false
local metaHistory = {}
local firstTerm, blittleTerm = term.current()
plc.bepimode = false      -- this is a family-friendly program! now stand still while I murder you
plc.evenDrawGrid = true   -- will you evenDraw(the)Grid ?
plc.renderBlittle = false -- whether or not to render all in blittle
plc.firstBG = term.getBackgroundColor()
plc.firstTX = term.getTextColor()
plc.changedImage = false
plc.isCurrentlyFilling = false
local theClipboard = {}

local _
local tableconcat = table.concat

local rendback = {
	b = colors.black,
	t = colors.gray,
}

local grid

local yield = function()
	os.queueEvent("yield")
	os.pullEvent("yield")
end

local paint = {
	scrollX = 0,
	scrollY = 0,
	t = colors.gray,
	b = colors.white,
	m = 1,	--  in case you want to use PAIN as a level editor or something
	c = " ",
	doGray = false,
}
local boxchar = {topLeft = true, topRight = true, left = true, right = true, bottomLeft = true, bottomRight = true}
local swapColors = false -- swaps background and text colors, for use with those tricky box characters
local scrollX, scrollY = 0, 0

local keysDown = {}
local miceDown = {}

local doRenderBar = 1 -- Not true or false

local fixstr = function(str)
	return str:gsub("\\(%d%d%d)",string.char)
end

local choice = function(input,breakkeys,returnNumber)
	local fpos = 0
	repeat
		event, key = os.pullEvent("key")
		if type(key) == "number" then key = keys.getName(key) end
		if key == nil then key = " " end
		if type(breakkeys) == "table" then
			for a = 1, #breakkeys do
				if key == breakkeys[a] then
					return ""
				end
			end
		end
		fpos = string.find(input, key)
	until fpos
	return returnNumber and fpos or key
end
local explode = function(div,str)
    if (div=='') then return false end
    local pos,arr = 0,{}
    for st,sp in function() return string.find(str,div,pos,true) end do
        arr[#arr+1] = str:sub(pos,st-1)
        pos = sp + 1
    end
    arr[#arr+1] = str:sub(pos)
    return arr
end

local cutString = function(max_line_length, str) -- from stack overflow
   local lines = {}
   local line
   str:gsub('(%s*)(%S+)',
      function(spc, word)
         if not line or #line + #spc + #word > max_line_length then
            lines[#lines+1] = line
            line = word
         else
            line = line..spc..word
         end
      end
   )
   lines[#lines+1] = line
   return lines
end

local getDrawingCharacter = function(topLeft, topRight, left, right, bottomLeft, bottomRight) -- thank you oli414
  local data = 128
  if not bottomRight then
        data = data + (topLeft and 1 or 0)
        data = data + (topRight and 2 or 0)
        data = data + (left and 4 or 0)
        data = data + (right and 8 or 0)
        data = data + (bottomLeft and 16 or 0)
  else
        data = data + (topLeft and 0 or 1)
        data = data + (topRight and 0 or 2)
        data = data + (left and 0 or 4)
        data = data + (right and 0 or 8)
        data = data + (bottomLeft and 0 or 16)
  end
  return {char = string.char(data), inverted = bottomRight}
end

local cutUp = function(len,tbl)
	local output = {}
	local e = 0
	local s
	for a = 1, #tbl do
		if #(tbl[a]:gsub(" ","")) == 0 then
			s = {""}
		else
			s = cutString(len,tbl[a])
		end
		for b = 1, #s do
			output[#output+1] = s[b]
		end
	end
	return output
end

local getEvents = function(...)
	local arg, output = table.pack(...)
	while true do
		output = {os.pullEvent()}
		for a = 1, #arg do
			if type(arg[a]) == "boolean" then
				if doRender == arg[a] then
					return {}
				end
			elseif output[1] == arg[a] then
				return table.unpack(output)
			end
		end
	end
end



local sanitize = function(sani,tize)
	local _,x = string.find(sani,tize)
	if x then
		return sani:sub(x+1)
	else
		return sani
	end
end
local ro = function(input, max)
	return math.floor(input % max)
end

local guiHelp = function(inputText)
	term.redirect(firstTerm)
	scr_x, scr_y = term.current().getSize()
	local _helpText = inputText or [[

'PAIN' super-verbose help page
  Programmed by LDDestroier

(use UP/DOWN or scrollwheel, exit with Q)
If you wish to use PAIN to its fullest, read everything here.
You'll be image-editing like a pro in no time flat.

Syntax:
>pain <filename> [view] [x] [y]
>pain [-n]
>pain [-h/--help]

[view]: renders the image once (optionally scrolling with [x] and [y])
"-n" or no arguments: Create new document, declare name upon saving
"-h" or "--help": Display short syntax help

You can see what colors are selected based on the word "PAIN" on the hotbar.

Hotkeys:
 left/right ctrl: Toggle the menu

 left click:
  +left shift = Drag and let go to draw a line
  -alone      = Place a dot

 Right Click: delete pixel

 Middle Click, or "T": Place text down with current colors; cancel with X

 "Z":
  +LeftAlt = Redo
  -alone   = Undo

 "P": Pick colors from position onscreen; cancel with X

 "N":
  +LeftShift = Change character to that of a special character
  -alone     = Change box character for drawing
  (cancel with CTRL, N, or by clicking outside)

 "[" or mouse scroll down:
  +LeftShift = Change to previous text color
  -alone     = Change to previous background color

 "]" or mouse scroll up:
  +LeftShift = Change to next text color
  -alone     = Change to next background color

 "F1":
  -alone = Access help screen

 "F3:"
  -alone = View all connected monitors

 Spacebar:
  +LeftShift = Toggle background grid
  -alone     = Toggle bar visibility

 Arrow keys:
  +LeftShift = Displaces the entire frame
  +Tab       = Moves canvas one pixel at a time
  -alone     = Looks around the canvas smoothly

 "+" (or equals):
  +LeftAlt    = Swap the current frame with the next frame
  +LeftShift  = Merge the current frame atop the next frame
  +RightShift = If you are making a new frame, duplicates the last frame
  -alone      = Change to next frame

 "-":
  +LeftAlt   = Swap the current frame with the previous frame
  +LeftShift = Merge the current frame atop the previous frame
  -alone     = Change to previous frame

 (oh good, you're actually reading this stuff)

 "A": Set the coordinates to 0,0

 "B": Toggle redirect to blittle, to preview in teletext characters

 "c":
  +LeftAlt = Select region to copy to specified clipboard
	-alone   = Input coordinates to scroll over to

 "LeftAlt + X": Select region to cut to specified clipboard

 "LeftAlt + X": Pastes from specified clipboard

 "G": toggle grayscale mode.
  Everything is in shades of gray.
  If you Save, it saves in grayscale.

 "F":
  +LeftShift = fill all empty pixels with background color and selected box character
  -alone     = activate fill tool - click anywhere to fill with color

 "M": set metadata for pixels (for game makers, otherwise please ignore)

==================================
 Thy Menu (accessible with CTRL):
==================================

 Left click on a menu item to select it.
 If you click on the menubar, release on an option to select it.

 "File > Save"
 Saves all frames to a specially formatted PAIN paint file. The format PAIN uses is very inefficient despite my best efforts, so Export if you don't use text or multiple frame.

 "File > Save As"
 Same as "File > Save", but you change the filename.

 "File > Export"
 Exports current frame to NFP, NFT, BLT, or the horribly inefficient PAIN format.

 "File > Open"
 Opens up a file picker for you to change the image currently being edited.

 "Edit > Delete Frame"
 Deletes the current frame. Tells you off if you try to delete the only frame.

 "Edit > Clear"
 Deletes all pixels on the current frame.

 "Edit > Crop Frame"
 Deletes all pixels that are outside of the screen.

 "Edit > Change Box Character"
 Opens the block character selection. Used for making those delicious subpixel pictures.

 "Edit > Change Special Character"
 Opens the special character selector, which lets you change the paint character to that of byte 0 to 255.

 "Edit > BLittle Shrink"
 Shrinks the current frame using the BLittle API. Very lossy unless you use one color, or are careful with how you use colors. You can set "Always Render Grid" to true to assist in making blocky graphics.

 "Edit > BLittle Grow"
 Grows the image by (2x, 3y) to reverse the effects of "BLittle Shrink". This isn't lossy, since all it does is inflate an image's size and converts the corresponding block characters.

 "Edit > Copy"
 Drag to select a region of the screen, and save it in a clipboard of a specified name.

 "Edit > Cut"
 Same as Copy, but will delete the selected region on the screen.

 "Edit > Paste"
 Takes the contents of the specified clipboard, and plops it on the canvas where the mouse is.
 (The mouse will indicate the top-left corner of the pasted selection)

 "Set > ..."
 Each option will toggle a config option (or set it's value to something else).
 Changing a value is saved automatically, and effective immediately.

 "Window > Set Screen Size"
 Sets the sizes of the screen border references displayed on the canvas.
 You can also input the name of a monitor object, and it will use its size instead.

 "Window > Set Grid Colors"
 Sets the backdrop colors to your currently selected color configuration.

 "About > PAIN"
 Tells you about PAIN and its developer.

 "About > File Formats"
 Tells you the ins and outs of the file formats, and a brief description of their creators.

 "About > Help"
 Opens up this help page.

 "Exit"
 Closes PAIN. I know, riviting stuff. You can close out of this help page with "Q", speaking of.


I hope my PAIN causes you joy.
]]
	_helpText = explode("\n",_helpText)
	helpText = cutUp(scr_x,_helpText)
	local helpscroll = 0
	term.setBackgroundColor(colors.gray)
	term.setTextColor(colors.white)
	term.clear()
	local evt, key
	while true do
		term.clear()
		for a = 1, scr_y do
			term.setCursorPos(1,a)
			term.clearLine()
			write(helpText[a-helpscroll] or "")
		end
		repeat
			evt,key = os.pullEvent()
		until evt == "key" or evt == "mouse_scroll"
		if evt == "key" then
			if key == keys.up then
				helpscroll = helpscroll + 1
			elseif key == keys.down then
				helpscroll = helpscroll - 1
			elseif key == keys.pageUp then
				helpscroll = helpscroll + scr_y
			elseif key == keys.pageDown then
				helpscroll = helpscroll - scr_y
			elseif (key == keys.q) or (key == keys.space) then
				doRender = true
				if plc.renderBlittle then term.redirect(blittleTerm) end
				scr_x, scr_y = term.current().getSize()
				return
			end
		elseif evt == "mouse_scroll" then
			helpscroll = helpscroll - key
		end
		if helpscroll > 0 then
			helpscroll = 0
		elseif helpscroll < -(#helpText-(scr_y-3)) then
			helpscroll = -(#helpText-(scr_y-3))
		end
	end
end

local tableRemfind = function(tbl, str)
	local out = tbl
	for a = 1, #tbl do
		if tbl[a] == str then
			table.remove(out,a)
			return out,a
		end
	end
	return {}
end

local stringShift = function(str,amt)
	return str:sub(ro(amt-1,#str)+1)..str:sub(1,ro(amt-1,#str))
end

local deepCopy
deepCopy = function(obj)
	if type(obj) ~= 'table' then return obj end
	local res = {}
	for k, v in pairs(obj) do res[deepCopy(k)] = deepCopy(v) end
	return res
end

local clearLines = function(y1, y2)
	local cx,cy = term.getCursorPos()
	for y = y1, y2 do
		term.setCursorPos(1,y)
		term.clearLine()
	end
	term.setCursorPos(cx,cy)
end

local renderBottomBar = function(txt,extraClearY)
	term.setCursorPos(1,scr_y - math.floor(#txt/scr_x))
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.black)
	clearLines(scr_y - (math.floor(#txt/scr_x) - (extraClearY or 0)), scr_y)
	return write(txt)
end

local bottomPrompt = function(txt,history,cho,breakkeys,returnNumber,writeIndent)
	local writeIndent = renderBottomBar(txt,writeIndent)
	local out
	sleep(0.05)
	if cho then
		out = choice(cho,breakkeys,returnNumber)
	else
		out = read(_,history)
	end
	return out, writeIndent
end

local makeSubMenu = function(x,y,options)
	local longestLen = 0
	for a = 1, #options do
		if #options[a] > longestLen then
			longestLen = #options[a]
		end
	end
	longestLen = longestLen + 1
	term.setTextColor(colors.black)
	local sel = 1
	local rend = function()
		for a = #options, 1, -1 do
			term.setCursorPos(x or 1, ((y or (scr_y-1)) - (#options-1)) + (a - 1))
			term.setBackgroundColor(a == sel and colors.white or colors.lightGray)
			term.write(options[a])
			term.setBackgroundColor(colors.lightGray)
			term.write((" "):rep(longestLen-#options[a]))
		end
	end
	local usingMouse = false
	while true do
		rend()
		local evt, key, mx, my = os.pullEvent()
		if evt == "key" then
			if key == keys.up then
				sel = sel - 1
			elseif key == keys.down then
				sel = sel + 1
			elseif (key == keys.enter) or (key == keys.right) then
				return sel, longestLen
			elseif (key == keys.leftCtrl) or (key == keys.rightCtrl) or (key == keys.backspace) or (key == keys.left) then
				return false, longestLen
			end
		elseif evt == "mouse_drag" or evt == "mouse_click" then
			if (mx >= x) and (mx < x+longestLen) and (my <= y and my > y-#options) then
				sel = math.min(#options,math.max(1,(my+#options) - y))
				usingMouse = true
			else
				usingMouse = false
				if evt == "mouse_click" then
					return false, longestLen
				end
			end
		elseif evt == "mouse_up" then
			if usingMouse then
				return sel, longestLen
			end
		end
		if sel > #options then sel = 1 elseif sel < 1 then sel = #options end
	end
end

local getDotsInLine = function( startX, startY, endX, endY ) -- stolen from the paintutils API...nwehehehe
	local out = {}
	startX = math.floor(startX)
	startY = math.floor(startY)
	endX = math.floor(endX)
	endY = math.floor(endY)
	if startX == endX and startY == endY then
		out = {{x=startX,y=startY}}
		return out
	end
    local minX = math.min( startX, endX )
	if minX == startX then
		minY = startY
		maxX = endX
		maxY = endY
	else
		minY = endY
		maxX = startX
		maxY = startY
	end
	local xDiff = maxX - minX
	local yDiff = maxY - minY
	if xDiff > math.abs(yDiff) then
        local y = minY
        local dy = yDiff / xDiff
        for x=minX,maxX do
            out[#out+1] = {x=x,y=math.floor(y+0.5)}
            y = y + dy
        end
    else
        local x = minX
        local dx = xDiff / yDiff
        if maxY >= minY then
            for y=minY,maxY do
                out[#out+1] = {x=math.floor(x+0.5),y=y}
                x = x + dx
            end
        else
            for y=minY,maxY,-1 do
                out[#out+1] = {x=math.floor(x+0.5),y=y}
                x = x - dx
            end
        end
    end
    return out
end

local movePaintEncoded = function(pe,xdiff,ydiff)
	local outpootis = deepCopy(pe)
	for a = 1, #outpootis do
		outpootis[a].x = outpootis[a].x+xdiff
		outpootis[a].y = outpootis[a].y+ydiff
	end
	return outpootis
end

local clearRedundant = function(dots)
	local input = {}
	local pheight = 0
	local pwidth = 0
	local minX, minY = 0, 0
	for a = 1, #dots do
		pheight = math.max(pheight, dots[a].y)
		pwidth = math.max(pwidth, dots[a].x)
		minX = math.min(minX, dots[a].x)
		minY = math.min(minY, dots[a].y)
	end
	for a = 1, #dots do
		if not input[dots[a].y] then input[dots[a].y] = {} end
		input[dots[a].y][dots[a].x] = dots[a]
	end
	local output = {}
	local frame = 0
	for y = minY, pheight do
		for x = minX, pwidth do
			if input[y] then
				if input[y][x] then
					output[#output+1] = input[y][x]
				end
			end
			if frame >= 50 then
				-- yield()
				frame = 0
			end
		end
	end
	return output
end

local grayOut = function(color)
	local c = deepCopy(_G.colors)
	local grays = {
		[c.white] = c.white,
		[c.orange] = c.lightGray,
		[c.magenta] = c.lightGray,
		[c.lightBlue] = c.lightGray,
		[c.yellow] = c.white,
		[c.lime] = c.lightGray,
		[c.pink] = c.lightGray,
		[c.gray] = c.gray,
		[c.lightGray] = c.lightGray,
		[c.cyan] = c.lightGray,
		[c.purple] = c.gray,
		[c.blue] = c.gray,
		[c.brown] = c.gray,
		[c.green] = c.lightGray,
		[c.red] = c.gray,
		[c.black] = c.black,
	}
	if (not color) or (color == " ") then return color end
	local newColor = grays[color] or 1
	return newColor
end

local getOnscreenCoords = function(tbl,_x,_y)
	local screenTbl = {}
	for a = 1, #tbl do
		if tbl[a].x+paint.scrollX > 0 and tbl[a].x+paint.scrollX <= scr_x then
			if tbl[a].y+paint.scrollY > 0 and tbl[a].y+paint.scrollY <= scr_y then
				screenTbl[#screenTbl+1] = {tbl[a].x+paint.scrollX,tbl[a].y+paint.scrollY}
			end
		end
	end
	if not _x and _y then
		return screenTbl
	else
		for a = 1, #screenTbl do
			if screenTbl[a][1] == _x and screenTbl[a][2] == _y then
				return true
			end
		end
		return false
	end
end

local clearAllRedundant = function(info)
	local output = {}
	for a = 1, #info do
		output[a] = clearRedundant(info[a])
		if a % 4 == 0 then yield() end
	end
	return output
end

local saveFile = function(path,info)
	local output = clearAllRedundant(info)
	local fileout = textutils.serialize(output):gsub("  ",""):gsub("\n",""):gsub(" = ","="):gsub(",}","}"):gsub("}},{{","}},\n{{")
	if #fileout >= fs.getFreeSpace(fs.getDir(path)) then
		barmsg = "Not enough space."
		return
	end
	local file = fs.open(path,"w")
	file.write(fileout)
	file.close()
end
local renderBar = function(msg,dontSetVisible)
	if (doRenderBar == 0) or plc.renderBlittle then return end
	if not dontSetVisible then plc.tsv(false) end
	term.setCursorPos(1,scr_y)
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.black)
	term.clearLine()
	term.setBackgroundColor(paint.b or rendback.b)
	term.setTextColor(paint.t or rendback.t)
	term.setCursorPos(2,scr_y)
	term.write("PAIN")
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.black)
	local fmsg = tableconcat({"Fr:",frame,"/",#paintEncoded," (",paint.scrollX,",",paint.scrollY,")"})
	term.setCursorPos(7,scr_y)
	term.write(msg)
	term.setCursorPos(scr_x-(#fmsg),scr_y)
	term.write(fmsg)
	if not dontSetVisible then plc.tsv(true) end
end

local tableFormatPE = function(input)
	local doot = {}
	local pwidths = {}
	local pheight = 0
	for k, dot in pairs(input) do
		pwidths[dot.y] = math.max((pwidths[dot.y] or 0), dot.x)
		pheight = math.max(pheight, dot.y)
		doot[dot.y] = doot[dot.y] or {}
		doot[dot.y][dot.x] = {
			char = dot.c,
			text = CTB(dot.t),
			back = CTB(dot.b)
		}
	end
	for y = 1, pheight do
		pwidths[y] = pwidths[y] or 0
		if doot[y] then
			for x = 1, pwidths[y] do
				doot[y][x] = doot[y][x] or {
					text = " ",
					back = " ",
					char = " ",
				}
			end
		else
			doot[y] = false
		end
	end
	return doot, pheight, pwidths
end

CTB = function(_color) --Color To Blit
	local blitcolors = {
		[0] = " ",
		[colors.white] = "0",
		[colors.orange] = "1",
		[colors.magenta] = "2",
		[colors.lightBlue] = "3",
		[colors.yellow] = "4",
		[colors.lime] = "5",
		[colors.pink] = "6",
		[colors.gray] = "7",
		[colors.lightGray] = "8",
		[colors.cyan] = "9",
		[colors.purple] = "a",
		[colors.blue] = "b",
		[colors.brown] = "c",
		[colors.green] = "d",
		[colors.red] = "e",
		[colors.black] = "f",
	}
	if _color == nil then return nil end
	return blitcolors[_color] or "f"
end

BTC = function(_color,allowZero) --Blit To Color
	local blitcolors = {
		[" "] = allowZero and 0 or nil,
		["0"] = colors.white,
		["1"] = colors.orange,
		["2"] = colors.magenta,
		["3"] = colors.lightBlue,
		["4"] = colors.yellow,
		["5"] = colors.lime,
		["6"] = colors.pink,
		["7"] = colors.gray,
		["8"] = colors.lightGray,
		["9"] = colors.cyan,
		["a"] = colors.purple,
		["b"] = colors.blue,
		["c"] = colors.brown,
		["d"] = colors.green,
		["e"] = colors.red,
		["f"] = colors.black,
	}
	if _color == nil then return nil end
	return blitcolors[_color]
end

local renderPainyThings = function(xscroll,yscroll,doGrid)
	local yadjust = (plc.renderBlittle and 0 or doRenderBar)
	if plc.bepimode then
		grid = {
			"Bepis",
			"episB",
			"pisBe",
			"isBep",
			"sBepi",
		}
	else
		grid = {
			"%%..",
			"%%..",
			"%%..",
			"..%%",
			"..%%",
			"..%%",
		}
	end
	term.setBackgroundColor(rendback.b)
	term.setTextColor(rendback.t)
	local badchar = "/"
	local blittlelabel = "blittle max"
	local screenlabel = "screen max"

	local dotBuffChar, dotBuffBack = "", "" --only used if gridBleedThrough is true
	local doot
	if doGrid then
		for y = 1, scr_y - yadjust do
			term.setCursorPos(1,y)
			-- the single most convoluted line I've ever written that works, and I love it
			term.write(stringShift(grid[ro(y+(yscroll+2),#grid)+1],xscroll+1):rep(math.ceil(scr_x/#grid[ro(y+(yscroll+2),#grid)+1])):sub(1,scr_x))
			term.setCursorPos((xscroll <= 0) and (1-xscroll) or 0,y)
			if ((screenEdges[2]+1)-yscroll) == y then --regular limit
				term.write( (string.rep("@", math.max(0,( (screenEdges[1])     ) - (#screenlabel+1)  )) ..screenlabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]) )
			elseif (((screenEdges[2]*3)+1)-yscroll) == y then --blittle limit
				term.write( (string.rep("@", math.max(0,( ((screenEdges[1]*2))   ) - (#blittlelabel+1) ))..blittlelabel:gsub(" ","@"):upper().."@@"):sub(xscroll>0 and xscroll or 0):sub(1,1+screenEdges[1]*2) )
			end
			-- Stupid easter eggs, ho! --
			if 1000-yscroll == y then
				term.setCursorPos(1000-xscroll,y)
				term.write(" What ARE you doing? Stop messing around! ")
			end
			if 2016-yscroll == y then
				term.setCursorPos(200-xscroll,y)
				term.write(" Lines don't like to be intersected, you know. ")
			end
			if 2017-yscroll == y then
				term.setCursorPos(200-xscroll,y)
				term.write(" It makes them very crossed. ")
			end
			if 800-yscroll == y then
				term.setCursorPos(1700-xscroll,y)
				term.write(" You stare deeply into the void. ")
			end
			if 801-yscroll == y then
				term.setCursorPos(1704-xscroll,y)
				term.write(" And the void ")
			end
			if 802-yscroll == y then
				term.setCursorPos(1704-xscroll,y)
				term.write(" stares back. ")
			end
			--Is this the end?--
			if (xscroll > ((screenEdges[1]*2)-scr_x)) then
				for y = 1, scr_y do
					if y+yscroll <= (screenEdges[2]*3) then
						if not (y == scr_y and doRenderBar == 1) then
							term.setCursorPos((screenEdges[1]+1)-(xscroll-screenEdges[1]),y)
							term.write("@")
						end
					end
				end
			end
			if (xscroll > (screenEdges[1]-scr_x)) then --regular limit
				for y = 1, scr_y do
					if y+yscroll <= screenEdges[2] then
						if not (y == scr_y and doRenderBar == 1) then
							term.setCursorPos((screenEdges[1]+1)-xscroll,y)
							term.write("@")
						end
					end
				end
			end
		end
		--render areas that won't save
		if xscroll < 0 then
			for y = 1, scr_y do
				if not (y == scr_y and doRenderBar == 1) then
					term.setCursorPos(1,y)
					term.write(badchar:rep(-xscroll))
				end
			end
		end
		if yscroll < 0 then
			for y = 1, -yscroll do
				if not (y == scr_y and doRenderBar == 1) then
					term.setCursorPos(1,y)
					term.write(badchar:rep(scr_x))
				end
			end
		end
	else
		for y = 1, scr_y - yadjust do
			term.setCursorPos(1,y)
			term.clearLine()
		end
	end
end

importFromPaint = function(theInput)
	local output = {}
	local input
	if type(theInput) == "string" then
		input = explode("\n",theInput)
	else
		input = {}
		for y = 1, #theInput do
			input[y] = ""
			for x = 1, #theInput[y] do
				input[y] = input[y]..(CTB(theInput[y][x]) or " ")
			end
		end
	end
	for a = 1, #input do
		line = input[a]
		for b = 1, #line do
			if (line:sub(b,b) ~= " ") and BTC(line:sub(b,b)) then
				output[#output+1] = {
					x = b,
					y = a,
					t = colors.white,
					b = BTC(line:sub(b,b)) or colors.black,
					c = " ",
				}
			end
		end
	end
	return output
end

local lddfm = {
	scroll = 0,
	ypaths = {}
}

lddfm.scr_x, lddfm.scr_y = term.getSize()

lddfm.setPalate = function(_p)
	if type(_p) ~= "table" then
		_p = {}
	end
	lddfm.p = { --the DEFAULT color palate
		bg =        _p.bg or colors.gray,			-- whole background color
		d_txt =     _p.d_txt or colors.yellow,		-- directory text color
		d_bg =      _p.d_bg or colors.gray,			-- directory bg color
		f_txt =     _p.f_txt or colors.white,		-- file text color
		f_bg =      _p.f_bg or colors.gray,			-- file bg color
		p_txt =     _p.p_txt or colors.black,		-- path text color
		p_bg =      _p.p_bg or colors.lightGray,	-- path bg color
		close_txt = _p.close_txt or colors.gray,	-- close button text color
		close_bg =  _p.close_bg or colors.lightGray,-- close button bg color
		scr =       _p.scr or colors.lightGray,		-- scrollbar color
		scrbar =    _p.scrbar or colors.gray,		-- scroll tab color
	}
end

lddfm.setPalate()

lddfm.foldersOnTop = function(floop,path)
	local output = {}
	for a = 1, #floop do
		if fs.isDir(fs.combine(path,floop[a])) then
			table.insert(output,1,floop[a])
		else
			table.insert(output,floop[a])
		end
	end
	return output
end

lddfm.filterFileFolders = function(list,path,_noFiles,_noFolders,_noCD,_doHidden)
	local output = {}
	for a = 1, #list do
		local entry = fs.combine(path,list[a])
		if fs.isDir(entry) then
			if entry == ".." then
				if not (_noCD or _noFolders) then table.insert(output,list[a]) end
			else
				if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
					if not _noFolders then table.insert(output,list[a]) end
				end
			end
		else
			if not ((not _doHidden) and list[a]:sub(1,1) == ".") then
				if not _noFiles then table.insert(output,list[a]) end
			end
		end
	end
	return output
end

lddfm.isColor = function(col)
	for k,v in pairs(colors) do
		if v == col then
			return true, k
		end
	end
	return false
end

lddfm.clearLine = function(x1,x2,_y,_bg,_char)
	local cbg, bg = term.getBackgroundColor()
	local x,y = term.getCursorPos()
	local sx,sy = term.getSize()
	if type(_char) == "string" then char = _char else char = " " end
	if type(_bg) == "number" then
		if lddfm.isColor(_bg) then bg = _bg
		else bg = cbg end
	else bg = cbg end
	term.setCursorPos(x1 or 1, _y or y)
	term.setBackgroundColor(bg)
	if x2 then --it pains me to add an if statement to something as simple as this
		term.write((char or " "):rep(x2-x1))
	else
		term.write((char or " "):rep(sx-(x1 or 0)))
	end
	term.setBackgroundColor(cbg)
	term.setCursorPos(x,y)
end

lddfm.render = function(_x1,_y1,_x2,_y2,_rlist,_path,_rscroll,_canClose,_scrbarY)
	local px,py = term.getCursorPos()
	plc.tsv(false)
	local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
	local rlist = _rlist or {"Invalid directory."}
	local path = _path or "And that's terrible."
	ypaths = {}
	local rscroll = _rscroll or 0
	for a = y1, y2 do
		lddfm.clearLine(x1,x2,a,lddfm.p.bg)
	end
	term.setCursorPos(x1,y1)
	term.setTextColor(lddfm.p.p_txt)
	lddfm.clearLine(x1,x2+1,y1,lddfm.p.p_bg)
	term.setBackgroundColor(lddfm.p.p_bg)
	term.write(("/"..path):sub(1,x2-x1))
	for a = 1,(y2-y1) do
		if rlist[a+rscroll] then
			term.setCursorPos(x1,a+(y1))
			if fs.isDir(fs.combine(path,rlist[a+rscroll])) then
				lddfm.clearLine(x1,x2,a+(y1),lddfm.p.d_bg)
				term.setTextColor(lddfm.p.d_txt)
				term.setBackgroundColor(lddfm.p.d_bg)
			else
				lddfm.clearLine(x1,x2,a+(y1),lddfm.p.f_bg)
				term.setTextColor(lddfm.p.f_txt)
				term.setBackgroundColor(lddfm.p.f_bg)
			end
			term.write(rlist[a+rscroll]:sub(1,x2-x1))
			ypaths[a+(y1)] = rlist[a+rscroll]
		else
			lddfm.clearLine(x1,x2,a+(y1),lddfm.p.bg)
		end
	end
	local scrbarY = _scrbarY or math.ceil( (y1+1)+( (_rscroll/(#_rlist-(y2-(y1+1))))*(y2-(y1+1)) ) )
	for a = y1+1, y2 do
		term.setCursorPos(x2,a)
		if a == scrbarY then
			term.setBackgroundColor(lddfm.p.scrbar)
		else
			term.setBackgroundColor(lddfm.p.scr)
		end
		term.write(" ")
	end
	if _canClose then
		term.setCursorPos(x2-4,y1)
		term.setTextColor(lddfm.p.close_txt)
		term.setBackgroundColor(lddfm.p.close_bg)
		term.write("close")
	end
	term.setCursorPos(px,py)
	plc.tsv(true)
	return scrbarY
end

lddfm.coolOutro = function(x1,y1,x2,y2,_bg,_txt,char)
	local cx, cy = term.getCursorPos()
	local bg, txt = term.getBackgroundColor(), term.getTextColor()
	term.setTextColor(_txt or colors.white)
	term.setBackgroundColor(_bg or colors.black)
	local _uwah = 0
	for y = y1, y2 do
		for x = x1, x2 do
			_uwah = _uwah + 1
			term.setCursorPos(x,y)
			term.write(char or " ")
			if _uwah >= math.ceil((x2-x1)*1.63) then sleep(0.05) _uwah = 0 end
		end
	end
	term.setTextColor(txt)
	term.setBackgroundColor(bg)
	term.setCursorPos(cx,cy)
end

lddfm.scrollMenu = function(amount,list,y1,y2)
	if #list >= y2-y1 then
		lddfm.scroll = lddfm.scroll + amount
		if lddfm.scroll < 0 then
			lddfm.scroll = 0
		end
		if lddfm.scroll > #list-(y2-y1) then
			lddfm.scroll = #list-(y2-y1)
		end
	end
end

lddfm.makeMenu = function(_x1,_y1,_x2,_y2,_path,_noFiles,_noFolders,_noCD,_noSelectFolders,_doHidden,_p,_canClose)
	if _noFiles and _noFolders then
		return false, "C'mon, man..."
	end
	if _x1 == true then
		return false, "arguments: x1, y1, x2, y2, path, noFiles, noFolders, noCD, noSelectFolders, doHidden, palate, canClose" -- a little help
	end
	lddfm.setPalate(_p)
	local path, list = _path or ""
	lddfm.scroll = 0
	local _pbg, _ptxt = term.getBackgroundColor(), term.getTextColor()
	local x1, x2, y1, y2 = _x1 or 1, _x2 or lddfm.scr_x, _y1 or 1, _y2 or lddfm.scr_y
	local keysDown = {}
	local _barrY
	while true do
		list = lddfm.foldersOnTop(lddfm.filterFileFolders(fs.list(path),path,_noFiles,_noFolders,_noCD,_doHidden),path)
		if (fs.getDir(path) ~= "..") and not (_noCD or _noFolders) then
			table.insert(list,1,"..")
		end
		_res, _barrY = pcall( function() return lddfm.render(x1,y1,x2,y2,list,path,lddfm.scroll,_canClose) end)
		if not _res then
			plc.tsv(true)
			error(_barrY)
		end
		local evt = {os.pullEvent()}
		if evt[1] == "mouse_scroll" then
			lddfm.scrollMenu(evt[2],list,y1,y2)
		elseif evt[1] == "mouse_click" then
			local butt,mx,my = evt[2],evt[3],evt[4]
			if (butt == 1 and my == y1 and mx <= x2 and mx >= x2-4) and _canClose then
				--lddfm.coolOutro(x1,y1,x2,y2)
				term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
				return false
			elseif ypaths[my] and (mx >= x1 and mx < x2) then --x2 is reserved for the scrollbar, breh
				if fs.isDir(fs.combine(path,ypaths[my])) then
					if _noCD or butt == 3 then
						if not _noSelectFolders or _noFolders then
							--lddfm.coolOutro(x1,y1,x2,y2)
							term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
							return fs.combine(path,ypaths[my])
						end
					else
						path = fs.combine(path,ypaths[my])
						lddfm.scroll = 0
					end
				else
					term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
					return fs.combine(path,ypaths[my])
				end
			end
		elseif evt[1] == "key" then
			keysDown[evt[2]] = true
			if evt[2] == keys.enter and not (_noFolders or _noCD or _noSelectFolders) then --the logic for _noCD being you'd normally need to go back a directory to select the current directory.
				--lddfm.coolOutro(x1,y1,x2,y2)
				term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
				return path
			end
			if evt[2] == keys.up then
				lddfm.scrollMenu(-1,list,y1,y2)
			elseif evt[2] == keys.down then
				lddfm.scrollMenu(1,list,y1,y2)
			end
			if evt[2] == keys.pageUp then
				lddfm.scrollMenu(y1-y2,list,y1,y2)
			elseif evt[2] == keys.pageDown then
				lddfm.scrollMenu(y2-y1,list,y1,y2)
			end
			if evt[2] == keys.home then
				lddfm.scroll = 0
			elseif evt[2] == keys["end"] then
				if #list > (y2-y1) then
					lddfm.scroll = #list-(y2-y1)
				end
			end
			if evt[2] == keys.h then
				if keysDown[keys.leftCtrl] or keysDown[keys.rightCtrl] then
					_doHidden = not _doHidden
				end
			elseif _canClose and (evt[2] == keys.x or evt[2] == keys.q or evt[2] == keys.leftCtrl) then
				--lddfm.coolOutro(x1,y1,x2,y2)
				term.setTextColor(_ptxt) term.setBackgroundColor(_pbg)
				return false
			end
		elseif evt[1] == "key_up" then
			keysDown[evt[2]] = false
		end
	end
end

local getBlittle = function()
	if not blittle then
		if fs.exists(fs.combine(plc.apipath,"blittle")) then
			os.loadAPI(fs.combine(plc.apipath,"blittle"))
			if not blittleTerm then
				blittleTerm = blittle.createWindow()
			end
			return blittleTerm, firstTerm
		else
			local geet = http.get("http://pastebin.com/raw/ujchRSnU")
			if not geet then
				return false
			else
				geet = geet.readAll()
				local file = fs.open(fs.combine(plc.apipath,"blittle"),"w")
				file.write(geet)
				file.close()
				os.loadAPI(fs.combine(plc.apipath,"blittle"))
				--fs.delete(plc.apipath)
				if not blittleTerm then
					blittleTerm = blittle.createWindow()
				end
				return blittleTerm, firstTerm
			end
		end
	else
		if not blittleTerm then
			blittleTerm = blittle.createWindow()
		end
		return blittleTerm, firstTerm
	end
end

local getUCG = function()
	if not ucg then
		if fs.exists(fs.combine(plc.apipath,"ucg")) then
			os.loadAPI(fs.combine(plc.apipath,"ucg"))
			return true
		else
			local geet = http.get("https://raw.githubusercontent.com/ardera/libucg/master/src/libucg")
			if not geet then
				return false
			else
				geet = geet.readAll()
				local file = fs.open(fs.combine(plc.apipath,"ucg"),"w")
				file.write(geet)
				file.close()
				os.loadAPI(fs.combine(plc.apipath,"ucg"))
			end
		end
	end
end

local getBBPack = function()
	if not bbpack then
		if fs.exists(fs.combine(plc.apipath,"bbpack")) then
			os.loadAPI(fs.combine(plc.apipath,"bbpack"))
			return true
		else
			local geet = http.get("https://pastebin.com/raw/cUYTGbpb")
			if not geet then
				return false
			else
				geet = geet.readAll()
				local file = fs.open(fs.combine(plc.apipath,"bbpack"),"w")
				file.write(geet)
				file.close()
				os.loadAPI(fs.combine(plc.apipath,"bbpack"))
			end
		end
	end
end

local getGIF = function()
	getBBPack()
	if not GIF then
		if fs.exists(fs.combine(plc.apipath,"GIF")) then
			os.loadAPI(fs.combine(plc.apipath,"GIF"))
			return true
		else
			local geet = http.get("https://pastebin.com/raw/5uk9uRjC")
			if not geet then
				return false
			else
				geet = geet.readAll()
				local file = fs.open(fs.combine(plc.apipath,"GIF"),"w")
				file.write(geet)
				file.close()
				os.loadAPI(fs.combine(plc.apipath,"GIF"))
			end
		end
	end
end

local NFPserializeImage = function(str)
	local bepis = explode("\n",str)
	local output = {}
	for y = 1, #bepis do
		output[y] = {}
		for x = 1, #bepis[y] do
			output[y][x] = BTC(bepis[y]:sub(x,x),true)
		end
	end
	return textutils.unserialize(textutils.serialize(output):gsub("\n",""):gsub(" ",""):gsub(",}","}"))
end

local importFromGIF = function(filename,verbose)
	getGIF()
	local output = {}
	local image
	local rawGif = GIF.loadGIF(filename)
	if painconfig.useFlattenGIF then
		if verbose then
			print("Flattening...")
		end
		rawGif = GIF.flattenGIF(rawGif)
		sleep(0.05)
	end
	local cx, cy = term.getCursorPos()
	for a = 1, #rawGif do
		output[a] = importFromPaint(GIF.toPaintutils(rawGif[a]))
		if verbose then
			term.setCursorPos(cx,cy)
			write("Did "..a.."/"..#rawGif.." ")
		end
		if a % 1 then sleep(0.05) end --used to be a % 2, might change later
	end
	return output
end

local exportToPaint

local exportToGIF = function(input)
	getGIF()
	local outGIF = {}
	for a = 1, #paintEncoded do
		outGIF[a] = NFPserializeImage(exportToPaint(paintEncoded[a]))
		sleep(0.05)
	end
	if painconfig.useFlattenGIF then
		return GIF.flattenGIF(GIF.buildGIF(table.unpack(outGIF)),true)
	else
		return GIF.buildGIF(table.unpack(outGIF))
	end
end

local importFromUCG = function(filename)
	getUCG()
	return importFromPaint(ucg.readFile(filename))
end

local exportToUCG = function(filename, input)
	getUCG()
	ucg.writeFile(filename, NFPserializeImage(exportToPaint(input)))
end

renderPAIN = function(dots,xscroll,yscroll,doPain,dontRenderBar)
	plc.tsv(false)
	local beforeTX,beforeBG = term.getTextColor(), term.getBackgroundColor()
	local cx,cy = term.getCursorPos()
	local FUCK, SHIT = pcall(function()
			if doPain then
				if (not plc.renderBlittle) then
					if not dontRenderBar then
						renderBar(barmsg,true)
					end
					renderPainyThings(xscroll,yscroll,plc.evenDrawGrid)
				else
					term.clear()
				end
			end
			for a = 1, #dots do
				local d = dots[a]
				if doPain then
					if not ((d.y-yscroll >= 1 and d.y-yscroll <= scr_y-(plc.renderBlittle and 0 or doRenderBar)) and (d.x-xscroll >= 1 and d.x-xscroll <= scr_x)) then
						d = nil
					end
				end
				if d then
					term.setCursorPos(d.x-(xscroll or 0),d.y-(yscroll or 0))
					term.setBackgroundColor((paint.doGray and grayOut(d.b) or d.b) or rendback.b)
					if painconfig.gridBleedThrough then
						term.setTextColor(rendback.t)
						term.write((d.x >= 1 and d.y >= 1) and grid[ ro( d.y+2, #grid)+1]:sub(1+ro(d.x+-1,#grid[1]), 1+ro(d.x+-1,#grid[1])) or "/")
					else
						term.setTextColor(      (paint.doGray and grayOut(d.t) or d.t) or rendback.t)
						term.write(d.c or " ")
					end
				end
			end
	end)
	term.setBackgroundColor(beforeBG or rendback.b)
	term.setTextColor(beforeTX or rendback.t)
	term.setCursorPos(cx,cy)
	plc.tsv(true)
	if not FUCK then error(SHIT) end --GOD DAMN IT
end

renderPAINFS = function(filename,xscroll,yscroll,frameNo,doPain)
	local file = fs.open(filename,"r")
	local contents = file.readAll()
	local amntFrames
	file.close()
	local tcontents = textutils.unserialize(contents)
	if type(tcontents) ~= "table" then
		tcontents = importFromPaint(contents)
	else
		amntFrames = #tcontents
		tcontents = tcontents[frameNo or 1]
	end
	renderPAIN(tcontents,xscroll,yscroll,doPain)
	return amntFrames
end

local putDotDown = function(dot) -- only 'x' and 'y' are required arguments
	paintEncoded[frame][#paintEncoded[frame]+1] = {
		x = dot.x + paint.scrollX,
		y = dot.y + paint.scrollY,
		c = dot.c or paint.c,
		b = dot.b or (swapColors and paint.t or paint.b),
		t = dot.t or (swapColors and paint.b or paint.t),
		m = dot.m or paint.m,
	}
end

local saveToUndoBuffer = function()
	if plc.undoPos < #plc.undoBuffer then
		for a = #plc.undoBuffer, plc.undoPos + 1, -1 do
			table.remove(plc.undoBuffer, a)
		end
	end
	if plc.undoPos >= painconfig.undoBufferSize then
		for a = 2, #plc.undoBuffer do
			plc.undoBuffer[a - 1] = plc.undoBuffer[a]
		end
		plc.undoBuffer[#plc.undoBuffer] = deepCopy(paintEncoded)
	else
		plc.undoPos = plc.undoPos + 1
		plc.undoBuffer[plc.undoPos] = deepCopy(paintEncoded)
	end
end

local doUndo = function()
	plc.undoPos = math.max(1, plc.undoPos - 1)
	paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos])
	if not paintEncoded[frame] then
		frame = #paintEncoded
	end
end

local doRedo = function()
	plc.undoPos = math.min(#plc.undoBuffer, plc.undoPos + 1)
	paintEncoded = deepCopy(plc.undoBuffer[plc.undoPos])
	if not paintEncoded[frame] then
		frame = #paintEncoded
	end
end

local putDownText = function(x,y)
	term.setCursorPos(x,y)
	term.setTextColor((paint.doGray and grayOut(paint.t or rendback.t)) or (paint.t or rendback.t))
	term.setBackgroundColor((paint.doGray and grayOut(paint.b or rendback.b)) or (paint.b or rendback.b))
	local msg = read()
	if #msg > 0 then
		for a = 1, #msg do
			putDotDown({x=(x+a)-1, y=y, c=msg:sub(a,a)})
		end
	end
	saveToUndoBuffer()
end

local deleteDot = function(x,y) --deletes all dots at point x,y
	local good = false
	for a = #paintEncoded[frame],1,-1 do
		local b = paintEncoded[frame][a]
		if (x == b.x) and (y == b.y) then
			table.remove(paintEncoded[frame],a)
			good = true
		end
	end
	return good
end

exportToPaint = function(input,noTransparent) --exports paintEncoded frame to regular paint format. input is expected to be paintEncoded[frame]
	local doopTXT, doopTXCOL, doopBGCOL = {}, {}, {}
	local p = input
	local pheight = 0
	local pwidth = 0
	for a = 1, #p do
		if p[a].y > pheight then
			pheight = p[a].y
		end
		if p[a].x > pwidth then
			pwidth = p[a].x
		end
	end
	for k,v in pairs(p) do
		if not doopBGCOL[v.y] then
			doopBGCOL[v.y] = {}
			doopTXCOL[v.y] = {}
			doopTXT[v.y] = {}
		end
		doopBGCOL[v.y][v.x] = CTB(v.b)
		doopTXCOL[v.y][v.x] = CTB(v.t)
		doopTXT[v.y][v.x] = v.c
	end
	local nfpoutputTXT, nfpoutputTXCOL, nfpoutputBGCOL = "", "", ""
	for y = 1, pheight do
		if doopBGCOL[y] then
			for x = 1, pwidth do
				if doopBGCOL[y][x] then
					nfpoutputBGCOL = nfpoutputBGCOL..doopBGCOL[y][x]
					nfpoutputTXCOL = nfpoutputTXCOL..doopTXCOL[y][x]
					nfpoutputTXT = nfpoutputTXT..(((doopTXT[y][x] == " " and noTransparent) and "\128" or doopTXT[y][x]) or " ")
				else
					nfpoutputBGCOL = nfpoutputBGCOL..(noTransparent and "0" or " ")
					nfpoutputTXCOL = nfpoutputTXCOL..(noTransparent and "0" or " ")
					nfpoutputTXT = nfpoutputTXT.." "
				end
			end
		end
		if y ~= pheight then
			nfpoutputBGCOL = nfpoutputBGCOL.."\n"
			nfpoutputTXCOL = nfpoutputTXCOL.."\n"
			nfpoutputTXT = nfpoutputTXT.."\n"
		end
	end
	return nfpoutputBGCOL, pheight, pwidth
end

local exportToNFT = function(input)

	local bgcode, txcode = "\30", "\31"
	local output = ""
	local text, back

	local doot, pheight, pwidths = tableFormatPE(input)

	for y = 1, pheight do

		text, back = "0", "f"
		if doot[y] then
			for x = 1, pwidths[y] do

				if doot[y][x] then
					if doot[y][x].back ~= back then
						back = doot[y][x].back
						output = output .. bgcode .. back
					end
					if doot[y][x].text ~= text then
						text = doot[y][x].text
						output = output .. txcode .. text
					end
					output = output .. doot[y][x].char
				else
					output = output .. " "
				end

			end
		end

		if y < pheight then
			output = output .. "\n"
		end
	end
	return output
end

local importFromNFT = function(input) --imports NFT formatted string image to paintEncoded[frame] formatted table image. please return a paintEncoded[frame] formatted table.
	local tinput = explode("\n",input)
	local tcol,bcol
	local cx --represents the x position in the picture
	local sx --represents the x position in the file
	local output = {}
	for y = 1, #tinput do
		tcol,bcol = colors.white,colors.black
		cx, sx = 1, 0
		while sx < #tinput[y] do
			sx = sx + 1
			if tinput[y]:sub(sx,sx) == "\30" then
				bcol = BTC(tinput[y]:sub(sx+1,sx+1))
				sx = sx + 1
			elseif tinput[y]:sub(sx,sx) == "\31" then
				tcol = BTC(tinput[y]:sub(sx+1,sx+1))
				sx = sx + 1
			else
				if tcol and bcol then
					output[#output+1] = {
						["x"] = cx,
						["y"] = y,
						["b"] = bcol,
						["t"] = tcol,
						["c"] = tinput[y]:sub(sx,sx),
						["m"] = 0,
					}
				end
				cx = cx + 1
			end
		end
	end
	return output
end

exportToBLT = function(input,filename,doAllFrames,noSave)
	local output = {}
	local thisImage,pheight,pwidth,nfpinput
	getBlittle()
	for a = doAllFrames and 1 or frame, doAllFrames and #input or frame do
		output[#output+1] = blittle.shrink(NFPserializeImage(exportToPaint(input[a]),true),colors.black)
	end
	if #output == 1 then output = output[1] end
	if not noSave then
		blittle.save(output,filename)
	end
	return output
end

importFromBLT = function(input) --takes in filename, not contents
	local output = {}
	getBlittle()
	local wholePic = blittle.load(input)
	if wholePic.height then wholePic = {wholePic} end
	local image
	for a = 1, #wholePic do
		image = wholePic[a]
		output[#output+1] = {}
		for y = 1, image.height*3 do
			for x = 1, math.max(#image[1][math.ceil(y/3)],#image[2][math.ceil(y/3)],#image[3][math.ceil(y/3)])*2 do
				output[#output][#output[#output]+1] = {
					m = 0,
					x = x,
					y = y,
					t = BTC((image[2][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
					b = BTC((image[3][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).."0"):sub(1,1)),
					c = BTC((image[1][math.ceil(y/3)]:sub(math.ceil(x/2),math.ceil(x/2)).." "):sub(1,1)),
				}
			end
		end
	end
	return output
end

local getTheDoots = function(pe)
	local hasBadDots = false
	local baddestX,baddestY = 1,1
	for b = 1, #pe do
		local doot = pe[b]
		if doot.x <= 0 or doot.y <= 0 then
			hasBadDots = true
			if doot.x < baddestX then
				baddestX = doot.x
			end
			if doot.y < baddestY then
				baddestY = doot.y
			end
		end
		if b % 64 == 0 then yield() end
	end
	return baddestX, baddestY
end

local checkBadDots = function()
	local hasBadDots = false
	for a = 1, #paintEncoded do
		local radx,rady = getTheDoots(paintEncoded[a])
		if radx ~= 1 or rady ~= 1 then
			hasBadDots = true
		end
	end
	if hasBadDots then
		local ting = bottomPrompt("Dot(s) are OoB! Save or fix? (Y/N/F)",_,"ynf",{keys.leftCtrl,keys.rightCtrl})
		if ting == "f" then
			for a = 1, #paintEncoded do
				local baddestX, baddestY = getTheDoots(paintEncoded[a])
				paintEncoded[a] = movePaintEncoded(paintEncoded[a],-(baddestX-1),-(baddestY-1))
			end
		elseif ting ~= "y" then
			barmsg = ""
			return false
		end
	end
end

local convertToGrayscale = function(pe)
	local output = pe
	for a = 1, #pe do
		for b = 1, #pe[a] do
			output[a][b].b = grayOut(pe[a][b].b)
			output[a][b].t = grayOut(pe[a][b].t)
			if not output[a][b].m then output[a][b].m = 1 end
		end
		if a % 2 == 0 then yield() end
	end
	return output
end

local reRenderPAIN = function(overrideRenderBar)
	local _reallyDoRenderBar = doRenderBar
--	doRenderBar = 1
	renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true,overrideRenderBar)
	doRenderBar = _reallyDoRenderBar
end

local fillTool = function(_frame,cx,cy,dot,isDeleting) -- "_frame" is the frame NUMBER
	local maxX, maxY = 1, 1
	local minX, minY = 1, 1
	paintEncoded = clearAllRedundant(paintEncoded)
	local frame = paintEncoded[_frame]
	local scx, scy = cx+paint.scrollX, cy+paint.scrollY
	local output = {}
	for a = 1, #frame do
		maxX = math.max(maxX, frame[a].x)
		maxY = math.max(maxY, frame[a].y)
		minX = math.min(minX, frame[a].x)
		minY = math.min(minY, frame[a].y)
	end

	maxX = math.max(maxX, scx)
	maxY = math.max(maxY, scy)
	minX = math.min(minX, scx)
	minY = math.min(minY, scy)

	maxX = math.max(maxX, screenEdges[1])
	maxY = math.max(maxY, screenEdges[2])

	local doop = {}
	local touched = {}
	local check = {[scy] = {[scx] = true}}
	for y = minY, maxY do
		doop[y] = {}
		touched[y] = {}
		for x = minX, maxX do
			doop[y][x] = {
				c = " ",
				b = 0,
				t = 0
			}
			touched[y][x] = false
		end
	end
	for a = 1, #frame do
		doop[frame[a].y][frame[a].x] = {
			c = frame[a].c,
			t = frame[a].t,
			b = frame[a].b
		}
	end
	local initDot = {
		c = doop[scy][scx].c,
		t = doop[scy][scx].t,
		b = doop[scy][scx].b
	}
	local chkpos = function(x, y, checkList)
		if (x < minX or x > maxX) or (y < minY or y > maxY) then
			return false
		else
			if (doop[y][x].b ~= initDot.b) or (doop[y][x].t ~= initDot.t and doop[y][x].c ~= " ") or (doop[y][x].c ~= initDot.c) then
				return false
			end
			if check[y] then
				if check[y][x] then
					return false
				end
			end
			if touched[y][x] then
				return false
			end
			return true
		end
	end
	local doBreak
	local step = 0
	local currentlyOnScreen
	while true do
		doBreak = true
		for chY, v in pairs(check) do
			for chX, isTrue in pairs(v) do
				currentlyOnScreen = (chX-paint.scrollX >= 1 and chX-paint.scrollX <= scr_x and chY-paint.scrollY >= 1 and chY-paint.scrollY <= scr_y)
				if isTrue and (not touched[chY][chX]) then
					step = step + 1
					if painconfig.doFillAnimation then
						if currentlyOnScreen then
							reRenderPAIN(true)
						end
					end
					if isDeleting then
						deleteDot(chX, chY)
					else
						frame[#frame+1] = {
							x = chX,
							y = chY,
							c = dot.c,
							t = dot.t,
							b = dot.b
						}
					end
					touched[chY][chX] = true
					-- check adjacent
					if chkpos(chX+1, chY) then
						check[chY][chX+1] = true
						doBreak = false
					end
					if chkpos(chX-1, chY) then
						check[chY][chX-1] = true
						doBreak = false
					end
					if chkpos(chX, chY+1) then
						check[chY+1] = check[chY+1] or {}
						check[chY+1][chX] = true
						doBreak = false
					end
					if chkpos(chX, chY-1) then
						check[chY-1] = check[chY-1] or {}
						check[chY-1][chX] = true
						doBreak = false
					end
					-- check diagonal
					if painconfig.doFillDiagonal then
						if chkpos(chX-1, chY-1) then
							check[chY-1] = check[chY-1] or {}
							check[chY-1][chX-1] = true
							doBreak = false
						end
						if chkpos(chX+1, chY-1) then
							check[chY-1] = check[chY-1] or {}
							check[chY-1][chX+1] = true
							doBreak = false
						end
						if chkpos(chX-1, chY+1) then
							check[chY+1] = check[chY+1] or {}
							check[chY+1][chX-1] = true
							doBreak = false
						end
						if chkpos(chX+1, chY+1) then
							check[chY+1] = check[chY+1] or {}
							check[chY+1][chX+1] = true
							doBreak = false
						end
					end
					if step % ((painconfig.doFillAnimation and currentlyOnScreen) and 4 or 1024) == 0 then -- tries to prevent crash
						sleep(0.05)
					end
				end
			end
		end
		if doBreak then
			break
		end
	end
	paintEncoded = clearAllRedundant(paintEncoded)
end

local boxCharSelector = function()
	local co = function(pos)
		if pos then
			term.setTextColor(colors.lime)
			term.setBackgroundColor(colors.green)
		else
			term.setTextColor(colors.lightGray)
			term.setBackgroundColor(colors.gray)
		end
	end
	local rend = function()
		term.setCursorPos(scr_x - 3, scr_y)
		term.setBackgroundColor(colors.lightGray)
		term.setTextColor(colors.gray)
		term.clearLine()
		term.write("\\" .. string.byte(getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight).char) .. " ")
		term.setTextColor(colors.black)
		term.write("Press CTRL or 'N' when ready.")
		term.setCursorPos(1,scr_y-3) co(boxchar.topLeft) write("Q") co(boxchar.topRight) write("W")
		term.setCursorPos(1,scr_y-2) co(boxchar.left) write("A") co(boxchar.right) write("S")
		term.setCursorPos(1,scr_y-1) co(boxchar.bottomLeft) write("Z") co(boxchar.bottomRight) write("X")
	end
	while true do
		rend()
		local evt = {os.pullEvent()}
		if evt[1] == "key" then
			local key = evt[2]
			if key == keys.leftCtrl or key == keys.rightCtrl or key == keys.n then
				break
			else
				if key == keys.q then boxchar.topLeft = not boxchar.topLeft end
				if key == keys.w then boxchar.topRight = not boxchar.topRight end
				if key == keys.a then boxchar.left = not boxchar.left end
				if key == keys.s then boxchar.right = not boxchar.right end
				if key == keys.z then boxchar.bottomLeft = not boxchar.bottomLeft end
				if key == keys.x then boxchar.bottomRight = not boxchar.bottomRight end
			end
		elseif evt[1] == "mouse_click" or evt[1] == "mouse_drag" then
			local button, mx, my = evt[2], evt[3], evt[4]
			if my >= scr_y-3 then
				if mx == 1 then
					if my == scr_y - 3 then boxchar.topLeft = not boxchar.topLeft end
					if my == scr_y - 2 then boxchar.left = not boxchar.left end
					if my == scr_y - 1 then boxchar.bottomLeft = not boxchar.bottomLeft end
				elseif mx == 2 then
					if my == scr_y - 3 then boxchar.topRight = not boxchar.topRight end
					if my == scr_y - 2 then boxchar.right = not boxchar.right end
					if my == scr_y - 1 then boxchar.bottomRight = not boxchar.bottomRight end
				elseif evt[1] == "mouse_click" then
					break
				end
			elseif evt[1] == "mouse_click" then
				break
			end
		end
	end
	if boxchar.topLeft and boxchar.topRight and boxchar.left and boxchar.right and boxchar.bottomLeft and boxchar.bottomRight then
		swapColors = false
		return " "
	else
		local output = getDrawingCharacter(boxchar.topLeft, boxchar.topRight, boxchar.left, boxchar.right, boxchar.bottomLeft, boxchar.bottomRight)
		swapColors = not output.inverted
		return output.char
	end
end

local specialCharSelector = function()
	local chars = {}
	local buff = 0
	for y = 1, 16 do
		for x = 1, 16 do
			chars[y] = chars[y] or {}
			chars[y][x] = string.char(buff)
			buff = buff + 1
		end
	end
	local sy = scr_y - (#chars + 1)
	local char = paint.c
	local render = function()
		for y = 1, #chars do
			for x = 1, #chars do
				term.setCursorPos(x,y+sy)
				if chars[y][x] == char then
					term.blit(chars[y][x], "5", "d")
				else
					term.blit(chars[y][x], "8", "7")
				end
			end
		end
	end
	local evt, butt, x, y
	render()

	term.setCursorPos(1,scr_y)
	term.setBackgroundColor(colors.lightGray)
	term.setTextColor(colors.black)
	term.clearLine()
	term.write("Press CTRL or 'N' when ready.")

	while true do
		term.setCursorPos(scr_x - 3, scr_y)
		term.setTextColor(colors.gray)
		term.write("\\" .. string.byte(char) .. " ")
		evt, butt, x, y = os.pullEvent()
		if (evt == "mouse_click" or evt == "mouse_drag") then
			if chars[y-sy] then
				if chars[y-sy][x] then
					if (chars[y-sy][x] ~= char) then
						char = chars[y-sy][x]
						render()
					end
				elseif evt == "mouse_click" then
					return char
				end
			elseif evt == "mouse_click" then
				return char
			end
		elseif evt == "key" then
			if (butt == keys.n) or (butt == keys.leftCtrl) then
				return char
			end
		end
	end
end

local dontDragThisTime = false
local resetInputState = function()
	miceDown = {}
	keysDown = {}
	isDragging = false
	dontDragThisTime = true
end

local gotoCoords = function()
	local newX = bottomPrompt("Goto X:")
	newX = tonumber(newX)
	local newY
		if newX then
		newY = bottomPrompt("Goto Y:")
		newY = tonumber(newY)
		paint.scrollX = newX or paint.scrollX
		paint.scrollY = newY or paint.scrollY
	end
end

local renderAllPAIN = function()
	renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
end

local checkIfNFP = function(str) --does not check table format, only string format
	local good = {
		['0'] = true,
		['1'] = true,
		['2'] = true,
		['3'] = true,
		['4'] = true,
		['5'] = true,
		['6'] = true,
		['7'] = true,
		['8'] = true,
		['9'] = true,
		a = true,
		b = true,
		c = true,
		d = true,
		e = true,
		f = true,
		[" "] = true,
		["\n"] = true
	}
	for a = 1, #str do
		if not good[str:sub(a,a):lower()] then
			return false
		end
	end
	return true
end

local selectRegion = function()
	local mevt, id, x1, y1 = os.pullEvent("mouse_click")
	local x2, y2, pos, redrawID
	local renderRectangle = true
	redrawID = os.startTimer(0.5)
	while true do
		mevt, id, x2, y2 = os.pullEvent()
		if mevt == "mouse_up" or mevt == "mouse_drag" or mevt == "mouse_click" then
			pos = {{
					x1 < x2 and x1 or x2,
					y1 < y2 and y1 or y2
				},{
					x1 < x2 and x2 or x1,
					y1 < y2 and y2 or y1
			}}
		end
		if mevt == "mouse_up" then
			break
		end
		if (mevt == "mouse_drag") or (mevt == "timer" and id == redrawID) then
			renderAllPAIN()
			if renderRectangle then
				term.setTextColor(rendback.t)
				term.setBackgroundColor(rendback.b)
				for y = pos[1][2], pos[2][2] do
					if y ~= scr_y then
						term.setCursorPos(pos[1][1], y)
						if (y == pos[1][2] or y == pos[2][2]) then
							term.write(("#"):rep(1 + pos[2][1] - pos[1][1]))
						else
							term.write("#")
							term.setCursorPos(pos[2][1], y)
							term.write("#")
						end
					end
				end
			end
		end
		if (mevt == "timer" and id == redrawID) then
			renderRectangle = not renderRectangle
			redrawID = os.startTimer(0.25)
		end
	end
	local output = {}
	pos[1][1] = pos[1][1] + paint.scrollX
	pos[2][1] = pos[2][1] + paint.scrollX
	pos[1][2] = pos[1][2] + paint.scrollY
	pos[2][2] = pos[2][2] + paint.scrollY
	for k,v in pairs(paintEncoded[frame]) do
		if v.x >= pos[1][1] and v.x <= pos[2][1] then
			if v.y >= pos[1][2] and v.y <= pos[2][2] then
				output[#output+1] = {
					x = v.x - pos[1][1],
					y = v.y - pos[1][2],
					t = v.t,
					c = v.c,
					b = v.b,
					m = v.m
				}
			end
		end
	end
	return output, pos[1][1], pos[1][2], pos[2][1], pos[2][2]
end

local openNewFile = function(fname, allowNonImageNFP)
	local file = fs.open(fname,"r")
	local contents = file.readAll()
	file.close()
	if type(textutils.unserialize(contents)) ~= "table" then
		term.setTextColor(colors.white)
		if contents:sub(1,3) == "BLT" then --thank you bomb bloke for this easy identifier
			if plc.pMode ~= 1 then print("Importing from BLT...") end
			return importFromBLT(fname), 3
		elseif contents:sub(1,3) == "GIF" then
			if plc.pMode ~= 1 then print("Importing from GIF, this'll take a while...") end
			return importFromGIF(fname,true), 5
		elseif contents:sub(1,4) == "?!7\2" then
			if plc.pMode ~= 1 then print("Importing from UCG...") end
			return {importFromUCG(fname)}, 6
		elseif contents:find(string.char(30)) and contents:find(string.char(31)) then
			if plc.pMode ~= 1 then print("Importing from NFT...") end
			return {importFromNFT(contents)}, 2
		elseif (checkIfNFP(contents) or allowNonImageNFP) then
			print("Importing from NFP...")
			return {importFromPaint(contents)}, 1
		else
			return false, "That is not a valid image file."
		end
	else
		return textutils.unserialize(contents), 4
	end
end

local editFuncs = {
	copy = function()
		local board = bottomPrompt("Copy to board: ")
		renderAllPAIN()
		renderBottomBar("Select region to copy.")
		local selectedDots = selectRegion()
		theClipboard[board] = selectedDots
		barmsg = "Copied to '"..board.."'"
		doRender = true
		keysDown = {}
		miceDown = {}
	end,
	cut = function()
		local board = bottomPrompt("Cut to board: ")
		renderAllPAIN()
		renderBottomBar("Select region to cut.")
		local selectedDots, x1, y1, x2, y2 = selectRegion()
		theClipboard[board] = selectedDots
		local dot
		for i = #paintEncoded[frame], 1, -1 do
		    dot = paintEncoded[frame][i]
		    if dot.x >= x1 and dot.x <= x2 then
		        if dot.y >= y1 and dot.y <= y2 then
		            table.remove(paintEncoded[frame], i)
		        end
		    end
		end
		barmsg = "Cut to '"..board.."'"
		doRender = true
		saveToUndoBuffer()
		keysDown = {}
		miceDown = {}
	end,
	paste = function()
		local board = bottomPrompt("Paste from board: ")
		renderAllPAIN()
		renderBottomBar("Click to paste. (top left corner)")
		if theClipboard[board] then
		    local mevt
		    repeat
		        mevt = {os.pullEvent()}
		    until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) <= scr_y-1)
		    for k,v in pairs(theClipboard[board]) do
		        paintEncoded[frame][#paintEncoded[frame]+1] = {
		            x = v.x + paint.scrollX + (mevt[3]),
		            y = v.y + paint.scrollY + (mevt[4]),
		            c = v.c,
		            t = v.t,
		            b = v.b,
		            m = v.m
		        }
		    end
		    paintEncoded[frame] = clearRedundant(paintEncoded[frame])
		    barmsg = "Pasted from '"..board.."'"
		    doRender = true
		    saveToUndoBuffer()
		    keysDown = {}
		    miceDown = {}
		else
		    barmsg = "No such clipboard."
		    doRender = true
		end
	end
}

local blockEnlargeFrame = function(frameNo)

	local frame = deepCopy(paintEncoded[frameNo])
	local charConvert = {
		["\129"] = {{true , false},{false, false},{false, false}},
		["\130"] = {{false, true },{false, false},{false, false}},
		["\131"] = {{true , true },{false, false},{false, false}},
		["\132"] = {{false, false},{true , false},{false, false}},
		["\133"] = {{true , false},{true , false},{false, false}},
		["\134"] = {{false, true },{true , false},{false, false}},
		["\135"] = {{true , true },{true , false},{false, false}},
		["\136"] = {{false, false},{false, true },{false, false}},
		["\137"] = {{true , false},{false, true },{false, false}},
		["\138"] = {{false, true },{false, true },{false, false}},
		["\139"] = {{true , true },{false, true },{false, false}},
		["\140"] = {{false, false},{true , true },{false, false}},
		["\141"] = {{true , false},{true , true },{false, false}},
		["\142"] = {{false, true },{true , true },{false, false}},
		["\143"] = {{true , true },{true , true },{false, false}},
		["\144"] = {{false, false},{false, false},{true , false}},
		["\145"] = {{true , false},{false, false},{true , false}},
		["\146"] = {{false, true },{false, false},{true , false}},
		["\147"] = {{true , true },{false, false},{true , false}},
		["\148"] = {{false, false},{true , false},{true , false}},
		["\149"] = {{true , false},{true , false},{true , false}},
		["\150"] = {{false, true },{true , false},{true , false}},
		["\151"] = {{true , true },{true , false},{true , false}},
		["\152"] = {{false, false},{false, true },{true , false}},
		["\153"] = {{true , false},{false, true },{true , false}},
		["\154"] = {{false, true },{false, true },{true , false}},
		["\155"] = {{true , true },{false, true },{true , false}},
		["\156"] = {{false, false},{true , true },{true , false}},
		["\157"] = {{true , false},{true , true },{true , false}},
		["\158"] = {{false, true },{true , true },{true , false}},
		["\159"] = {{true , true },{true , true },{true , false}},
	}
	local output, b = {}
	for k, dot in pairs(frame) do
		b = charConvert[dot.c] or {{false, false},{false, false},{false, false}}
		for y = 1, #b do
			for x = 1, #b[y] do
				output[#output+1] = {
					x = (dot.x - 1) * 2 + (x - 0),
					y = (dot.y - 1) * 3 + (y - 0),
					c = " ",
--					t = b[y][x] and dot.t or dot.b,
--					b = b[y][x] and dot.b or dot.t
					t = b[y][x] and dot.b or dot.t,
					b = b[y][x] and dot.t or dot.b
				}
			end
		end
	end
	barmsg = "Grew image."

	return output
end

local displayMenu = function()
	doRender = false
	menuOptions = {"File","Edit","Window","Set","About","Exit"}
	local diss = " "..tableconcat(menuOptions," ")
	local cleary = scr_y-math.floor(#diss/scr_x)

	local fileSave = function()
		checkBadDots()
		local output = deepCopy(paintEncoded)
		if paint.doGray then
			output = convertToGrayscale(output)
		end
		if not plc.fileName then
			renderBottomBar("Save as: ")
			local fnguess = read()
			if fs.isReadOnly(fnguess) then
				barmsg = "'"..fnguess.."' is read only."
				return false
			elseif fnguess:gsub(" ","") == "" then
				return false
			elseif fs.isDir(fnguess) then
				barmsg = "'"..fnguess.."' is already a directory."
				return false
			elseif #fnguess > 255 then
				barmsg = "Filename is too long."
				return false
			else
				plc.fileName = fnguess
			end
		end
		saveFile(plc.fileName,output)
		term.setCursorPos(9,scr_y)
		return plc.fileName
	end
	local filePrint = function()
		local usedDots, dot = {}, {}
		for a = 1, #paintEncoded[frame] do
			dot = paintEncoded[frame][a]
			if dot.x > paint.scrollX and dot.x < (paint.scrollX + 25) and dot.y > paint.scrollX and dot.y < (paint.scrollY + 21) then
				if dot.c ~= " " then
					usedDots[dot.t] = usedDots[dot.t] or {}
					usedDots[dot.t][#usedDots[dot.t]+1] = {
						x = dot.x - paint.scrollX,
						y = dot.y - paint.scrollY,
						char = dot.c
					}
				end
			end
		end
		local dyes = {
			[1] = "bonemeal",
			[2] = "orange dye",
			[4] = "magenta dye",
			[8] = "light blue dye",
			[16] = "dandelion yellow",
			[32] = "lime dye",
			[64] = "pink dye",
			[128] = "gray dye",
			[256] = "light gray dye",
			[512] = "cyan dye",
			[1024] = "purple dye",
			[2048] = "lapis lazuli",
			[4096] = "cocoa beans",
			[8192] = "cactus green",
			[16384] = "rose red",
			[32768] = "ink sac",
		}
		local printer = peripheral.find("printer")
		if not printer then
			barmsg = "No printer found."
			return false
		end
		local page
		for color, dotList in pairs(usedDots) do
			term.setBackgroundColor(colors.black)
			term.setTextColor((color == colors.black) and colors.gray or color)
			term.clear()
			cwrite("Please insert "..dyes[color].." into the printer.", nil, math.floor(scr_y/2))
			term.setTextColor(colors.lightGray)
			cwrite("Then, press spacebar.", nil, math.floor(scr_y/2) + 1)
			local evt
			sleep(0.05)
			repeat
				evt = {os.pullEvent("key")}
			until evt[2] == keys.space
			page = page or printer.newPage()
			if not page then
				barmsg = "Check ink/paper."
				return
			end
			for k,v in pairs(usedDots[color]) do
				printer.setCursorPos(v.x, v.y)
				printer.write(v.char)
			end
		end
		printer.endPage()
		barmsg = "Printed."
	end
	local fileExport = function(menuX,getRightToIt,_fileName)
		local exportMode
		if not tonumber(getRightToIt) then
			exportMode = makeSubMenu(menuX or 8,scr_y-2,{"Paint","NFT","BLT","PAIN Native","GIF","UCG"})
		else
			exportMode = getRightToIt
		end
		if exportMode == false then return false end
		local pe, exportName, writeIndent, result
		if exportMode == 4 then
			local exNm = fileSave()
			if exNm then
				plc.changedImage = false
				return exNm
			else
				return nil
			end
		else
			checkBadDots()
			if _fileName then
				exportName, writeIndent = _fileName, #_fileName
			else
				exportName, writeIndent = bottomPrompt("Export to: /")
			end
			nfpoutput = ""
			if fs.combine("",exportName) == "" then
				barmsg = "Export cancelled."
				return
			end
			if fs.isReadOnly(exportName) then
				barmsg = "That's read-only."
				return
			end
			if fs.exists(exportName) and (not _fileName) then
				local plea = (plc.progname == fs.combine("",exportName)) and "Overwrite ORIGINAL file!?" or "Overwrite?"
				result, _wIn = bottomPrompt(plea.." (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
				writeIndent = writeIndent + _wIn
				if result ~= "y" then return end
			end
			local output
			pe = deepCopy(paintEncoded)
			if paint.doGray then
				pe = convertToGrayscale(pe)
			end
			local doSerializeBLT = false
		end
		if exportMode == 1 then
			output = exportToPaint(pe[frame])
			if plc.askToSerialize then
				result, _wIn = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{})
				writeIndent = writeIndent + _wIn
			else result, _wIn = "n", 0 end
			if result == "y" then
				output = textutils.serialize(NFPserializeImage(output)):gsub(" ",""):gsub("\n",""):gsub(",}","}")
			end
		elseif exportMode == 2 then
			output = exportToNFT(pe[frame])
		elseif exportMode == 3 then
			local doAllFrames, _wIn = bottomPrompt("Save all frames, or current? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl},writeIndent)
			writeIndent = writeIndent + _wIn
			if plc.askToSerialize then
				doSerializeBLT = bottomPrompt("Save as serialized? (Y/N)",_,"yn",{},writeIndent) == "y"
			end
			output = textutils.serialise(exportToBLT(pe,exportName,doAllFrames == "y",doSerializeBLT))
		elseif exportMode == 5 then
			getGIF()
			GIF.saveGIF(exportToGIF(pe),exportName)
		elseif exportMode == 6 then
			exportToUCG(exportName,pe[frame])
		end
		if ((exportMode ~= 3) and (exportMode ~= 4) and (exportMode ~= 5) and (exportMode ~= 6)) or doSerializeBLT then
			local file = fs.open(exportName,"w")
			file.write(output)
			file.close()
		end
		return exportName
	end

	local editClear = function(ignorePrompt)
		local outcum = ignorePrompt and "y" or bottomPrompt("Clear the frame? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
		if outcum == "y" then
			paintEncoded[frame] = {}
			saveToUndoBuffer()
			barmsg = "Cleared frame "..frame.."."
		end
	end

	local editDelFrame = function()
		local outcum = bottomPrompt("Thou art sure? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
		if outcum == "y" then
			if #paintEncoded == 1 then
				return editClear(true)
			end
			table.remove(paintEncoded,frame)
			barmsg = "Deleted frame "..frame.."."
			if paintEncoded[frame-1] then
				frame = frame - 1
			else
				frame = frame + 1
			end
			if #paintEncoded < frame then
				repeat
					frame = frame - 1
				until #paintEncoded >= frame
			end
			saveToUndoBuffer()
		end
	end
	local editCrop = function()
		local outcum = bottomPrompt("Crop all but visible? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
		if outcum == "y" then
			local ppos = 1
			local deletedAmnt = 0
			for a = #paintEncoded[frame], 1, -1 do
				local x, y = paintEncoded[frame][a].x, paintEncoded[frame][a].y
				if (x <= paint.scrollX) or (x > paint.scrollX + scr_x) or (y <= paint.scrollY) or (y > paint.scrollY + scr_y) then
					table.remove(paintEncoded[frame],a)
					deletedAmnt = deletedAmnt + 1
				else
					ppos = ppos + 1
				end
				if ppos > #paintEncoded[frame] then break end
			end
			saveToUndoBuffer()
			barmsg = "Cropped frame."
		end
	end
	local editBoxCharSelector = function()
		paint.c = boxCharSelector()
	end
	local editSpecialCharSelector = function()
		paint.c = specialCharSelector()
	end

	local windowSetScrSize = function()
		local x,y
		x = bottomPrompt("Scr.X OR monitor name:",{},nil,{keys.leftCtrl,keys.rightCtrl})
		if x == "" then
			return
		elseif x == "pocket" then
			screenEdges = {26,20}
		elseif x == "turtle" then
			screenEdges = {39,13}
		elseif x == "computer" then
			screenEdges = {51,19}
		elseif tonumber(x) then
			if tonumber(x) <= 0 then
				barmsg = "Screen X must be greater than 0."
				return
			end
			screenEdges[1] = math.abs(tonumber(x))
			y = bottomPrompt("Scr.Y:",{},nil,{keys.leftCtrl,keys.rightCtrl})
			if tonumber(y) then
				if tonumber(y) <= 0 then
					barmsg = "Screen Y must be greater than 0."
					return
				end
				screenEdges[2] = math.abs(tonumber(y))
			end
			barmsg = "Screen size changed."
		else
			local mon = peripheral.wrap(x)
			if not mon then
				barmsg = "No such monitor."
				return
			else
				if peripheral.getType(x) ~= "monitor" then
					barmsg = "That's not a monitor."
					return
				else
					screenEdges[1], screenEdges[2] = mon.getSize()
					barmsg = "Screen size changed."
					return
				end
			end
		end
	end
	local aboutPAIN = function()
		local helpText = [[

      
         
       
           
         

Advanced Paint Program
 by LDDestroier
 or EldidiStroyrr
  if you please!

PAIN is a multi-frame paint program with the intention of becoming a stable, well-used, and mondo-useful CC drawing utility.

The main focus during development is to add many functions that you might see in real programs like MSPAINT, such as lines or a proper fill tool, as well as to export/import to and from as many image formats as possible.

My ultimate goal is to have PAIN be the default paint program for most every operating system on the forums (for what it's worth). In order to do this, I'll need to make sure that PAIN is stable, easy to use, and can be easily limited by an OS to work with more menial tasks like making a single icon or what have you.

I hope my PAIN brings you joy.
]]
		guiHelp(helpText)
	end
	local aboutFileFormats = function()
		local helpText = [[
Here's info on the file formats.

 "NFP":
Used in rom/programs/paint, and the format for paintutils. It's a handy format, but the default rendering function is inefficient as hell, and it does not store text data, only background.
Cannot save multiple frames.

 "NFT":
Used in npaintpro and most everything else, it's my favorite of the file formats because it does what NFP does, but allows for text in the pictures. Useful for storing screenshots or small icons where an added level of detail is handy. Created by nitrogenfingers, thank him.
Cannot save multiple frames.

 "BLT":
Used exclusively with Bomb Bloke's BLittle API, and as such is handy with making pictures with block characters. Just keep in mind that those 2*3 grid squares in PAIN represent individual characters in BLT.
BLT can save multiple frames!

 "PAIN Native":
The basic, tabular, and wholly inefficient format that PAIN uses. Useful for doing math within the program, not so much for long term file storage. It stores text, but just use NFT if you don't need multiple frames.
Obviously, this can save multiple frames.

 "GIF":
The API was made by Bomb Bloke, huge thanks for that, but GIF is a universal file format used in real paint programs. Very useful for converting files on your computer to something like NFP, but doesn't store text. Be careful when opening up big GIF files, they can take a long time to load.
Being GIF, this saves multiple frames!

 "UCG":
Stands for Universal Compressed Graphics. This format was made by ardera, and uses Huffman Code and run-length encoding in order to reduce file sizes tremendously. However, it only saves backgrounds and not text data.
Cannot save multiple frames.


I recommend using NFT if you don't need multiple frames, NFP if you don't need text, UCG if the picture is really big, Native PAIN if you need both text and multiframe support, and GIF if you want to use something like MS Paint or Pinta or GIMP or whatever.
]]
		guiHelp(helpText)
	end
	local menuPoses = {}
	local menuFunctions = {
		[1] = function() --File
			while true do
				local output, longestLen = makeSubMenu(1,cleary-1,{
					"Save",
					"Save As",
					"Export",
					"Open",
					((peripheral.find("printer")) and "Print" or nil)
				})
				if output == 1 then -- Save
					local _fname = fileExport(_,plc.defaultSaveFormat,plc.fileName)
					if _fname then
						barmsg = "Saved as '".._fname.."'"
						lastPaintEncoded = deepCopy(paintEncoded)
						plc.changedImage = false
					end
					break
				elseif output == 2 then -- Save As
					local oldfilename = plc.fileName
					plc.fileName = nil
					local res = fileExport(_,plc.defaultSaveFormat)
					if not res then
						plc.fileName = oldfilename
					else
						barmsg = "Saved as '"..plc.fileName.."'"
					end
					break
				elseif output == 3 then --Export
					local res = fileExport(longestLen+1)
					if res then
						barmsg = "Exported as '"..res.."'"
					end
					break
				elseif output == 4 then -- Open
					renderBottomBar("Pick an image file.")
					local newPath = lddfm.makeMenu(2, 2, scr_x-1, scr_y-2, fs.getDir(plc.fileName or plc.progname), false, false, false, true, false, nil, true)
					if newPath then
						local pen, form = openNewFile(newPath, painconfig.readNonImageAsNFP)
						if not pen then
							barmsg = form
						else
							plc.fileName = newPath
							paintEncoded, lastPaintEncoded = pen, deepCopy(pen)
							plc.defaultSaveFormat = form
							plc.undoPos = 1
							plc.undoBuffer = {deepCopy(paintEncoded)}
							barmsg = "Opened '" .. fs.getName(newPath) .. "'"
							paint.scrollX, paint.scrollY, paint.doGray = 1, 1, false
						end
					end
					break
				elseif output == 5 then -- Print
					filePrint()
					break
				elseif output == false then
					return "nobreak"
				end
				reRenderPAIN(true)
			end
		end,
		[2] = function() --Edit
			local output = makeSubMenu(6,cleary-1,{
				"Delete Frame",
				"Clear Frame",
				"Crop Frame",
				"Choose Box Character",
				"Choose Special Character",
				"BLittle Shrink",
				"BLittle Grow",
				"Copy Region",
				"Cut Region",
				"Paste Region"
			})
			if output == 1 then
				editDelFrame()
			elseif output == 2 then
				editClear()
			elseif output == 3 then
				editCrop()
			elseif output == 4 then
				editBoxCharSelector()
			elseif output == 5 then
				editSpecialCharSelector()
			elseif output == 6 then
				local res = bottomPrompt("You sure? It's lossy! (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
				if res == "y" then
					getBlittle()
					local bltPE = blittle.shrink(NFPserializeImage(exportToPaint(paintEncoded[frame])))
					_G.SHRINKOUT = bltPE
					paintEncoded[frame] = {}
					for y = 1, bltPE.height do
						for x = 1, bltPE.width do
							paintEncoded[frame][#paintEncoded[frame]+1] = {
								c = bltPE[1][y]:sub(x,x),
								t = BTC(bltPE[2][y]:sub(x,x),true),
								b = BTC(bltPE[3][y]:sub(x,x),true),
								x = x,
								y = y,
							}
						end
					end
					saveToUndoBuffer()
					barmsg = "Shrunk image."
				end
			elseif output == 7 then
				paintEncoded[frame] = blockEnlargeFrame(frame)
			elseif output == 8 then
				editFuncs.copy()
			elseif output == 9 then
				editFuncs.cut()
			elseif output == 10 then
				editFuncs.paste()
			elseif output == false then
				return "nobreak"
			end
		end,
		[3] = function() --Window
			local output = makeSubMenu(11,cleary-1,{
				"Set Screen Size",
				"Set Scroll XY",
				"Set Grid Colors"
			})
			if output == 1 then
				windowSetScrSize()
			elseif output == 2 then
				gotoCoords()
			elseif output == 3 then
				rendback.b = paint.b
				rendback.t = paint.t
			elseif output == false then
				return "nobreak"
			end
		end,
		[4] = function() --Set
			local output = makeSubMenu(17,cleary-1,{
				(painconfig.readNonImageAsNFP 	and "(T)" or "(F)") .. " Load Non-images",
				(painconfig.useFlattenGIF 		and "(T)" or "(F)") .. " Flatten GIFs",
				(painconfig.gridBleedThrough 	and "(T)" or "(F)") .. " Always Render Grid",
				(painconfig.doFillDiagonal 		and "(T)" or "(F)") .. " Fill Diagonally",
				(painconfig.doFillAnimation 	and "(T)" or "(F)") .. " Do Fill Animation",
				(painconfig.useSetVisible		and "(T)" or "(F)") .. " Use setVisible()",
				"(" .. painconfig.undoBufferSize .. ") Set Undo Buffer Size",
			})
			if output == 1 then
				painconfig.readNonImageAsNFP = not painconfig.readNonImageAsNFP
			elseif output == 2 then
				painconfig.useFlattenGIF = not painconfig.useFlattenGIF
			elseif output == 3 then
				painconfig.gridBleedThrough = not painconfig.gridBleedThrough
			elseif output == 4 then
				painconfig.doFillDiagonal = not painconfig.doFillDiagonal
			elseif output == 5 then
				painconfig.doFillAnimation = not painconfig.doFillAnimation
			elseif output == 6 then
				painconfig.useSetVisible = not painconfig.useSetVisible
			elseif output == 7 then
				local newUndoBufferSize = bottomPrompt("New undo buffer size: ")
				if tonumber(newUndoBufferSize) then
					painconfig.undoBufferSize = math.abs(tonumber(newUndoBufferSize))
					plc.undoBuffer = {deepCopy(paintEncoded)}
					plc.undoPos = 1
				else
					return
				end
			end
			useConfig("save")
		end,
		[5] = function() --About
			local output = makeSubMenu(17,cleary-1,{
				"PAIN",
				"File Formats",
				"Help!"
			})
			if output == 1 then
				aboutPAIN()
			elseif output == 2 then
				aboutFileFormats()
			elseif output == 3 then
				guiHelp()
			end
		end,
		[6] = function() --Exit
			if plc.changedImage then
				local outcum = bottomPrompt("Abandon unsaved work? (Y/N)",_,"yn",{keys.leftCtrl,keys.rightCtrl})
				sleep(0.05)
				if outcum == "y" then
					return "exit"
				else
					return nil
				end
			else
				return "exit"
			end
		end,
	}
	local cursor = 1
	local redrawmenu = true
	local initial = os.time()
	local clickdelay = 0.003

	local redrawTheMenu = function()
		for a = cleary,scr_y do
			term.setCursorPos(1,a)
			term.setBackgroundColor(colors.lightGray)
			term.clearLine()
		end
		term.setCursorPos(2,cleary)
		for a = 1, #menuOptions do
			if a == cursor then
				term.setTextColor(colors.black)
				term.setBackgroundColor(colors.white)
			else
				term.setTextColor(colors.black)
				term.setBackgroundColor(colors.lightGray)
			end
			menuPoses[a] = {term.getCursorPos()}
			write(menuOptions[a])
			term.setBackgroundColor(colors.lightGray)
			if a ~= #menuOptions then
				write(" ")
			end
		end
		redrawmenu = false
	end

	while true do
		if redrawmenu then
			redrawTheMenu()
			redrawmenu = false
		end
		local event,key,x,y = getEvents("key","char","mouse_click","mouse_up","mouse_drag")
		if event == "key" then
			if key == keys.left then
				redrawmenu = true
				cursor = cursor - 1
			elseif key == keys.right then
				redrawmenu = true
				cursor = cursor + 1
			elseif key == keys.enter then
				redrawmenu = true
				local res = menuFunctions[cursor]()
				resetInputState()
				if res == "exit" then
					doRender = true
					return "exit"
				elseif res == "nobreak" then
					reRenderPAIN(true)
				else
					doRender = true
					return
				end
			elseif key == keys.leftCtrl or key == keys.rightCtrl then
				doRender = true
				return
			end
		elseif event == "char" then
			for a = 1, #menuOptions do
				if key:lower() == menuOptions[a]:sub(1,1):lower() and a ~= cursor then
					cursor = a
					redrawmenu = true
					break
				end
			end
		elseif event == "mouse_click" or event == "mouse_up" then
			if y < cleary then
				resetInputState()
				doRender = true
				return
			elseif key == 1 and initial+clickdelay < os.time() then --key? more like button
				for a = 1, #menuPoses do
					if y == menuPoses[a][2] then
						if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
							cursor = a
							redrawTheMenu()
							local res = menuFunctions[a]()
							os.queueEvent("queue")
							os.pullEvent("queue")
							resetInputState()
							doRender = true
							if res == "exit" then
								return "exit"
							else
								return
							end
						end
					end
				end
			end
		end
		if (initial+clickdelay < os.time()) and string.find(event,"mouse") then
			if key == 1 then --key? key? what key? all I see is button!
				for a = 1, #menuPoses do
					if y == menuPoses[a][2] then
						if x >= menuPoses[a][1] and x <= menuPoses[a][1]+#menuOptions[a] then
							cursor = a
							redrawmenu = true
							break
						end
					end
				end
			end
		end
		if cursor < 1 then
			cursor = #menuOptions
		elseif cursor > #menuOptions then
			cursor = 1
		end
	end
end

local lastMX,lastMY,isDragging

local doNonEventDrivenMovement = function() --what a STUPID function name, dude
	local didMove
	while true do
		didMove = false
		if (not keysDown[keys.leftShift]) and (not isDragging) and (not keysDown[keys.tab]) then
			if keysDown[keys.right] then
				paint.scrollX = paint.scrollX + 1
				didMove = true
			elseif keysDown[keys.left] then
				paint.scrollX = paint.scrollX - 1
				didMove = true
			end
			if keysDown[keys.down] then
				paint.scrollY = paint.scrollY + 1
				didMove = true
			elseif keysDown[keys.up] then
				paint.scrollY = paint.scrollY - 1
				didMove = true
			end
			if didMove then
				if lastMX and lastMY then
					if miceDown[1] then
						os.queueEvent("mouse_click",1,lastMX,lastMY)
					end
					if miceDown[2] then
						os.queueEvent("mouse_click",2,lastMX,lastMY)
					end
				end
				doRender = true
			end
		end
		sleep(0.05)
	end
end

local linePoses = {}
local dragPoses = {}

local listAllMonitors = function()
	term.setBackgroundColor(colors.gray)
	term.setTextColor(colors.white)
	local periphs = peripheral.getNames()
	local mons = {}
	for a = 1, #periphs do
		if peripheral.getType(periphs[a]) == "monitor" then
			mons[#mons+1] = periphs[a]
		end
	end
	if #mons == 0 then
		mons[1] = "No monitors found."
	end
	term.setCursorPos(3,1)
	term.clearLine()
	term.setTextColor(colors.yellow)
	term.write("All monitors:")
	term.setTextColor(colors.white)
	for y = 1, #mons do
		term.setCursorPos(2,y+1)
		term.clearLine()
		term.write(mons[y])
	end
	sleep(0.05)
	getEvents("char","mouse_click")
	doRender = true
end

local getInput = function() --gotta catch them all
	local drawEveryEvent = false
	local doot = function()
		local button, x, y, oldmx, oldmy, origx, origy
		local isDragging = false
		local proceed = false
		local evt, oldx, oldy = {}
		local button, points, key, dir
		renderBar(barmsg)
		while true do
			evt = {getEvents("mouse_scroll","mouse_click", "mouse_drag","mouse_up","key","key_up",true)}

			--doRender = false
			oldx, oldy = paint.scrollX,paint.scrollY

			if (evt[1] == "mouse_scroll") and (not viewing) then
				dir = evt[2]
				if dir == 1 then
					if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
						paint.t = paint.t * 2
						if paint.t > 32768 then
							paint.t = 32768
						end
					else
						paint.b = paint.b * 2
						if paint.b > 32768 then
							paint.b = 32768
						end
					end
				else
					if keysDown[keys.leftShift] or keysDown[keys.rightShift] then
						paint.t = math.ceil(paint.t / 2)
						if paint.t < 1 then
							paint.t = 1
						end
					else
						paint.b = math.ceil(paint.b / 2)
						if paint.b < 1 then
							paint.b = 1
						end
					end
				end
				renderBar(barmsg)
			elseif ((evt[1] == "mouse_click") or (evt[1] == "mouse_drag")) and (not viewing) then
				if evt[1] == "mouse_click" then
					origx, origy = evt[3], evt[4]
				end
				oldmx,oldmy = x or evt[3], y or evt[4]
				lastMX,lastMY = evt[3],evt[4]
				button,x,y = evt[2],evt[3],evt[4]
				if plc.renderBlittle then
					x = 2*x
					y = 3*y
					lastMX = 2*lastMX
					lastMY = 3*lastMY
				end
				linePoses = {{x=oldmx,y=oldmy},{x=x,y=y}}
				miceDown[button] = true
				if y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
					if (button == 3) then
						renderBottomBar("Type text onto canvas:")
						putDownText(x,y)
						miceDown = {}
						keysDown = {}
						doRender = true
					elseif button == 1 then
						if keysDown[keys.leftShift] and evt[1] == "mouse_click" then
							isDragging = true
						end
						if isDragging then
							if evt[1] == "mouse_click" or dontDragThisTime then
								dragPoses[1] = {x=x,y=y}
							end
							dragPoses[2] = {x=x,y=y}
							points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
							renderAllPAIN()
							for a = 1, #points do
								term.setCursorPos(points[a].x, points[a].y)
								term.blit(paint.c, CTB(paint.t), CTB(paint.b))
							end
						elseif (not dontDragThisTime) then
							if evt[1] == "mouse_drag" then
								points = getDotsInLine(linePoses[1].x,linePoses[1].y,linePoses[2].x,linePoses[2].y)
								for a = 1, #points do
									putDotDown({x=points[a].x, y=points[a].y})
								end
							else
								putDotDown({x=x, y=y})
							end
							plc.changedImage = true
							doRender = true
						end
						dontDragThisTime = false
					elseif button == 2 and y <= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
						deleteDot(x+paint.scrollX,y+paint.scrollY)
						plc.changedImage = true
						doRender = true
					end
				elseif origy >= scr_y-(plc.renderBlittle and 0 or doRenderBar) then
					miceDown = {}
					keysDown = {}
					isDragging = false
					local res = displayMenu()
					if res == "exit" then break end
					doRender = true
				end
			elseif (evt[1] == "mouse_up") and (not viewing) and (not plc.isCurrentlyFilling) then
				origx,origy = 0,0
				button = evt[2]
				miceDown[button] = false
				oldmx,oldmy = nil,nil
				lastMX, lastMY = nil,nil
				if isDragging then
					points = getDotsInLine(dragPoses[1].x,dragPoses[1].y,dragPoses[2].x,dragPoses[2].y)
					for a = 1, #points do
						putDotDown({x=points[a].x, y=points[a].y})
					end
					plc.changedImage = true
					doRender = true
				end
				saveToUndoBuffer()
				isDragging = false
			elseif evt[1] == "key" then
				key = evt[2]
				if (isDragging or not keysDown[keys.leftShift]) and (keysDown[keys.tab]) then
					if key == keys.right and (not keysDown[keys.right]) then
						paint.scrollX = paint.scrollX + 1
						doRender = true
					elseif key == keys.left and (not keysDown[keys.left]) then
						paint.scrollX = paint.scrollX - 1
						doRender = true
					end
					if key == keys.down and (not keysDown[keys.down]) then
						paint.scrollY = paint.scrollY + 1
						doRender = true
					elseif key == keys.up and (not keysDown[keys.up]) then
						paint.scrollY = paint.scrollY - 1
						doRender = true
					end
				end
				keysDown[key] = true
				if key == keys.space then
					if keysDown[keys.leftShift] then
						plc.evenDrawGrid = not plc.evenDrawGrid
					else
						doRenderBar = math.abs(doRenderBar-1)
					end
					doRender = true
				end
				if key == keys.b then
					local blTerm, oldTerm = getBlittle()
					plc.renderBlittle = not plc.renderBlittle
					isDragging = false
					term.setBackgroundColor(rendback.b)
					term.clear()
					if plc.renderBlittle then
						term.redirect(blTerm)
						blTerm.setVisible(true)
					else
						term.redirect(oldTerm)
						blTerm.setVisible(false)
					end
					doRender = true
					scr_x, scr_y = term.current().getSize()
				end
				if keysDown[keys.leftAlt] then
					if (not plc.renderBlittle) then
						if (key == keys.c) then
							editFuncs.copy()
						elseif (key == keys.x) then
							editFuncs.cut()
						elseif (key == keys.v) then
							editFuncs.paste()
						end
					end
				else
					if (key == keys.c) and (not plc.renderBlittle) then
						gotoCoords()
						resetInputState()
						doRender = true
					end
				end
				if (keysDown[keys.leftShift]) and (not isDragging) then
					if key == keys.left then
						paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],-1,0)
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					elseif key == keys.right then
						paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],1,0)
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					elseif key == keys.up then
						paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,-1)
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					elseif key == keys.down then
						paintEncoded[frame] = movePaintEncoded(paintEncoded[frame],0,1)
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					end
				end
				if keysDown[keys.leftAlt] then
					if #paintEncoded > 1 then
						if key == keys.equals and paintEncoded[frame+1] then --basically plus
							local first = deepCopy(paintEncoded[frame])
							local next = deepCopy(paintEncoded[frame+1])
							paintEncoded[frame] = next
							paintEncoded[frame+1] = first
							frame = frame + 1
							barmsg = "Swapped prev frame."
							doRender = true
							plc.changedImage = true
							saveToUndoBuffer()
						end
						if key == keys.minus and paintEncoded[frame-1] then
							local first = deepCopy(paintEncoded[frame])
							local next = deepCopy(paintEncoded[frame-1])
							paintEncoded[frame] = next
							paintEncoded[frame-1] = first
							frame = frame - 1
							barmsg = "Swapped next frame."
							doRender = true
							plc.changedImage = true
							saveToUndoBuffer()
						end
					end
				elseif keysDown[keys.leftShift] then
					if #paintEncoded > 1 then
						if key == keys.equals and paintEncoded[frame+1] then --basically plus
							for a = 1, #paintEncoded[frame] do
								paintEncoded[frame+1][#paintEncoded[frame+1] + 1] = paintEncoded[frame][a]
							end
							table.remove(paintEncoded, frame)
							paintEncoded = clearAllRedundant(paintEncoded)
							barmsg = "Merged next frame."
							doRender = true
							plc.changedImage = true
							saveToUndoBuffer()
						end
						if key == keys.minus and paintEncoded[frame-1] then
							for a = 1, #paintEncoded[frame] do
								paintEncoded[frame-1][#paintEncoded[frame-1] + 1] = paintEncoded[frame][a]
							end
							table.remove(paintEncoded, frame)
							frame = frame - 1
							paintEncoded = clearAllRedundant(paintEncoded)
							barmsg = "Merged previous frame."
							doRender = true
							plc.changedImage = true
							saveToUndoBuffer()
						end
					end
				else
					if key == keys.equals then --basically 'plus'
						if plc.renderBlittle then
							frame = frame + 1
							if frame > #paintEncoded then frame = 1 end
						else
							if not paintEncoded[frame+1] then
								paintEncoded[frame+1] = {}
								local sheet = paintEncoded[frame]
								if keysDown[keys.rightShift] then
									paintEncoded[frame+1] = deepCopy(sheet)
								end
							end
							frame = frame + 1
						end
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					elseif key == keys.minus then
						if plc.renderBlittle then
							frame = frame - 1
							if frame < 1 then frame = #paintEncoded end
						else
							if frame > 1 then
								frame = frame - 1
							end
						end
						saveToUndoBuffer()
						doRender = true
						plc.changedImage = true
					end
				end
				if not plc.renderBlittle then
					if key == keys.m then
						local incum = bottomPrompt("Set meta: ",metaHistory)
						paint.m = incum:gsub(" ","") ~= "" and incum or paint.m
						if paint.m ~= metaHistory[#metaHistory] then
							metaHistory[#metaHistory+1] = paint.m
						end
						doRender = true
						isDragging = false
					end
					if key == keys.f7 then
						plc.bepimode = not plc.bepimode
						doRender = true
					end
					if key == keys.t then
						renderBottomBar("Click to place text.")
						local mevt
						repeat
							mevt = {os.pullEvent()}
						until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] == 1 and (mevt[4] or scr_y) <= scr_y-(plc.renderBlittle and 0 or doRenderBar))
						if not (mevt[1] == "key" and mevt[2] == keys.x) then
							local x,y = mevt[3],mevt[4]
							if plc.renderBlittle then
								x = 2*x
								y = 3*y
							end
							renderBottomBar("Type text onto canvas:")
							putDownText(x,y)
							miceDown = {}
							keysDown = {}
						end
						doRender = true
						plc.changedImage = true
						isDragging = false
					end
					if key == keys.f and not (keysDown[keys.leftShift] or keysDown[keys.rightShift]) and (not plc.isCurrentlyFilling) then
						renderBottomBar("Click to fill area.")
						local mevt
						repeat
							mevt = {os.pullEvent()}
						until (mevt[1] == "key" and mevt[2] == keys.x) or (mevt[1] == "mouse_click" and mevt[2] <= 2 and (mevt[4] or scr_y) <= scr_y-(renderBlittle and 0 or doRenderBar))
						if not (mevt[1] == "key" and mevt[2] == keys.x) then
							local x,y = mevt[3],mevt[4]
							if plc.renderBlittle then
								x = 2*x
								y = 3*y
							end
							miceDown = {}
							keysDown = {}
							os.queueEvent("filltool_async", frame, x, y, paint, mevt[2] == 2)
						end
						doRender = true
						plc.changedImage = true
						isDragging = false
					end
					if key == keys.p then
						renderBottomBar("Pick color with cursor:")
						paintEncoded = clearAllRedundant(paintEncoded)
						local mevt
						local keepPicking = true
						
						while keepPicking do
							mevt = {os.pullEvent()} 
							if mevt[1] == "mouse_click" then
								keepPicking = false
								resetInputState()
								if mevt[4] < scr_y then
									local x, y = mevt[3]+paint.scrollX, mevt[4]+paint.scrollY
									if plc.renderBlittle then
										x = 2*x
										y = 3*y
									end
									local p
									for a = 1, #paintEncoded[frame] do
										p = paintEncoded[frame][a]
										if (p.x == x) and (p.y == y) then
											paint.t = p.t or paint.t
											paint.b = p.b or paint.b
											paint.c = p.c or paint.c
											paint.m = p.m or paint.m
											break
										end
									end
								end
							elseif mevt[1] == "key" then
								if mevt[2] == keys.x or evt[2] == keys.q then
									keepPicking = false
								end
							end
						end
						doRender = true
						isDragging = false
					end
					if (key == keys.leftCtrl or key == keys.rightCtrl) then
						keysDown = {[207] = keysDown[207]}
						isDragging = false
						local res = displayMenu()
						paintEncoded = clearAllRedundant(paintEncoded)
						if res == "exit" then break end
						doRender = true
					end
				end
				if (key == keys.f and keysDown[keys.leftShift]) then
					local deredots = {}
					plc.changedImage = true
					for a = 1, #paintEncoded[frame] do
						local dot = paintEncoded[frame][a]
						if dot.x-paint.scrollX > 0 and dot.x-paint.scrollX <= scr_x then
							if dot.y-paint.scrollY > 0 and dot.y-paint.scrollY <= scr_y then
								deredots[#deredots+1] = {dot.x-paint.scrollX, dot.y-paint.scrollY}
							end
						end
					end
					for y = 1, scr_y do
						for x = 1, scr_x do
							local good = true
							for a = 1, #deredots do
								if (deredots[a][1] == x) and (deredots[a][2] == y) then
									good = bad
									break
								end
							end
							if good then
								putDotDown({x=x, y=y})
							end
						end
					end
					saveToUndoBuffer()
					doRender = true
				end
				if key == keys.g then
					paint.doGray = not paint.doGray
					plc.changedImage = true
					saveToUndoBuffer()
					doRender = true
				end
				if key == keys.a then
					paint.scrollX = 0
					paint.scrollY = 0
					doRender = true
				end
				if key == keys.n then
					if keysDown[keys.leftShift] then
						paint.c = specialCharSelector()
					else
						paint.c = boxCharSelector()
					end
					resetInputState()
					doRender = true
				end
				if key == keys.f1 then
					guiHelp()
					resetInputState()
					isDragging = false
				end
				if key == keys.f3 then
					listAllMonitors()
					resetInputState()
					isDragging = false
				end
				if key == keys.leftBracket then
					os.queueEvent("mouse_scroll",2,1,1)
				elseif key == keys.rightBracket then
					os.queueEvent("mouse_scroll",1,1,1)
				end
				if key == keys.z then
					if keysDown[keys.leftAlt] and plc.undoPos < #plc.undoBuffer then
						doRedo()
						barmsg = "Redood."
						doRender = true
					elseif plc.undoPos > 1 then
						doUndo()
						barmsg = "Undood."
						doRender = true
					end
				end
			elseif evt[1] == "key_up" then
				local key = evt[2]
				keysDown[key] = false
			end
			if (oldx~=paint.scrollX) or (oldy~=paint.scrollY) then
				doRender = true
			end
			if drawEveryEvent and doRender then
				renderAllPAIN()
				doRender = false
			end
		end
	end
	if drawEveryEvent then
		doot()
	else
		parallel.waitForAny(doot, function()
			while true do
				sleep(0.05)
				if doRender then
					renderAllPAIN()
					doRender = false
				end
			end
		end)
	end
end

runPainEditor = function(...) --needs to be cleaned up
	local tArg = table.pack(...)
	if not (tArg[1] == "-n" or (not tArg[1])) then
		plc.fileName = shell.resolve(tostring(tArg[1]))
	end

	if not plc.fileName then
		paintEncoded = {{}}
	elseif not fs.exists(plc.fileName) then
		local ex = plc.fileName:sub(-4):lower()
		if ex == ".nfp" then
			plc.defaultSaveFormat = 1
		elseif ex == ".nft" then
			plc.defaultSaveFormat = 2
		elseif ex == ".blt" then
			plc.defaultSaveFormat = 3
		elseif ex == ".gif" then
			plc.defaultSaveFormat = 5
		elseif ex == ".ucg" then
			plc.defaultSaveFormat = 6
		else
			plc.defaultSaveFormat = 4
		end
		paintEncoded = {{}}
	elseif fs.isDir(plc.fileName) then
		if math.random(1,32) == 1 then
			write("Oh") sleep(0.2)
			write(" My") sleep(0.2)
			print(" God") sleep(0.3)
			write("That is a") sleep(0.1) term.setTextColor(colors.red)
			write(" FLIPPING") sleep(0.4)
			print(" FOLDER.") sleep(0.2) term.setTextColor(colors.white)
			print("You crazy person.") sleep(0.2)
		else
			print("That's a folder.")
		end
		return
	else
		paintEncoded, plc.defaultSaveFormat = openNewFile(plc.fileName, readNonImageAsNFP)
		if not paintEncoded then
			return print(plc.defaultSaveFormat)
		end
	end

	local asyncFillTool = function()
		local event, frameNo, x, y, dot
		plc.isCurrentlyFilling = false
		while true do
			event, frameNo, x, y, dot, isDeleting = os.pullEvent("filltool_async")
			plc.isCurrentlyFilling = true
			renderBottomBar("Filling area...")
			fillTool(frameNo, x, y, dot, isDeleting)
			saveToUndoBuffer()
			plc.isCurrentlyFilling = false
			reRenderPAIN(doRenderBar == 0)
		end
	end

	if not paintEncoded[frame] then paintEncoded = {paintEncoded} end
	if plc.pMode == 1 then
		doRenderBar = 0
		renderPAIN(paintEncoded[tonumber(tArg[5]) or 1],-(tonumber(tArg[3]) or 0),-(tonumber(tArg[4]) or 0)) -- 'pain filename view X Y frame'
		sleep(0.05)
		return
	else
		renderPAIN(paintEncoded[frame],paint.scrollX,paint.scrollY,true)
	end
	lastPaintEncoded = deepCopy(paintEncoded)
	plc.undoBuffer = {deepCopy(paintEncoded)}
	parallel.waitForAny(getInput, doNonEventDrivenMovement, asyncFillTool)

	term.setCursorPos(1,scr_y)
	term.setBackgroundColor(colors.black)
	term.clearLine()
end

if not shell then error("shell API is required, sorry") end

runPainEditor(table.unpack(plc.tArg))
