--[[
Modified read() with Tab-Completion v1.2 by Espen
Original from shell-file of ComputerCraft v1.46

Changelog:
v1.2 - Added a separate program to to allow runtime-injection of the custom read() into the running shell of an individual computer.
        Thanks @BigSHinyToys for making me aware of the global table's dropped protection. ^_^
v1.12 - Fixed: Pressing Tab without entering anything threw an error.
v1.11 - Removed previously introduced, but now unnecessary parameter from read()
v1.1 - Path traversal implemented
v1.0 - Initial release
--]]

local function read( _sReplaceChar, _tHistory )
	term.setCursorBlink( true )

    local sLine = ""
    local tMatches = { pointer = 0, get = function( self ) return self[ self.pointer ] end }    -- tMatches:get() == tMatches[ tMatches.pointer ]
	local nHistoryPos = nil
	local nPos = 0
    if _sReplaceChar then
		_sReplaceChar = string.sub( _sReplaceChar, 1, 1 )
	end
	
	local w, h = term.getSize()
	local sx, sy = term.getCursorPos()	
	
	local function redraw( _sCustomReplaceChar )
		local nScroll = 0
		if sx + nPos >= w then
			nScroll = (sx + nPos) - w
		end
			
		term.setCursorPos( sx, sy )
		local sReplace = _sCustomReplaceChar or _sReplaceChar
		if sReplace then
			term.write( string.rep(sReplace, string.len(sLine) - nScroll) )
		else
			term.write( string.sub( sLine, nScroll + 1 ) )
		end
		term.setCursorPos( sx + nPos - nScroll, sy )
	end
	
	while true do
		local sEvent, param = os.pullEvent()
		if sEvent == "char" then
            tMatches = { pointer = 0, get = function( self ) return self[ self.pointer ] end }  -- Reset completion match-table.
			sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
			nPos = nPos + 1
			redraw()
			
		elseif sEvent == "key" then
            if not (param == keys.tab) then
                tMatches = { pointer = 0, get = function( self ) return self[ self.pointer ] end }  -- Reset completion match-table.
            end
		    if param == keys.enter then
				-- Enter
				break
				
            elseif param == keys.tab then
                -- Tab
                if #tMatches > 0 then   -- tab was pressed before.
                    -- [[ We already have matches, show the next one ]]
                    local nLastMatchSize = string.len( tMatches:get() )
                    
                    -- Shift pointer to next match.
                    tMatches.pointer = tMatches.pointer + 1
                    if tMatches.pointer > #tMatches then tMatches.pointer = 1 end  -- Wrap around if the pointer has gone past the end.
                    
                    -- Clear the line if the new match is smaller than the previous.
                    if string.len(tMatches:get()) < nLastMatchSize then redraw(" ") end
                    -- Assemble the new line.
                    sLine = string.sub( sLine, 1, nPos - nLastMatchSize ) .. tMatches:get()
                    nPos = string.len(sLine)
                    redraw()
                else
                    -- [[ No matches yet, look for some now. ]]
                    local sLastPar = string.match( sLine, "[^%s]+$" ) or ""     -- Get last entered parameter.
                    local sToMatch = string.match( sLastPar, "[^%s/\.]+$" ) or ""   -- Get filename-part of sLastPar.
                    
                    -- Only look for matches if there was something afters the last slash.
                    if sToMatch and sToMatch ~= "" then
                        -- Get absolute path of the entered location.
                        local sAbsPath = shell.resolve( sLastPar )
                        -- Cut off filename, e.g. /some/test/path -> /some/test/
                        if not fs.isDir( sAbsPath ) then
                            sAbsPath = string.match( sAbsPath, "^([^%s]+[/\])[^%s/\]+[/\]?$" )
                        end
                        if not sAbsPath then sAbsPath = shell.resolve( "/" ) end
                        
                        -- Search for matches in the resolved folder.
                        local ok, tFileList = pcall( fs.list, sAbsPath )
                        if ok then
                            local match = nil
                            -- Populate table with all matches.
                            for k, v in ipairs(tFileList) do
                                match = string.match( v, "^"..sToMatch..".*$" )
                                if match then
                                    table.insert( tMatches, match )
                                end
                            end
                        end
                        
                        -- Show first match.
                        if #tMatches > 0 then
                            tMatches.pointer = 1
                            local partialMatch = string.gsub( tMatches:get(), "^"..sToMatch, "" )
                            sLine = sLine..partialMatch
                            nPos = nPos + string.len( partialMatch )
                            redraw()
                        end
                    end
                end
                
			elseif param == keys.left then
				-- Left
				if nPos > 0 then
					nPos = nPos - 1
					redraw()
				end
				
			elseif param == keys.right then
				-- Right				
				if nPos < string.len(sLine) then
					nPos = nPos + 1
					redraw()
				end
			
			elseif param == keys.up or param == keys.down then
                -- Up or down
				if _tHistory then
					redraw(" ");
					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
					redraw()
                end
			elseif param == keys.backspace then
				-- Backspace
				if nPos > 0 then
					redraw(" ");
					sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
					nPos = nPos - 1					
					redraw()
				end
			elseif param == keys.home then
				-- Home
				nPos = 0
				redraw()		
			elseif param == keys.delete then
				if nPos < string.len(sLine) then
					redraw(" ");
					sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )				
					redraw()
				end
			elseif param == keys["end"] then
				-- End
				nPos = string.len(sLine)
				redraw()
			end
		end
	end
	
	term.setCursorBlink( false )
	term.setCursorPos( w + 1, sy )
	print()
	
	return sLine
end

_G.read = read