--[[
Author: TheOriginalBIT
Version: 3.2 ALPHA
Created: 17 Dec 2012
Last Update: 23 Dec 2012

License:

COPYRIGHT NOTICE
Copyright  2012 Joshua Asbury a.k.a TheOriginalBIT

All software produced by TheOriginalBIT has Attribution-NonCommercial-ShareAlike licence.

TheOriginalBIT, herein refered to as "the author" allows you to copy,
distribute and adapt the work under the following conditions:

Attribution  This license MUST be present at the top of the file.
You must attribute the work in the manner specified by the author
                                        (but not in any way that suggests that they endorse you or your use of the work).
                                If using parts of this code you must attribute the author above code snippet

Noncommercial        You may not use this work for commercial purposes.

Share Alike  If you alter, transform, or build upon this work, you may distribute the resulting work
                                        only under the same or similar license to this one.

Any of the above conditions can be waived if you get permission from the copyright holder.

This software is provided by the author "AS IS". As such the author does not take any responsibility for
any damage caused to your systems, physical or virtual, especially from misuse or modification of code.
]]--

----------------------------------------------------------
-------------------- Screensaver Utils -------------------
----------------------------------------------------------

local sizeX, sizeY = term.getSize()
local hexCodes = { [10] = "a", [11] = "b", [12] = "c", [13] = "d", [14] = "e", [15] = "f" }

local function hexLookup( char )
	local value = tonumber(char, 16)
	return (value and math.pow(2, value) or nil)
end

local function initTimers( update_freq )
	local t = {}
	
	t["update"] = os.startTimer( update_freq )
	t["time"] = os.startTimer(0.4)
	
	return t
end

local function colorCheck()
	return (term.isColor == nil and false or term.isColor())
end

local function clear()
	if colorCheck then
		term.setBackgroundColor(colors.black)
		term.setTextColor(colors.white)
	end
	term.clear()
	term.setCursorPos(1,1)
end

----------------------------------------------------------
----------------------- Login Funcs ----------------------
----------------------------------------------------------

local function check( unlockPassword, text, hashFunc )
	if type(unlockPassword) == "table" then
		for _, v in pairs(unlockPassword) do
			if (hashFunc and hashFunc(text) or text) == v then
				return true
			end
		end
		return false
	elseif type(unlockPassword) == "string" then
		return (hashFunc and hashFunc(text) or text) == unlockPassword
	end

	error("Something went seriously wrong!", 2)
end

local function login(unlockPassword, hashFunc)
	unlockPassword = unlockPassword == nil and "default" or unlockPassword

	local text = ""
	local error = ""
	local errorTimer = os.startTimer(0)
	local timeOut = os.startTimer(10)
	
	while true do
		event = { os.pullEventRaw() }

		clear()

		if event[1] == "char" then
			text = text..event[2]
		elseif event[1] == "key" then
			error = ""
			if event[2] == keys.backspace then
				text = text:sub(1, text:len() - 1)
			elseif event[2] == keys.enter then
				if check( unlockPassword, text, hashFunc ) then
					return true
				else
					text = ""
					error = "Error: Incorrect Password"
					errorTimer = os.startTimer(2)
				end
			end
			timeOut = os.startTimer(10)
		elseif event[1] == "timer" and event[2] == errorTimer then
			error = ""
		elseif event[1] == "timer" and event[2] == timeOut then
			text = ""
			return false
		end


		term.setCursorPos(6, sizeY / 2)

		write("Enter Password: ")

		for i = 1, #text do
			write("*")
		end

		term.setCursorPos(2, sizeY)
		write(error)
	end
	
	errorTimer = nil
	timeOut = nil
end

----------------------------------------------------------
--------------- Bouncing Ball Render Mode ----------------
----------------------------------------------------------

local function data_struct_ball()
	
	local t = {}
	
	t["x"] = sizeX / 2
	t["y"] = sizeY / 2
	t["color"] = colors.red
	t["moveX"] = 1
	t["moveY"] = 1
	
	return t
end

local function render_ball( data )
	term.setCursorPos( data["x"], data["y"] )
	term.setBackgroundColor( data["color"] )
	write(" ")
end

local function update_ball( data )
	if data["x"] >= sizeX then
		data["moveX"] = -1
	elseif data["x"] <= 2 then
		data["moveX"] = 1
	end

	if data["y"] >= sizeY then
		data["moveY"] = -1
	elseif data["y"] <= 2 then
		data["moveY"] = 1
	end

	data["x"] = data["x"] + data["moveX"]
	data["y"] = data["y"] + data["moveY"]
