--[[
 PROGDOR file bundling program

Download with:
 pastebin get YXx5jjMV progdor
 std ld progdor progdor

This is a stable release. You fool!
--]]

local doCompress = false --even if this is false, it will decompress compressed files. nifty, huh?

local doPastebin = false
local tArg = {...}
local input, outpath
if tArg[1] == "-p" then --the p is for pastebin
	doPastebin = true
	input = tArg[2]
	outpath = tArg[3]
else
	input = tArg[1]
	outpath = tArg[2]
end

local progdor = fs.getName(shell.getRunningProgram())
local dir = shell.dir()
local displayHelp = function()
	local txt = progdor.." <input> [output]\nCompression is "..tostring(doCompress):upper().."."
	return print(txt)
end

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

-- CCA API START --

local bit = bit32
local function pack(bn1, bn2)
	return bit.band(bn1, 0xFF), bit.rshift(bn1, 8) + bit.lshift(bit.band(bn2, 0xF), 4), bit.rshift(bn2, 4)
end
local function upack(b1, b2, b3)
	return (b1 + bit.lshift(bit.band(b2, 0xF), 8)), (bit.lshift(b3,4) + bit.band(bit.rshift(b2, 4), 0xF))
end
local function createDict(bool)
	local ret = {}
	for i = 1, 255 do
		if bool then
			ret[string.char(i)] = i
		else
			ret[i] = string.char(i)
		end
	end
	if not bool then ret[256] = 256 end
	return ret
