--[[
	cLinux : Lore of the Day!
	Made by Piorjade, daelvn

	NAME:        /sys/commandline
	CATEGORY:    boot
	SET:         Boot III
	VERSION:     01:alpha0
	DESCRIPTION:
		This script is the commandline and is run as a service.
]]--
--variables and tables
tasks = {
	list = {},
	last_uid = 0,
	somethingInFG = false,
}

_CONTROL = {
	colors = {
		mainBg = colors.black,
		mainFg = colors.white,
		userBg = colors.black,
		userFg = colors.yellow,
		middleBg = colors.black,
		middleFg = colors.white,
		pathBg = colors.black,
		pathFg = colors.blue,
	}
}

local errors = {
	ok = "noError",
	err = nil,
}
local started = {}
local windows = {}
local last_uid = 0
local mainwindow = nil
local mainuid = nil
local lastx = 1
local lasty = 1
local foreground = {}
local sudo = false
local sudopw = ""
--functions



function readPw()
	--[[
							Basically the read function,
							without writing any character on the screen.

							(like in a linux terminal)
	]]

	local str = ""
	term.setCursorBlink(true)
	repeat
		local _, k = os.pullEventRaw()
		if _ == "char" then
			str = str..k
		elseif _ == "key" and k == keys.backspace and #str > 0 then
			str = string.sub(str, 1, #str-1)
		end
	until _ == "key" and k == keys.enter
	print("")
	term.setCursorBlink(false)
	return str
end
_put('readPw', readPw)




local function login()
--[[
						login and register script
]]
	--attempt to get the list of users, located in ("/sys/usrData")
	local t = lib.perm.usrs.getList()
	if #t < 1 then
		--If the userlist is empty
		print("Register a user.")
		term.write("Name: ")
		local tmpUsr = ""
		local tmpPw = ""
		repeat
			--read input for username, repeat if nothing was entered or the user entered "root"
			tmpUsr = read()
			if tmpUsr == "root" then
				print("Please use another name.")
				term.write("Name: ")
				tmpUsr = ""
			end
			if #tmpUsr < 1 then
				print("Please enter a name.")
				term.write("Name: ")
			end
		until #tmpUsr > 0
		term.write("Password: ")
		repeat
			--read input for password, repeat if nothing was entered
			tmpPw = readPw()
			if #tmpPw < 1 then
				print("Please enter a password.")
				term.write("Password: ")
			end
		until #tmpPw > 0
		local ok = lib.perm.usrs.addUser(tmpUsr, tmpPw)
		--attempt to create the user, restarts login script if something goes wrong
		if ok then
			print("Successfully created!")
			term.setCursorPos(1,1)
			term.setBackgroundColor(colors.black)
			term.clear()
			fs.makeDir("/home/root")
			login()
		else
			print("Error...")
			sleep(2)
			login()
		end
	elseif #t > 0 then
		--If the userlist isn't empty
		print("Login with an existing user.")
		term.write("Name: ")
		local tmpUsr = ""
		local tmpPw = ""
		repeat
			--read input for username, repeat if nothing was entered
			tmpUsr = read()
			if #tmpUsr < 1 then
				print("Please enter a name.")
				term.write("Name: ")
			end
		until #tmpUsr > 0
		term.write("Password: ")
		repeat
			--read input for password, repeat if nothing was entered
			tmpPw = readPw()
			if #tmpPw < 1 then
				print("Please enter a password.")
				term.write("Password: ")
			end
		until #tmpPw > 0
		local ok = lib.perm.permission.login(tmpUsr, tmpPw)
		--attempt to log in, prints error if the input is false or something goes wrong
		if ok == false then
			print("Oops, that didn't work, let's try again.")
			login()
		elseif ok == true then
			return
		elseif ok == nil then
			print("User doesn't exist.")
			login()
		end
	else
		--if perm didn't return the list, it restarts the script
		print("Error with userlist.")
		sleep(2)
		login()
	end
end



local function readcmd( _sReplaceChar, _tHistory, _fnComplete )		--Modified read(), just to print the current user and path at the front
    term.setCursorBlink( true )

    local sLine = ""
    local nHistoryPos
    local nPos = 0
    if _sReplaceChar then
        _sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
    end

    local tCompletions
    local nCompletion
    local function recomplete()
        if _fnComplete and nPos == string.len(sLine) then
            tCompletions = _fnComplete( sLine )
            if tCompletions and #tCompletions > 0 then
                nCompletion = 1
            else
                nCompletion = nil
            end
        else
            tCompletions = nil
            nCompletion = nil
        end
    end

    local function uncomplete()
        tCompletions = nil
        nCompletion = nil
    end

    local w = term.getSize()
    local sx = term.getCursorPos()

	local function rdfirst()
		local x, y = term.getCursorPos()
		term.setCursorPos(1, y)
		local cPath = shell.dir()
		term.setBackgroundColor(_CONTROL.colors.userBg)
		term.setTextColor(_CONTROL.colors.userFg)
		term.write(lib.perm.permission.getCurrentUser())
		term.setBackgroundColor(_CONTROL.colors.middleBg)
		term.setTextColor(_CONTROL.colors.middleFg)
		term.write("@")
		term.setBackgroundColor(_CONTROL.colors.pathBg)
		term.setTextColor(_CONTROL.colors.pathFg)
		local cUser = lib.perm.permission.getCurrentUser()
		local i, j = string.find(cPath, "/home/"..cUser.."/")
		if i==1 then
			cPath = "~/"..string.sub(cPath, j+1, #cPath)
		end
		term.write(cPath.."> ")
		local all = lib.perm.permission.getCurrentUser().."@"..cPath.."> "
		term.setBackgroundColor(_CONTROL.colors.mainBg)
		term.setTextColor(_CONTROL.colors.mainFg)
		local nx, ny = term.getCursorPos()
		term.setCursorPos(x, y)
		sx = nx
	end

    local function redraw( _bClear )
		term.redirect(oldTerm)
		term.clearLine()
		rdfirst()



        local nScroll = 0
        if sx + nPos >= w then
            nScroll = (sx + nPos) - w
        end







        local cx,cy = term.getCursorPos()
        term.setCursorPos( sx, cy )
        local sReplace = (_bClear and " ") or _sReplaceChar
        if sReplace then
            term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
        else
            term.write( string.sub( sLine, nScroll + 1 ) )
        end

        if nCompletion then
            local sCompletion = tCompletions[ nCompletion ]
            local oldText, oldBg
            if not _bClear then
                oldText = term.getTextColor()
                oldBg = term.getBackgroundColor()
                term.setTextColor( colors.white )
                term.setBackgroundColor( colors.gray )
            end
            if sReplace then

                term.write( string.rep( sReplace, string.len( sCompletion ) ) )
            else

                term.write( sCompletion )
            end
            if not _bClear then
                term.setTextColor( oldText )
                term.setBackgroundColor( oldBg )
            end
        end

        term.setCursorPos( sx + nPos - nScroll, cy )
    end

    local function clear()

        redraw( true )
    end

    recomplete()
    redraw()




    local function acceptCompletion()
        if nCompletion then
            -- Clear
            clear()

            -- Find the common prefix of all the other suggestions which start with the same letter as the current one
            local sCompletion = tCompletions[ nCompletion ]
            sLine = sLine .. sCompletion
            nPos = string.len( sLine )

            -- Redraw
            recomplete()
            redraw()
        end
    end

    while true do
				term.setCursorBlink( true )
        local sEvent, param = os.pullEventRaw()

		redraw()
		if sEvent == "char" then
            -- Typed key
            clear()
            sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
            nPos = nPos + 1
            recomplete()
            redraw()

        elseif sEvent == "paste" then
            -- Pasted text
            clear()
            sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
            nPos = nPos + string.len( param )
            recomplete()
            redraw()

        elseif sEvent == "key" then
            if param == keys.enter then
                -- Enter
                if nCompletion then
                    clear()
                    uncomplete()
                    redraw()
                end
								term.setCursorBlink(false)
                break

            elseif param == keys.left then
                -- Left
                if nPos > 0 then
                    clear()
                    nPos = nPos - 1
                    recomplete()
                    redraw()
                end

            elseif param == keys.right then
                -- Right
                if nPos < string.len(sLine) then
                    -- Move right
                    clear()
                    nPos = nPos + 1
                    recomplete()
                    redraw()
                else
                    -- Accept autocomplete
                    acceptCompletion()
                end

            elseif param == keys.up or param == keys.down then
                -- Up or down
                if nCompletion then
                    -- Cycle completions
                    clear()
                    if param == keys.up then
                        nCompletion = nCompletion - 1
                        if nCompletion < 1 then
                            nCompletion = #tCompletions
                        end
                    elseif param == keys.down then
                        nCompletion = nCompletion + 1
                        if nCompletion > #tCompletions then
                            nCompletion = 1
                        end
                    end
                    redraw()

                elseif _tHistory then
                    -- Cycle history
                    clear()
                    if param == keys.up then
                        -- Up
                        if nHistoryPos == nil then
                            if #_tHistory > 0 then
                                nHistoryPos = #_tHistory
                            end
                        elseif nHistoryPos > 1 then
                            nHistoryPos = nHistoryPos - 1
                        end
                    else
                        -- Down
                        if nHistoryPos == #_tHistory then
                            nHistoryPos = nil
                        elseif nHistoryPos ~= nil then
                            nHistoryPos = nHistoryPos + 1
                        end
                    end
                    if nHistoryPos then
                        sLine = _tHistory[nHistoryPos]
                        nPos = string.len( sLine )
                    else
                        sLine = ""
                        nPos = 0
                    end
                    uncomplete()
                    redraw()

                end

            elseif param == keys.backspace then
                -- Backspace
                if nPos > 0 then
                    clear()
                    sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
                    nPos = nPos - 1
                    recomplete()
                    redraw()
                end

            elseif param == keys.home then
                -- Home
                if nPos > 0 then
                    clear()
                    nPos = 0
                    recomplete()
                    redraw()
                end

            elseif param == keys.delete then
                -- Delete
                if nPos < string.len(sLine) then
                    clear()
                    sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
                    recomplete()
                    redraw()
                end

            elseif param == keys["end"] then
                -- End
                if nPos < string.len(sLine ) then
                    clear()
                    nPos = string.len(sLine)
                    recomplete()
                    redraw()
                end

            elseif param == keys.tab then
                -- Tab (accept autocomplete)
                acceptCompletion()

            end

        elseif sEvent == "term_resize" then
            -- Terminal resized
            w = term.getSize()
            redraw()

        end
    end

    local cx, cy = term.getCursorPos()
    term.setCursorBlink( false )
    term.setCursorPos( w + 1, cy )
    print()

    return sLine
end

local function cmd()
	local _aliases = {}
--[[
	Basically the whole terminal
]]




	--Get the name of the logged in user and set the directory
	local cUser = lib.perm.permission.getCurrentUser()
	shell.setDir("/home/"..cUser)

	--read .bashcol
	if fs.exists("/home/"..cUser.."/.bashcol") then
		local file, err = fs.open("/home/"..cUser.."/.bashcol", "r")
		if not file then
			cLinuxPrintError(".bashcol", err)
		else
			local t = file.readAll()
			t = textutils.unserialize(t)
			file.close()
			for each, color in pairs(t) do
				_CONTROL.colors[each] = color
			end
		end
	end

	--set aliases
	if fs.exists("/home/"..cUser.."/.aliases") then
		local file, err = fs.open("/home/"..cUser.."/.aliases", "r")
		if not file then
			cLinuxPrintError(".aliases", err)
		else
			local t = file.readAll()
			t = textutils.unserialize(t)
			file.close()
			for each, alias in pairs(t) do
				_aliases[each] = alias
			end
		end
	end

	while true do
		--start commandline loop

		--Print the current user and the path (example: test@/bin> _ )
		local cPath = shell.dir()
		oldTerm = term.current()
		term.setBackgroundColor(_CONTROL.colors.userBg)
		term.setTextColor(_CONTROL.colors.userFg)
		term.write(lib.perm.permission.getCurrentUser())
		term.setBackgroundColor(_CONTROL.colors.middleBg)
		term.setTextColor(_CONTROL.colors.middleFg)
		term.write("@")
		term.setBackgroundColor(_CONTROL.colors.pathBg)
		term.setTextColor(_CONTROL.colors.pathFg)

		--if the user is in his home directory, write ~ instead
		local i, j = string.find(cPath, "/home/"..lib.perm.permission.getCurrentUser().."/")
		if i == 1 then
			cPath = "~/"..string.sub(cPath, j+1)
		end
		term.write(cPath.."> ")
		term.setBackgroundColor(_CONTROL.colors.mainBg)
		term.setTextColor(_CONTROL.colors.mainFg)

		--Start reading the command
		local e = readcmd( nil, {}, shell.complete )
		--local e = readcmd( nil, {})
		--Start searching for 'sudo' and 'bg', also check if the user put false directories in
		local corruptPath = false
		local corruptMessage = ""
		local bg = false


		function sd()
			--'sudo', ask for root password
			term.write("Enter root password: ")
			local p = readPw()
			p = lib.perm.hashPw(p, "root")
			local ok = lib.perm.usrs.checkRootPw(p)
			if not ok then
				printError("Wrong password.")
			elseif ok == true then
				sudopw = p
				sudo = true
			end
		end

		--Search for 'sudo', if yes, start sd()
		local i, j = string.find(e, "sudo ")
		if i == 1 then
			e = string.sub(e, 6)
			local i, j = string.find(e, " ")
			local c = ""
			if i then
				c = string.sub(e, 1, i-1)
			end
			if fs.exists(c) or fs.exists(shell.dir().."/"..c) or fs.exists("/bin/"..c) then
				sd()
				if e == nil then
					e = ""
				end
			end
		end

		--Search for 'bg', if yes, remove 'bg ' from the input and set bg to true
		local i, j = string.find(e, "bg ")
		if i == 1 then
			e = string.sub(e, 4)
			bg = true
			if e == nil then
				e = ""
			end
		end


		--filter out every argument after the command
		local args = {}
		local oe = e
		local arg = ""
		local i, j = string.find(e, " ")
		if i == nil then
			e = e
		else
			arg = string.sub(e, j+1, #e)
			e = string.sub(e, 1, i-1)

		end

		if arg == nil or arg == "" then
			arg = ""
		else
			repeat
				local i, j = string.find(arg, "%s")
				if i ~= nil then
					local a = string.sub(arg, 1, i-1)
					local x, y = string.find(a, "~/")
					if x == 1 and y == 2 then
						local c = string.sub(a, 3, #a)
						a = "/home/"..lib.perm.permission.getCurrentUser().."/"..c
					end
					table.insert(args, a)
					arg = string.sub(arg, j+1, #arg)
				else
					local i, j = string.find(arg, "~/")
					if i == 1 and j == 2 then
						local c = string.sub(arg, 3, #arg)
						arg = "/home/"..lib.perm.permission.getCurrentUser().."/"..c
					end
					table.insert(args, arg)
				end
			until i == nil
		end


		--built-in commands
		local maincommands = {
			"kill",
			"service",
			"ps"
		}

		function kill(uid)
			--Function to kill a thread
			if tonumber(uid) == 1 then
				cLinuxPrintError("Core","You can't kill that.")
				return
			end

			local ok = thread.kill(tasks, uid)

			if ok then
				print("Success!")
			elseif ok == false then
				print("This shouldn't happen.")
			elseif ok == nil then
				print("Process not found.")
			end
		end


		function sv(...)
			--service manager (start, stop, enable and disable services)
			local tArgs = {...}
			local jArgs = {}
			for _, a in ipairs(tArgs) do
				if _ > 2 then
					table.insert(jArgs, a)
				end
			end
			local command = tArgs[1]
			local p = tArgs[2]
			if command == "start" then
				local ok, err = shell.startServ(p, jArgs)
				if not ok then
					printError(err)
				else
					print("Successfully started!")
				end
			elseif command == "stop" then
				local ok = shell.stopServ(p)
				if not ok then
					printError("Service not found.")
				else
					print("Stopped.")
				end
			elseif command == "enable" then
				local ok, err = lib.serv.set(p, true)
				if not ok then
					printError(err)
				else
					print("Successfully enabled! You need to restart.")
				end
			elseif command == "disable" then
				local ok, err = lib.serv.set(p, false)
				if not ok then
					printError(err)
				else
					print("Successfully disabled! You need to restart.")
				end
			elseif command == "core" then
				local ok, err = lib.serv.set(p, "core")
				if not ok then
					printError(err)
				else
					print("Successfully set as core! You need to restart.")
				end
			else
				print("Usage: service <start|stop|enable|disable|core> <name/path> [args]")
			end
		end


		--check if the user put a built-in command in
		local m = false
		for _, a in ipairs(maincommands) do
			if a == e then
				m = true
				break
			end
		end

		--replace the command with alias
		if _aliases[e] then
			e = _aliases[e]
		end
		--Start checking for false directory input
		local cPath = shell.dir()
		local i, j = string.find(e, "/")
		if i == 1 and j == i or m then
			e = e
		else
			if not fs.exists("/bin/"..e) then
				e = cPath..e
			end
		end
		local i, j = string.find(e, "//")
		if i then
			corruptPath = true
			corruptMessage = "Invalid path."
		end
		local i, j = string.find(e, "%.%.")
		if i and #e > 2 then
			corruptPath = true
			corruptMessage = "Invalid path."
		end
		for _, a in ipairs(args) do
			local i, j = string.find(a, "//")
			if i then
				corruptPath = true
				corruptMessage = "Invalid path."
			end
			local i, j = string.find(a, "%.%.")
			if i and #a > 2 then
				corruptPath = true
				corruptMessage = "Invalid path."
			end
		end

		local newEnv = {}
		for k, v in pairs(_G) do
			newEnv[k] = v
		end
		newEnv['_G'] = newEnv
		setmetatable(newEnv, {})



		--Start executing commands
		if not corruptPath then
			local dat = {
				name = "",
				password = "",
			}
			if sudo then
				dat.name  = "root"
				dat.password = sudopw
			else
				dat.name, dat.password = lib.perm.permission.getCurrentUser
			end
			if fs.exists(e) and #e > 0 and not m then
				--if the user put a full path in and it exists
				if bg == false then
					--Start in foreground mode
					local f, err = loadfile(e)
					if not f then
						printError(err)
					else

						local f, err = thread.new(f, newEnv, e, tasks, args, dat)
						if not f then
							printError(err)
						else
							for _, a in ipairs(tasks.list) do
								if a.name == e then
									a.background = false
									break
								end
							end
							tasks.somethingInFG = true
						end
					end
				elseif bg == true then
					--start in background mode
					local f, err = loadfile(e)
					if not f then
						printError(err)
					else
						local f, err = thread.new(f, newEnv, e, tasks, args, dat)
						if f then
							for _, v in ipairs(tasks.list) do
								if v.name == e then
									v.background = true
									break
								end
							end
						else
							printError(err)
						end
					end
				end
			elseif fs.exists(shell.dir()..e) and #e > 0 and not m then
				--if the file exists in the current directory
				if bg == false then
					--start in foreground mode
					local f, err = loadfile(shell.dir().."/"..e)
					if not f then
						printError(err)
					else
						--f(unpack(args))
						local f, err = thread.new(f, newEnv, shell.dir().."/"..e, tasks, args, dat)
						if not f then
							printError(err)
						else
							for _, a in ipairs(tasks.list) do
								if a.name == shell.dir().."/"..e then
									a.background = false
									break
								end
							end
							tasks.somethingInFG = true
						end
					end
				elseif bg == true then
					--start in background mode
					local f, err = loadfile(shell.dir().."/"..e)
					if not f then
						printError(err)
					else
						local f, err = thread.new(f, newEnv, shell.dir().."/"..e, tasks, args, dat)
						if f then
							for _, v in ipairs(tasks.list) do
								if v.name == shell.dir().."/"..e then
									v.background = true
									break
								end
							end
						else
							printError(err)
						end
					end
				end
			elseif fs.exists("/bin/"..e) and #e > 0 and not m then
				--if the file exists in /bin/
				if bg == false then
					--start in foreground mode
					local f, err = loadfile("/bin/"..e)
					if not f then
						printError(err)
					else
						--f(unpack(args))
						local f, err = thread.new(f, newEnv, "/bin/"..e, tasks, args, dat)
						if not f then
							printError(err)
						else
							for _, a in ipairs(tasks.list) do
								if a.name == "/bin/"..e then
									a.background = false
									break
								end
							end
							tasks.somethingInFG = true
						end
					end
				elseif bg == true then

					--start in background mode, if NOT sudo! sudo would mess things up
					local f, err = loadfile("/bin/"..e)
					if not f then
						printError(err)
					else
						local f, err = thread.new(f, newEnv, "/bin/"..e, tasks, args, dat)
						if f then
							for _, v in ipairs(tasks.list) do
								if v.name == "/bin/"..e then
									v.background = true
									break
								end
							end
						else
							printError(err)
						end
					end
				end
			elseif m then
				--if the user entered a built-in command
				if e == "kill" then
					--check for arguments and start kill()
					if #args ~= 1 then
						print("Usage: kill <UID>")
					else
						kill(args[1])
					end
				elseif e == "service" then
					--check for arguments and start sv()
					if #args ~= 2 then
						print("Usage: service <start|stop|enable|disable> <name/path> [args]")
					else
						sv(args[1], args[2])
					end
				elseif e == "ps" then
					print("UID | Name/Path")
					for k, v in pairs(tasks.list) do
						if not v.dead then
							term.write(tostring(v.uid).." | ")
							print(tostring(v.name))
						end
					end
				end

			elseif fs.exists(e) == false and #e > 0 then
				--if any of these above don't exist
				print("Command not found.")
			end
			if sudo then
				--if sudo was entered, get out of it again
				sudo = false
			end
		else
			--if the user entered a false directory
			printError(corruptMessage)
		end

	end
end

--code

--Clear the screen
term.setCursorPos(1,1)
term.setBackgroundColor(colors.black)
term.clear()

--start the login script, if no user is logged in

local ok, err, err2 = pcall(lib.perm.permission.getCurrentUser)
if not ok then
	flag.STATE_CRASHED = err
end
if type(err) == "string" and #err < 1 or err == nil then
	login()
end

--Add the cmd function as the main task, with the parent window
local maxX, maxY = term.getSize()
local n, err = thread.new(cmd, getfenv(1), "/sys/cmdbak", tasks)
if not n then
	printError(err)
end
local running = true
local evt = {}
while running do
	local ok = thread.resumeAll(tasks, evt)
	evt = {os.pullEventRaw()}
	if ok == false then
		running = false
	end
end
