-- command.lua, a very basic command interpreter for miniOS
--are we in miniOS?
if not miniOS then error("This program requires miniOS!", 0) end

local shell = {}

local tArgs={...}
local continue
if tArgs[1] == "-c" then continue = true table.remove(tArgs, 1)
else miniOS.cmdBat = nil end

local function intro() print(_OSVERSION .. " [".. math.floor(computer.totalMemory()/1024).."k RAM, around "..math.floor(miniOS.freeMem/1024).."k Free]" .."\nCommand Interpreter By Skye M.\n") end 
if not continue then intro() end

local history = {}
if miniOS.cmdHistory then history = miniOS.cmdHistory end
if miniOS.cmdDrive then fs.drive.setcurrent(miniOS.cmdDrive) end

local function fixPath(path)
	checkArg(1, path, "string")
	if path:sub(1,1) == '"' and path:sub(-1,-1) == '"' then path = path:sub(2, -2) end
	return path
end

local function runprog(file, parts)
	miniOS.cmdHistory = history
	miniOS.cmdDrive = fs.drive.getcurrent()
	table.remove(parts, 1)
	error({[1]="INTERRUPT", [2]="RUN", [3]=file, [4]=parts})
end

local function runbat(file, parts)
	if not miniOS.cmdBat then miniOS.cmdBat = {} end
	local lines = {}
	local line = ""
	local handle = fs.open(file)
	repeat
		local char = fs.read(handle, 1)
		if (char == "\n" or char == nil) then
			lines[#lines+1] = line
			if char == nil then line = nil end
			if char == "\n" then line = "" end
		else line = line .. char end
	until line == nil
	fs.close(handle)

	--for _,l in ipairs(lines) do print(l) end
	miniOS.cmdBat[#miniOS.cmdBat + 1] = lines
	os.exit(0)
end

local function listdrives()
	for letter, address in fs.drive.list() do
		print(letter, address)
	end
end

local function labels()
	for letter, address in fs.drive.list() do
		print(letter, component.invoke(address, "getLabel") or "")
	end
end

local function label(parts)
	proxy, reason = fs.proxy(parts[2])
	if not proxy then
		print(reason)
		return
	end
	if #parts < 3 then
		print(proxy.getLabel() or "no label")
	else
		local result, reason = proxy.setLabel(parts[3])
		if not result then print(reason or "could not set label") end
	end
end

local function outputFile(file, paged)
  local handle, reason = filesystem.open(file)
  if not handle then
    error(reason, 2)
  end
  local buffer = ""
  repeat
    local data, reason = filesystem.read(handle)
    if not data and reason then
      error(reason)
    end
    buffer = buffer .. (data or "")
  until not data
  filesystem.close(handle)
  if paged then printPaged(buffer)
  else print(buffer) end
end

local function dir(folder)
	--we will have to get the current dir later (we will need fs.resolve!)
	folder = (folder or "")
	--is it a directory?
	if not fs.isDirectory(folder) then print("No such folder.") return end
	--if it is we start...
	local output = ""
	--put the list of files into a massive string
	for file in filesystem.list(folder) do output = output .. file .. "\n" end
	--get rid of the last newline
	output = output:sub(0, -2)
	--we want the output to be paged
	printPaged(output)
end

local function moveFile(from, to, force)
	checkArg(1, from, "string")
	checkArg(2, to, "string")
	if fs.isDirectory(to) then
	    if not fs.name then error("Need to specify name for destination!", 0) end
		to = to .. "/" .. fs.name(from)
    end
	if fs.exists(to) then
		if not force then
			printErr("target file exists")
			return
		end
		fs.remove(to)
	end
	local result, reason = fs.rename(from, to)
	if not result then
		error(reason or "unknown error", 0)
	end
end

local function copyFile(from, to, force)
	checkArg(1, from, "string")
	checkArg(2, to, "string")
	if fs.isDirectory(to) then
	    if not fs.name then error("Need to specify name for destination!", 0) end
		to = to .. "/" .. fs.name(from)
    end
	if fs.exists(to) then
		if not force then
			printErr("target file exists")
			return
		end
		fs.remove(to)
	end
	local result, reason = fs.copy(from, to)
	if not result then
		error(reason or "unknown error", 0)
	end
end

local function twoFileCommandHelper(run, parts)
	if #parts >= 3 then
		if parts[2] == "-f" then
			table.remove(parts, 2)
			run(fixPath(parts[2]), fixPath(parts[3]), true)
			return true
		else
			run(fixPath(parts[2]), fixPath(parts[3]))
			return true
		end
	else printErr("Bad Parameters!") return true end
end

local function runline(line)
	checkArg(1, line, "string", "nil")
	--print(line)
	line = text.trim(line)
	if line == "" then return true end
	parts = text.tokenize(line)
	command = string.lower(text.trim(parts[1]))
	--blank commands
	if command == "" then return true end
	if command == nil then return true end
	--drive selector
	if #command == 2 then if string.sub(command, 2, 2) == ":" then filesystem.drive.setcurrent(string.sub(command, 1, 1)) return true end end

	--internal commands
	if command == "exit" then history = {} return "exit" end
	if command == "cls" then term.clear(); term.gpu().setForeground(0xFFFFFF); term.gpu().setBackground(0x000000) return true end
	if command == "ver" then print(_OSVERSION) return true end
	if command == "mem" then print(math.floor(computer.totalMemory()/1024).."k RAM, "..math.floor(computer.freeMemory()/1024).."k Free") return true end
	if command == "dir" then if parts[2] then dir(fixPath(parts[2])) else dir() end return true end
	if command == "intro" then intro() return true end
	if command == "disks" then listdrives() return true end
	if command == "discs" then listdrives() return true end
	if command == "drives" then listdrives() return true end
	if command == "labels" then labels() return true end
	if command == "scndrv" then filesystem.drive.scan() return true end
	if command == "label" then if parts[2] then label(parts) return true else printErr("Invalid Parameters") return false end end
	if command == "type" then outputFile(fixPath(parts[2])) return true end
	if command == "more" then outputFile(fixPath(parts[2]), true) return true end
	if command == "echo" then print(table.concat(parts, " ", 2)) return true end
	if command == "print" then print(table.concat(parts, "\t", 2)) return true end
	if command == "touch" then filesystem.close(filesystem.open(fixPath(parts[2]), 'w')) return true end
	if command == "del" then if filesystem.remove(fixPath(parts[2])) then return true else error("Can't delete!",0) end end
	if command == "copy" then return twoFileCommandHelper(copyFile, parts) end
	if command == "rename" then return twoFileCommandHelper(moveFile, parts) end
	if command == "ren" then return twoFileCommandHelper(moveFile, parts) end
	if command == "move" then return twoFileCommandHelper(moveFile, parts) end
  if command == "mkdir" then return filesystem.makeDirectory(fixPath(parts[2])) end
	if command == "cmds" then printPaged([[
Internal Commands:
exit --- Exit the command interpreter, Usually restarts it.
cls ---- Clears the screen.
ver ---- Outputs version information.
mem ---- Outputs memory information.
dir ---- Lists the files on the current disk or a path.
cmds --- Lists the commands.
intro -- Outputs the introduction message.
drives - Lists the drives and their addresses.
labels - Lists the drives and their labels.
scndrv - Updates the drive list.
label -- Sets the label of a drive.
echo --- Outputs its arguments.
type --- Like echo, but outputs a file.
more --- Like type, but the output is paged.
touch -- Creates a file.
del ---- Deletes a file.
copy --- Copies a file.
move --- Moves a file.
ren ---- Renames a file.
mkdir -- Creates a directory.]]) printPaged() return true end

  --external commands and programs
	command = parts[1]
	if filesystem.exists(command) then
		if not filesystem.isDirectory(command) then
			if text.endswith(command, ".lua") then runprog(command, parts) return true end
			if text.endswith(command, ".bat") then runbat(command, parts) return true end
			runprog(command, parts) return true
		end
	end
	if filesystem.exists(command .. ".lua")  then
		if not filesystem.isDirectory(command .. ".lua") then
			runprog(command .. ".lua", parts)
			return true
		end
	end
	if filesystem.exists(command .. ".bat") then
		if not filesystem.isDirectory(command .. ".bat") then
			runbat(command .. ".bat", parts)
			return true
		end
	end

	print("'" .. parts[1] .. "' is not an internal or external command, program or batch file.")
	return false
end

function shell.runline(line)
	local result = table.pack(pcall(runline, line))
	if result[1] then
		return table.unpack(result, 2, result.n)
	else
		if type(result[2]) == "table" then if result[2][1] == "INTERRUPT" then error(result[2]) end end
		printErr("ERROR:", result[2])
	end
end

if shell.runline(table.concat(tArgs, " ")) == "exit" then return end

local cmds = {"exit", "cls", "ver", "mem", "dir ", "cmds", "intro", "drives", "labels", "echo ", "type ", "more ", "touch", "del ", "copy ", "move ", "ren ", "mkdir "}

while true do
	if miniOS.cmdBat and #miniOS.cmdBat == 0 then
		miniOS.cmdBat = nil
	end

	local line
	if miniOS.cmdBat then
		while #miniOS.cmdBat > 0 do
			repeat
				line = miniOS.cmdBat[#miniOS.cmdBat][1]
				if line == nil then
					miniOS.cmdBat[#miniOS.cmdBat] = nil
					line = ""
				else
					table.remove(miniOS.cmdBat[#miniOS.cmdBat], 1)
				end
			until line ~= "" or #miniOS.cmdBat <= 0
			if line ~= "" then break end
		end
	else
		term.write(filesystem.drive.getcurrent() ..">")
		line = term.read(history, nil, function(line, pos)
      local filtered = {}
      
      local space = string.match(line, '^.*() ')
      
      if space == nil then
        for _,option in ipairs(cmds) do
          if string.sub(option, 1, #line) == line then
            filtered[#filtered + 1] = option
          end
        end
      end
      
      local preline
      if space ~= nil then
        preline = string.sub(line, 1, space)
        line = string.sub(line, space + 1)
      else
        preline = ""
      end
      local path
      local dirsep = string.match(line, '^.*()/')
      if dirsep ~= nil then
        path = string.sub(line, 1, dirsep)
      else path = "" end
      
      for file in fs.list(path) do
        file = path .. file
        if string.sub(file, 1, #line) == line and string.sub(file, -1) == '/' then
          filtered[#filtered + 1] = preline .. file
        elseif string.sub(file, 1, #line) == line and (string.sub(file, -4) == '.lua' or string.sub(file, -4) == '.bat') then
          filtered[#filtered + 1] = preline .. file .. ' '
        end
      end
      return filtered
    end)
		while #history > 10 do
			table.remove(history, 1)
		end
	end
	if shell.runline(line) == "exit" then
		if miniOS.cmdBat then miniOS.cmdBat[#miniOS.cmdBat] = nil end
		return true
	end
end