end
local function cp(sInput)
	local dic = createDict(true)
	local s = ""
	local ch
	local dlen = 256
	local result = {}
	local temp
	for i = 1, #sInput do
		if dlen == 4095 then
			result[#result + 1] = dic[s]
			result[#result + 1] = 256
			dic = createDict(true)
			dlen = 256
			s = ""
		end
		ch = sInput:sub(i, i)
		temp = s..ch
		if dic[temp] then
			s = temp
		else
			result[#result + 1] = dic[s]
			dlen = dlen	+1
			dic[temp] = dlen		
			s = ch
		end
	end
	result[#result + 1] = dic[s]
	
	return result
end
local function dc(data)
	local dic = createDict(false)	
	local entry
	local ch
	local currCode
	local result = {}
	result[#result + 1] = dic[data[1]]
	prefix = dic[data[1]]
	for i = 2, #data do
		currCode = data[i]
		if currCode == 256 then
			dic = createDict(false)
			prefix = ""
		else
			entry = dic[currCode]
			if entry then--exists in dictionary
				ch = entry:sub(1, 1)		
				result[#result + 1] = entry
				if prefix ~= "" then
					dic[#dic+1] = prefix .. ch
				end
			else	
				ch = prefix:sub(1, 1)
				result[#result + 1] = prefix..ch
				dic[#dic + 1] = prefix..ch
			end
		
			prefix = dic[currCode]
		end
	end
	
	return table.concat(result)
end
local function trim(inp)
	for i = 0,2 do
		if inp[#inp] == 0 then
			inp[#inp] = nil
		end
	end
end
local function decompress(input)
	local rec = {}
	for i = 1, #input, 3 do
		if i % 66 == 0 then
			yield()
		end
		rec[#rec+1], rec[#rec+2] = upack(input[i], input[i+1] or 0, input[i+2] or 0)
	end
	trim(rec)
	return dc(rec)
end
local function compress(input)
	local rec = {}
	local data = cp(input)
	for i=1, #data, 2 do
		yield()
		rec[#rec+1], rec[#rec+2], rec[#rec+3] = pack(data[i], data[i+1] or 0)
	end
	trim(rec)
	return rec
end

-- CCA API END --

local fixstr = function(str)
	return str:gsub("\\(%d%d%d)",string.char)
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
        table.insert(arr,str:sub(pos,st-1))
        pos = sp + 1
    end
    table.insert(arr,str:sub(pos))
    return arr
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 tablize = function(input)
	if type(input) == "string" then
		return explode("\n",input)
	elseif type(input) == "table" then
		return table.concat(input,"\n")
	end
end
local compyress = function(input)
	return string.char(unpack(compress(input)))
end
local decompyress = function(input)
	local out = {}
	for a = 1, #input do
		table.insert(out,string.byte(input:sub(a,a)))
	end
	return decompress(out)
end
local listAll
listAll = function(_path, _files, noredundant)
	local path = _path or ""
	local files = _files or {}
	if #path > 1 then table.insert(files, path) end
	for _, file in ipairs(fs.list(path)) do
		local path = fs.combine(path, file)
		if (file ~= thisProgram) then
			local guud = true
			if guud then
				if fs.isDir(path) then
					listAll(path, files, noredundant)
				else
					table.insert(files, path)
				end
			end
		end
	end
	if noredundant then
		for a = 1, #files do
			if fs.isDir(tostring(files[a])) then
				if #fs.list(tostring(files[a])) ~= 0 then
					table.remove(files,a)
				end
			end
		end
	end
	return files
end
if not (input) then
	return displayHelp()
end
if not outpath then
	outpath = input
end

local choice = function(input,verbose)
	if not input then
		input = "yn"
	end
	if verbose then
		write("[")
		for a = 1, #input do
			write(input:sub(a,a):upper())
			if a < #input then
				write(",")
			end
		end
		write("]?")
	end
	local evt,char
	repeat
		evt,char = os.pullEvent("char")
	until string.find(input:lower(),char:lower())
	if verbose then
		print(char:upper())
	end
	local pos = string.find(input:lower(),char:lower())
	return pos, char:lower()
end

local postToPastebin = function(name, contents)
	local key = "0ec2eb25b6166c0c27a394ae118ad829"
	local response = http.post(
		"http://pastebin.com/api/api_post.php",
		"api_option=paste&"..
		"api_dev_key="..key.."&"..
		"api_paste_format=lua&"..
		"api_paste_name="..textutils.urlEncode(name).."&"..
		"api_paste_code="..textutils.urlEncode(contents)
	)
	if response then
		local sResponse = response.readAll()
		response.close()
		local sCode = string.match( sResponse, "[^/]+$" )
		return sCode
	else
		return false
	end
	return 
end

function doPack(input,output,doCompress,verbose) --make sure that shell exists before using verbose mode
	local tx = term.getTextColor()
	if not doPastebin then
		if not fs.exists(input) then return 3 end
		if fs.isReadOnly(output) then return 5 end
	end
	local packageSelf = true
	local packageReadOnly = true
	local ro_asked = false
	local ps_asked = false
	if fs.isDir(input) then --if not a package
		local out = {}
		local list = listAll(input,nil,true)
		if verbose then
			for a = 1, #list do --this checks for self and read-only files
				if fs.isReadOnly(list[a]) and (not ro_asked) then
					write("Include read-only files? ")
					if choice("yn",true) == 2 then
						packageReadOnly = false
					end
					ro_asked = true
				end
				if fs.combine("",list[a]) == shell.getRunningProgram() and (not ps_asked) then
					write("Include self? ")
					if choice("yn",true) == 2 then
						packageSelf = false
					end
					ps_asked = true
				end
			end
		end
		for a = 1, #list do --this loop kills fascists
			local is_self = fs.combine("",list[a]) == fs.combine("",shell.getRunningProgram())
			if not ((is_self and not packageSelf) or (fs.isReadOnly(list[a]) and not packageReadOnly)) then
				if verbose then
					write("[")
					if term.isColor() then term.setTextColor(colors.lightGray) end
					write(sanitize(list[a],fs.combine(dir,input)))
					term.setTextColor(tx)
					write("]")
				end
				if fs.isDir(list[a]) then
					out[sanitize(list[a],fs.combine(dir,input))] = true
				else
					local file = fs.open(list[a],"r")
					local cont = file.readAll()
					file.close()
					if doCompress then
						out[sanitize(list[a],fs.combine(dir,input))] = tablize(compyress(cont))
					else
						out[sanitize(list[a],fs.combine(dir,input))] = tablize(cont)
					end
				end
				local tx = term.getTextColor()
				if fs.getName(list[a]):lower() == "peasant" then
					if term.isColor() then
						term.setTextColor(colors.orange)
					end
					print(" BURNINATED")
				else
					if term.isColor() then
						term.setTextColor(colors.green)
					end
					print(" GOOD")
				end
				term.setTextColor(tx)
			else
				if fs.getName(list[a]):lower() == "peasant" then
					print("Spared "..list[a])
				else
					print("Skipped "..list[a])
				end
			end
		end
		local fullOutput = tostring(doCompress).."\n"..fixstr(textutils.serialize(out))
		local sCode
		if doPastebin then
			print("Uploading...")
			sCode = postToPastebin(input,fullOutput)
			return 7, "Code = '"..sCode.."'"
		else
			if fs.isDir(output) then fs.delete(output) end
			local file = fs.open(output,"w")
			file.write(fullOutput)
			file.close()
			return 1
		end
	else --if a package
		local list, isCompy
		if not doPastebin then
			local file = fs.open(input,"r")
			isCompy = file.readLine()
			list = file.readAll()
			file.close()
		else
			local file = http.get("http://pastebin.com/raw/"..tostring(input))
			if file then
				isCompy = file.readLine()
				list = file.readAll()
			else
				return 6
			end
		end
		local list = textutils.unserialize(list)
		if type(list) ~= "table" then
			return 4
		end
		if fs.exists(output) then
			fs.delete(output)
		end
		local amnt = 0
		for k,v in pairs(list) do
			amnt = amnt + 1
		end
		local num = 0
		for k,v in pairs(list) do
			num = num + 1
			if v == true then
				fs.makeDir(fs.combine(output,fs.combine(k,dir)))
			else
				local file = fs.open(fs.combine(output,fs.combine(k,dir)),"w")
				if verbose then
					write("[")
					if term.isColor() then term.setTextColor(colors.lightGray) end
					write(k)
					term.setTextColor(tx)
					write("]")
				end
				if isCompy:gsub(" ","") == "true" then
					file.write(decompyress(tablize(v)))
				else
					file.write(tablize(v))
				end
				file.close()
				local tx = term.getTextColor()
				if fs.getName(k):lower() == "peasant" then
					if term.isColor() then
						term.setTextColor(colors.orange)
					end
					print(" UNBURNINATED")
				else
					if term.isColor() then
						term.setTextColor(colors.green)
					end
					print(" GOOD")
				end
				term.setTextColor(tx)
			end
		end
		return 2
	end
end

local success, res, otherRes = pcall( function() return doPack(input,outpath,doCompress,true) end ) --functionized it!

if not success then
	term.setTextColor(colors.white)
	print("\n***Something went wrong!***")
	return printError(res)
end

if res then
	local msgs = {
		[1] = "Successfully packed '"..input.."/' as '"..outpath.."'",
		[2] = "Successfully unpacked '"..input.."' to '"..outpath.."/'",
		[3] = "That file/folder does not exist.",
		[4] = "That file isn't a packed folder.",
		[5] = "You don't have permission.",
		[6] = "Failed to connect.",
		[7] = "Uploaded successfully.",
	}
	print(msgs[res])
	if otherRes then
		print(otherRes)
	end
end