end

----------------------------------------------------------
----------------- Full Flash Render Mode -----------------
----------------------------------------------------------

local function data_struct_fullflash()
	local t = {}
	t["color"] = 1
	return t
end

local function render_fullflash( data )
	term.setCursorPos(1,1)
	term.setBackgroundColor( data["color"] )
	term.clear()
end

local function update_fullflash( data )
	data["color"] = 2 ^ math.random(0,15)
end

----------------------------------------------------------
-------------------- Dots Render Mode --------------------
----------------------------------------------------------

local function data_struct_dots()
	local t = {}
	
	t["image"] = {}
	
	return t
end

local function render_dots( data )
	for _, i in pairs(data["image"]) do
		term.setCursorPos(i["x"], i["y"])
		term.setBackgroundColor(i["color"])
		term.write(" ")
	end
end

local function update_dots( data )
	if #data["image"] >= 100 then
		data["image"] = {}
	end

	newDot = {
		["x"] = math.random(1, sizeX),
		["y"] = math.random(1, sizeY),
		["color"] = 2 ^ math.random(0,15)
	}
	
	table.insert(data["image"], newDot)
end

----------------------------------------------------------
-------------- Horizontal Lines Render Mode --------------
----------------------------------------------------------

local function data_struct_horizontallines()
	return {}
end

local function render_horizontallines( data )
	for y = 1, sizeY do
		term.setBackgroundColor( 2 ^ math.random(0,15) )
		term.setCursorPos(1,y)
		term.clearLine()
	end
end

local function update_horizontallines( data ) end

----------------------------------------------------------
--------------- Vertical Lines Render Mode ---------------
----------------------------------------------------------

local function data_struct_verticallines()
	return {}
end

local function render_verticallines( data )
	for x = 1, sizeX do
		term.setBackgroundColor( 2 ^ math.random(0,15) )
		for y = 1, sizeY do
			term.setCursorPos(x,y)
			write(" ")
		end
	end
end

local function update_verticallines( data ) end

----------------------------------------------------------
---------------- Moving Time Render Mode -----------------
----------------------------------------------------------

local function data_struct_timemove()
	local t = {}
	local theTime = textutils.formatTime(os.time(), false)
	
	t["timeX"] = math.random(1, (sizeX - theTime:len()))
	t["timeY"] = math.random(1, sizeY)
	t["moveX"] = 1
	t["moveY"] = 1
	
	return t
end

local function render_timemove( data )
	term.setCursorPos( data["timeX"], data["timeY"] )
	write( textutils.formatTime(os.time(), false) )
end

local function update_timemove( data )
	local theTime = textutils.formatTime(os.time(), false)
	
	data["timeX"] = data["timeX"] + data["moveX"]
	data["timeY"] = data["timeY"] + data["moveY"]
	
	if data["timeX"] <= 1 then
		data["timeX"] = 1
		data["moveX"] = 1
	elseif (data["timeX"] + theTime:len()) - 1 >= sizeX then
		data["timeX"] = sizeX - theTime:len() + 1
		data["moveX"] = -1
	end
	
	if data["timeY"] <= 1 then
		data["timeY"] = 1
		data["moveY"] = 1
	elseif data["timeY"] >= sizeY then
		data["timeY"] = sizeY
		data["moveY"] = -1
	end
	
	rand = math.random(0,10)

	if rand == 2 then
		data["moveX"] = -1
	elseif rand == 4 then
		data["moveX"] = 1
	elseif rand == 6 then
		data["moveY"] = -1
	elseif rand == 8 then
		data["moveY"] = 1
	end
end

----------------------------------------------------------
--------------- Shifty Smiley Render Mode ----------------
----------------------------------------------------------

local function data_struct_smiley()
	local t = {}
	
	t["moveEyes"] = false
	t["blink"] = false
	t["eyeOffset"] = 1
	t["image"] = {
		"                    444444444           ",
		"                  4444444444444         ",
		"                44444444444444444       ",
		"               44444444444444444444     ",
		"              4444444444444444444444    ",
		"             444440004444444400044444   ",
		"             444440004444444400044444   ",
		"            44444444444444444444444444  ",
		"            44444444444444444444444444  ",
		"            44444444444444444444444444  ",
		"             444444444444444444444444   ",
		"             444444ffffffffffff444444   ",
		"              444444feeeffeeef444444    ",
		"               44444444eeee44444444     ",
		"                 4444444444444444       ",
		"                   444444444444         ",
		"                     44444444           "
	}
	
	return t
end

local function render_smiley( data )
	for r = 1, #data["image"] do
		for c = 1, data["image"][r]:len() do
			term.setCursorPos(1 + c, 1 + r)
			char = data["image"][r]:sub(c,c)
			
			if char ~= " " then
				term.setBackgroundColor( hexLookup( char ) )
				write(" ")
			end
		end
		if r ~= #data["image"] then print() end
	end
	
	term.setBackgroundColor( colors.black )
	term.setCursorPos(20 + data["eyeOffset"], 8)
	write(" ")
	term.setCursorPos(31 + data["eyeOffset"], 8)
	write(" ")
	
	if data["bink"] then
		term.setBackgroundColor( colors.yellow )
		for i = 0, 2 do
			term.setCursorPos(20 + i, 7)
			write(" ")
			term.setCursorPos(20 + i, 7 + 1)
			write(" ")
			term.setCursorPos(31 + i, 7)
			write(" ")
			term.setCursorPos(31 + i, 7 + 1)
			write(" ")
		end
	end
end

local function update_smiley( data )
	local rand = math.random(0,10)
	
	if data["blink"] and data["blinkProgress"] == 1 then
		data["blinkProgress"] = 2
		data["blink"] = false
	elseif not data["blink"] and data["blinkProgress"] == 1 then
		data["blinkProgress"] = 0
	elseif not data["blink"] and data["blinkProgress"] == 2 then
		data["blinkProgress"] = 1
	end
	
	if rand >= 5 and rand <= 8 then
		data["eyeOffset"] = math.random(0,2)
	elseif rand == 1 and (not data["blink"] or data["blinkProgress"] ~= 0) then
		data["blink"] = true
		data["blinkProgress"] = 1
	end
end

----------------------------------------------------------
------------------- Growth Render Mode -------------------
----------------------------------------------------------

local function data_struct_growth()
	local t = {}
	
	t["growth1"] = {}
	t["growth2"] = {}
	t["growth3"] = {}
	t["growth4"] = {}
	
	t["growth1HeadX"], t["growth1HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY / 2))
	t["growth2HeadX"], t["growth2HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY / 2))
	t["growth3HeadX"], t["growth3HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY / 2))
	t["growth4HeadX"], t["growth4HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY / 2))
		
	return t
end

local function render_growth( data )
	for _,v in pairs(data["growth1"]) do
		term.setCursorPos(v[1], v[2])
		term.setBackgroundColor(v[3])
		term.write(" ")
	end
	
	for _,v in pairs(data["growth2"]) do
		term.setCursorPos(v[1], v[2])
		term.setBackgroundColor(v[3])
		term.write(" ")
	end
	
	for _,v in pairs(data["growth3"]) do
		term.setCursorPos(v[1], v[2])
		term.setBackgroundColor(v[3])
		term.write(" ")
	end
	
	for _,v in pairs(data["growth4"]) do
		term.setCursorPos(v[1], v[2])
		term.setBackgroundColor(v[3])
		term.write(" ")
	end
end

local function update_growth( data )
	if #data["growth1"] >= 100 then
		data["growth1"] = {}
		data["growth1HeadX"], data["growth1HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY))
	end
	
	if #data["growth2"] >= 110 then
		data["growth2"] = {}
		data["growth1HeadX"], data["growth1HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY))
	end
	
	if #data["growth3"] >= 120 then
		data["growth3"] = {}
		data["growth1HeadX"], data["growth1HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY))
	end
	
	if #data["growth4"] >= 90 then
		data["growth4"] = {}
		data["growth1HeadX"], data["growth1HeadY"] = math.floor(math.random(1, sizeX)), math.floor(math.random(1, sizeY))
	end
	
	data["growth1HeadX"] = (data["growth1HeadX"] + math.random(-1,1)) % sizeX
	data["growth1HeadY"] = (data["growth1HeadY"] + math.random(-1,1)) % sizeX
	data["growth2HeadX"] = (data["growth2HeadX"] + math.random(-1,1)) % sizeX
	data["growth2HeadY"] = (data["growth2HeadY"] + math.random(-1,1)) % sizeX
	data["growth3HeadX"] = (data["growth3HeadX"] + math.random(-1,1)) % sizeX
	data["growth3HeadY"] = (data["growth3HeadY"] + math.random(-1,1)) % sizeX
	data["growth4HeadX"] = (data["growth4HeadX"] + math.random(-1,1)) % sizeX
	data["growth4HeadY"] = (data["growth4HeadY"] + math.random(-1,1)) % sizeX
	
	table.insert(data["growth1"], {data["growth1HeadX"], data["growth1HeadY"], math.random(1,30) == 10 and colors.brown or math.random(1,20) == 10 and colors.lime or colors.green})
	table.insert(data["growth2"], {data["growth2HeadX"], data["growth2HeadY"], math.random(1,30) == 10 and colors.brown or math.random(1,20) == 10 and colors.lime or colors.green})
	table.insert(data["growth3"], {data["growth3HeadX"], data["growth3HeadY"], math.random(1,30) == 10 and colors.brown or math.random(1,20) == 10 and colors.lime or colors.green})
	table.insert(data["growth4"], {data["growth4HeadX"], data["growth4HeadY"], math.random(1,30) == 10 and colors.brown or math.random(1,20) == 10 and colors.lime or colors.green})
end

----------------------------------------------------------
--------------- NDF-OS Special Render Mode ---------------
---------- By request of NDFJay - NDF-OS Creator ---------
----------------------------------------------------------

local function data_struct_ndf()
	local t = {}
	
	t["image"] = {
	"   7777777777777   ",
	" 77733333333333777 ",
	"7733333333333333377",
	"7333330033300333337",
	"7333330733370333337",
	"8333333333333330338",
	"8333333333333003338",
	"8833333333300333388",
	" 88833333333333888 ",
	"   8888888888888   "}
	
	t["x"] = math.random(1, sizeX - #t["image"][1])
	t["y"] = math.random(1, sizeY - (#t["image"] + 2))
	t["moveX"] = 1
	t["moveY"] = 1
	
	return t
end

local function render_ndf( data )
	term.setCursorPos(1,1)
	term.setBackgroundColor(colors.white)
	term.clear()
	for r = 1, #data["image"] do
		for c = 1, data["image"][r]:len() do
			term.setCursorPos(data["x"] + c, data["y"] + r)
			char = data["image"][r]:sub(c,c)

			if char ~= " " then
				term.setBackgroundColor( hexLookup( char ) )
				write(" ")
			end
		end
		print()
	end
	term.setBackgroundColor(colors.white)
	term.setTextColor(colors.black)
	local theTime = textutils.formatTime(os.time(), false)
	local timeX = data["x"] + (data["image"][1]:len() / 2) - 2
	local timeY = data["y"] + (#data["image"]) + 2
	term.setCursorPos(timeX, timeY)
	term.write(theTime)
	term.setTextColor(colors.white)
end

local function update_ndf( data )
	if data["x"] + data["image"][1]:len() >= sizeX then
		data["moveX"] = -1
	elseif data["x"] < 1 then
		data["moveX"] = 1
	end

	if data["y"] + #data["image"] >= sizeY - 2 then
		data["moveY"] = -1
	elseif data["y"] < 1 then
		data["moveY"] = 1
	end
	
	data["x"] = data["x"] + data["moveX"]
	data["y"] = data["y"] + data["moveY"]
end

----------------------------------------------------------
-------------------- Main Render Func --------------------
----------------------------------------------------------

local function render( render_func, update_func, data_struct, update_freq, draw_time )
	local timers = initTimers( update_freq )
	local theTime = textutils.formatTime(os.time(), false)
	
	while true do
		local event = { os.pullEventRaw() }
		clear()
		if event[1] == "key" or event[1] == "mouse_click" or event[1] == "mouse_scroll" or event[1] == "mouse_drag" then
			timers = nil
			shell.run(".var/gui")
			return
		elseif event[1] == "timer" then
			if event[2] == timers["update"] then
				update_func( data_struct )
				timers["update"] = os.startTimer( update_freq )
			elseif event[2] == timers["time"] then
				theTime = textutils.formatTime(os.time(), false)
				timers["time"] = os.startTimer(0.4)
			end
		end
		
		render_func( data_struct )

		if render_func ~= render_ndf then
			term.setBackgroundColor(colors.black)
			term.setTextColor(colors.white)
		else
			term.setBackgroundColor(colors.white)
			term.setTextColor(colors.black)
		end
		
		if draw_time then
			term.setCursorPos(sizeX - theTime:len() - 2, sizeY - 1)
			write(theTime)
		end
	end
end

----------------------------------------------------------
---------------- Render Modes Table Funcs ----------------
----------------------------------------------------------

local RENDER_MODES = {
	["bouncing_ball"] = { render_ball, update_ball, data_struct_ball, 0.2, true, "Moving Ball" },
	["full_flash"] = { render_fullflash, update_fullflash, data_struct_fullflash, 1, true, "Flashing Screen"},
	["dots"] = { render_dots, update_dots, data_struct_dots, 0.2, true, "Random Dots" },
	["horizontal_lines"] = { render_horizontallines, update_horizontallines, data_struct_horizontallines, 2, true, "Colorful Lines (Horizontal)" },
	["vertical_lines"] = { render_verticallines, update_verticallines, data_struct_verticallines, 2, true, "Colorful Lines (Vertical)" },
	["moving_time"] = { render_timemove, update_timemove, data_struct_timemove, 0.5, false, "Bouncing Time"},
	["shifty_smiley"] = { render_smiley, update_smiley, data_struct_smiley, 0.2, true, "Smiley Face"},
	["growth"] = { render_growth, update_growth, data_struct_growth, 0.2, true, "Spreading Moss"},
	["ndf-os"] = { render_ndf, update_ndf, data_struct_ndf, 0.4, true, "(Special) NDF-OS" }
}

function getRenderModeTable()
	return RENDER_MODES
end

local function getRenderMode(key)
	return RENDER_MODES[key]
end

----------------------------------------------------------
--------------- Render Mode Register Funcs ---------------
----------------------------------------------------------

function registerRenderModePrepacked( key, render_mode_packed )
	if type(key) ~= "string" then
		error( "A key must be of type string when registering a new render mode, found to be "..type(key).."." )
	end
	
	if type( render_mode_packed ) ~= "table" then
		error( "Invalid parameter ("..type( render_mode )..") when registering new render mode, should be a table." )
	end
	
	if #render_mode_packed > 4 then
		error( "Too much information when registering new render mode, render mode table should contain 4 items, actually contains "..#render_mode_packed.."." )
	end
	
	if not render_mode_packed[1] or not render_mode_packed[2] or not render_mode_packed[3] then
		error( "No pointer to "..(not render_mode_packed[1] and "render" or not render_mode_packed[2] and "update" or not render_mode_packed[3] and "data structure").." function supplied when registering new render mode." )
	end
	
	if type(render_mode_packed[1]) ~= "function" then
		error( "Render function parameter was referenced to a "..type(render_mode_packed[1]).." not a function when registering new render mode." )
	end
	
	if type(render_mode_packed[2]) ~= "function" then
		error( "Update function parameter was referenced to a "..type(render_mode_packed[2]).." not a function when registering new render mode." )
	end
	
	if type(render_mode_packed[3]) ~= "function" then
		error( "Data structure function parameter was referenced to a "..type(render_mode_packed[3]).." not a function when registering new render mode." )
	end
	
	if type(render_mode_packed[4]) ~= "number" then
		error( "The update frequency parameter should be a number, was found to be "..type(render_mode_packed[3])..", when registering new render mode." )
	end
	
	if type(render_mode_packed[5]) ~= "string" then
		error( "The display name parameter should be a string, was found to be "..type(render_mode_packed[4])..", when registering new render mode." )
	end
	
	if type(RENDER_MODES[key]) ~= "table" then
		error( "Render mode key, "..key..", already exists when attempting to register new render mode." )
	end
	
	RENDER_MODES[key] = render_mode_packed
end

function registerRenderMode( key, render_func, update_func, data_struct_func, update_freq, display_name )
	t = { render_func, update_func, data_struct_func, update_freq, display_name }
	registerRenderModePrepacked( key, t )
end

----------------------------------------------------------
------------------ Entry Func of Program -----------------
----------------------------------------------------------

local function ssRender(render_mode, show_time, require_login, unlock_password, hash_func)
	clear()

	if not colorCheck() then error( "Sorry, this API only runs on Advanced Computers", 2) end

	if type(render_mode) ~= "string" then error( "Invalid render mode supplied", 2) end

	mode_info = getRenderMode( render_mode )

	if not mode_info then error( "The render mode \""..render_mode.."\" does not exist", 2) end

	render_func, update_func, data_struct_func, update_freq, display_time = mode_info[1], mode_info[2], mode_info[3], mode_info[4], mode_info[5]
	
	data_struct = data_struct_func()
	
	while true do
		render( render_func, update_func, data_struct, update_freq, ((display_time == true) and (show_time == true)))

		if not require_login then break end

		if login(unlock_password, hash_func) then break end
	end
	
	data_struct = nil
end

tArgs = { ... }

--[[if #tArgs < 3 or #tArgs > 5 then
	error("Invalid amount of arguments supplied.")
end

ssRender( tArgs[1], tArgs[2]:lower() == "true", tArgs[3]:lower() == "true", tArgs[4], tArgs[5])]]--

ssRender("ndf-os", false, false)