rem=rem --[[--lua
@setlocal&  set luafile="%~f0" & if exist "%~f0.bat" set luafile="%~f0.bat"
@win32\lua5.1\bin\lua5.1.exe %luafile% %*&  exit /b ]]

local vars = {}


vars.PREFIX = nil
vars.VERSION = "3.11"
vars.SYSCONFDIR = nil
vars.CONFBACKUPDIR = nil
vars.SYSCONFFILENAME = nil
vars.CONFIG_FILE = nil
vars.TREE_ROOT = nil
vars.TREE_BIN = nil
vars.TREE_LMODULE = nil
vars.TREE_CMODULE = nil
vars.LUA = nil
vars.LUA_PREFIX = nil
vars.LUA_DIR = nil
vars.LUA_BINDIR = nil
vars.LUA_INCDIR = nil
vars.LUA_LIBDIR = nil
vars.LUALIB = nil
vars.LUA_VERSION = "5.1"
vars.LUA_SHORTV = nil   -- "51"
vars.LUA_RUNTIME = nil
vars.UNAME_M = nil
vars.COMPILER_ENV_CMD = nil
vars.MINGW_BIN_PATH = nil
vars.MINGW_CC = nil
vars.MINGW_MAKE = nil
vars.MINGW_RC = nil
vars.MINGW_LD = nil
vars.MINGW_AR = nil
vars.MINGW_RANLIB = nil

local FORCE = false
local FORCE_CONFIG = false
local INSTALL_LUA = false
local USE_MINGW = false
local USE_MSVC_MANUAL = false
local REGISTRY = true
local NOADMIN = false
local PROMPT = true
local SELFCONTAINED = false

local lua_version_set = false

---
-- Some helpers
-- 

local pe = assert(loadfile(".\\win32\\pe-parser.lua"))()

local function die(message)
	if message then print(message) end
	print()
	print("Failed installing LuaRocks. Run with /? for help.")
	os.exit(1)
end

local function exec(cmd)
	--print(cmd)
	local status = os.execute("type NUL && "..cmd)
	return (status == 0 or status == true) -- compat 5.1/5.2
end

local function exists(filename)
	local fd, _, code = io.open(filename, "r")
	if code == 13 then
		-- code 13 means "Permission denied" on both Unix and Windows
		-- io.open on folders always fails with code 13 on Windows
		return true
	end
	if fd then
		fd:close()
		return true
	end
	return false
end

local function mkdir (dir)
	return exec([[MKDIR "]]..dir..[[" >NUL]])
end

-- does the current user have admin privileges ( = elevated)
local function permission()
	return exec("net session >NUL 2>&1") -- fails if not admin
end

-- rename filename (full path) to backupname (name only), appending number if required
-- returns the new name (name only)
local function backup(filename, backupname)
	local path = filename:match("(.+)%\\.-$").."\\"
	local nname = backupname
	local i = 0
	while exists(path..nname) do
		i = i + 1
		nname = backupname..tostring(i)
	end
	exec([[REN "]]..filename..[[" "]]..nname..[[" > NUL]])
	return nname
end

-- interpolate string with values from 'vars' table
local function S (tmpl)
	return (tmpl:gsub('%$([%a_][%w_]*)', vars))
end

local function print_help()
	print(S[[
Installs LuaRocks.

/P [dir]       Where to install LuaRocks. 
               Default is %PROGRAMFILES%\LuaRocks

Configuring the destinations:
/TREE [dir]    Root of the local system tree of installed rocks.
               Default is {BIN}\..\ if {BIN} ends with '\bin'
               otherwise it is {BIN}\systree. 
/SCRIPTS [dir] Where to install commandline scripts installed by
               rocks. Default is {TREE}\bin.
/LUAMOD [dir]  Where to install Lua modules installed by rocks.
               Default is {TREE}\share\lua\{LV}.
/CMOD [dir]    Where to install c modules installed by rocks.
               Default is {TREE}\lib\lua\{LV}.
/CONFIG [dir]  Location where the config file should be installed.
               Default is to follow /P option
/SELFCONTAINED Creates a self contained installation in a single
               directory given by /P.
               Sets the /TREE and /CONFIG options to the same 
               location as /P. And does not load registry info
               with option /NOREG. The only option NOT self
               contained is the user rock tree, so don't use that
               if you create a self contained installation.
               
Configuring the Lua interpreter:
/LV [version]  Lua version to use; either 5.1, 5.2, 5.3, or 5.4.
               Default is auto-detected.
/LUA [dir]     Location where Lua is installed - e.g. c:\lua\5.1\
               If not provided, the installer will search the system
               path and some default locations for a valid Lua
               installation.
               This is the base directory, the installer will look
               for subdirectories bin, lib, include. Alternatively
               these can be specified explicitly using the /INC,
               /LIB, and /BIN options.
/INC [dir]     Location of Lua includes - e.g. c:\lua\5.1\include
               If provided overrides sub directory found using /LUA.
/LIB [dir]     Location of Lua libraries (.dll/.lib) - e.g. c:\lua\5.1\lib
               If provided overrides sub directory found using /LUA.
/BIN [dir]     Location of Lua executables - e.g. c:\lua\5.1\bin
               If provided overrides sub directory found using /LUA.
/L             Install LuaRocks' own copy of Lua even if detected,
               this will always be a 5.1 installation.
               (/LUA, /INC, /LIB, /BIN cannot be used with /L)

Compiler configuration:
               By default the installer will try to determine the 
               Microsoft toolchain to use. And will automatically use 
               a setup command to initialize that toolchain when 
               LuaRocks is run. If it cannot find it, it will default 
               to the /MSVC switch.
/MSVC          Use MS toolchain, without a setup command (tools must
               be in your path)
/MW            Use mingw as build system (tools must be in your path)

Other options:
/FORCECONFIG   Use a single config location. Do not use the
               LUAROCKS_CONFIG variable or the user's home directory.
               Useful to avoid conflicts when LuaRocks
               is embedded within an application.
/F             Remove installation directory if it already exists.
/NOREG         Do not load registry info to register '.rockspec'
               extension with LuaRocks commands (right-click).
/NOADMIN       The installer requires admin privileges. If not
               available it will elevate a new process. Use this
               switch to prevent elevation, but make sure the
               destination paths are all accessible for the current
               user.
/Q             Do not prompt for confirmation of settings

]])
end

-- ***********************************************************
-- Option parser
-- ***********************************************************
local function parse_options(args)
	for _, option in ipairs(args) do
		local name = option.name:upper()
		if name == "/?" then
			print_help()
			os.exit(0)
		elseif name == "/P" then
			vars.PREFIX = option.value
		elseif name == "/CONFIG" then
			vars.SYSCONFDIR = option.value
		elseif name == "/TREE" then
			vars.TREE_ROOT = option.value
		elseif name == "/SCRIPTS" then
			vars.TREE_BIN = option.value
		elseif name == "/LUAMOD" then
			vars.TREE_LMODULE = option.value
		elseif name == "/CMOD" then
			vars.TREE_CMODULE = option.value
		elseif name == "/LV" then
			vars.LUA_VERSION = option.value
			lua_version_set = true
		elseif name == "/L" then
			INSTALL_LUA = true
		elseif name == "/MW" then
			USE_MINGW = true
		elseif name == "/MSVC" then
			USE_MSVC_MANUAL = true
		elseif name == "/LUA" then
			vars.LUA_PREFIX = option.value
		elseif name == "/LIB" then
			vars.LUA_LIBDIR = option.value
		elseif name == "/INC" then
			vars.LUA_INCDIR = option.value
		elseif name == "/BIN" then
			vars.LUA_BINDIR = option.value
		elseif name == "/FORCECONFIG" then
			FORCE_CONFIG = true
		elseif name == "/F" then
			FORCE = true
		elseif name == "/SELFCONTAINED" then
			SELFCONTAINED = true
		elseif name == "/NOREG" then
			REGISTRY = false
		elseif name == "/NOADMIN" then
			NOADMIN = true
		elseif name == "/Q" then
			PROMPT = false
		else
			die("Unrecognized option: " .. name)
		end
	end
end

-- check for combination/required flags
local function check_flags()
	if SELFCONTAINED then
		if not vars.PREFIX then
			die("Option /P is required when using /SELFCONTAINED")
		end
		if vars.SYSCONFDIR or vars.TREE_ROOT or vars.TREE_BIN or vars.TREE_LMODULE or vars.TREE_CMODULE then
			die("Cannot combine /TREE, /SCRIPTS, /LUAMOD, /CMOD, or /CONFIG with /SELFCONTAINED")
		end
	end
	if INSTALL_LUA then
		if vars.LUA_INCDIR or vars.LUA_BINDIR or vars.LUA_LIBDIR or vars.LUA_PREFIX then
			die("Cannot combine option /L with any of /LUA /BIN /LIB /INC")
		end
		if vars.LUA_VERSION ~= "5.1" then
			die("Bundled Lua version is 5.1, cannot install "..vars.LUA_VERSION)
		end
	end
	if not vars.LUA_VERSION:match("^5%.[1234]$") then
		die("Bad argument: /LV must either be 5.1, 5.2, 5.3, or 5.4")
	end
  if USE_MSVC_MANUAL and USE_MINGW then
    die("Cannot combine option /MSVC and /MW")
  end
end

-- ***********************************************************
-- Detect Lua
-- ***********************************************************
local function detect_lua_version(interpreter_path)
	local handler = io.popen(('type NUL && "%s" -e "io.stdout:write(_VERSION)" 2>NUL'):format(interpreter_path), "r")
	if not handler then
		return nil, "interpreter does not work"
	end
	local full_version = handler:read("*a")
	handler:close()

	local version = full_version:match(" (5%.[1234])$")
	if not version then
		return nil, "unknown interpreter version '" .. full_version .. "'"
	end
	return version
end

local function look_for_interpreter(directory)
	local names
	if lua_version_set then
		names = {S"lua$LUA_VERSION.exe", S"lua$LUA_SHORTV.exe"}
	else
		names = {"lua5.4.exe", "lua54.exe", "lua5.3.exe", "lua53.exe", "lua5.2.exe", "lua52.exe", "lua5.1.exe", "lua51.exe"}
	end
	table.insert(names, "lua.exe")
	table.insert(names, "luajit.exe")

	local directories
	if vars.LUA_BINDIR then
		-- If LUA_BINDIR is specified, look only in that directory.
		directories = {vars.LUA_BINDIR}
	else
		-- Try candidate directory and its `bin` subdirectory.
		directories = {directory, directory .. "\\bin"}
	end

	for _, dir in ipairs(directories) do
		for _, name in ipairs(names) do
			local full_name = dir .. "\\" .. name
			if exists(full_name) then
				print("       Found " .. name .. ", testing it...")
				local version, err = detect_lua_version(full_name)
				if not version then
					print("       Error: " .. err)
				else
					if version ~= vars.LUA_VERSION then
						if lua_version_set then
							die("Version of interpreter clashes with the value of /LV. Please check your configuration.")
						else
							vars.LUA_VERSION = version
							vars.LUA_SHORTV = version:gsub("%.", "")
						end
					end

					vars.LUA = full_name
					vars.LUA_BINDIR = dir
					return true
				end
			end
		end
	end

	if vars.LUA_BINDIR then
		die(("Working Lua executable (one of %s) not found in %s"):format(table.concat(names, ", "), vars.LUA_BINDIR))
	end
	return false
end

local function look_for_link_libraries(directory)
	-- MinGW does not generate .lib, nor needs it to link, but MSVC does,
	-- so .lib must be listed first to ensure they are found first if present,
	-- to prevent MSVC trying to link to a .dll, which won't work.
	local names = {S"lua$LUA_VERSION.lib", S"lua$LUA_SHORTV.lib", S"liblua$LUA_SHORTV.a", S"lua$LUA_VERSION.dll", S"lua$LUA_SHORTV.dll", "liblua.dll.a"}
	local directories
	if vars.LUA_LIBDIR then
		directories = {vars.LUA_LIBDIR}
	else
		directories = {directory, directory .. "\\lib", directory .. "\\bin"}
	end

	for _, dir in ipairs(directories) do
		for _, name in ipairs(names) do
			local full_name = dir .. "\\" .. name
			print("    checking for " .. full_name)
			if exists(full_name) then
				vars.LUA_LIBDIR = dir
				vars.LUALIB = name
				print("       Found " .. name)
				return true
			end
		end
	end

	if vars.LUA_LIBDIR then
		die(("Link library (one of %s) not found in %s"):format(table.concat(names, ", "), vars.LUA_LIBDIR))
	end
	return false
end

local function look_for_headers(directory)
	local directories
	if vars.LUA_INCDIR then
		directories = {vars.LUA_INCDIR}
	else
		directories = {
			directory .. S"\\include\\lua\\$LUA_VERSION",
			directory .. S"\\include\\lua$LUA_SHORTV",
			directory .. S"\\include\\lua$LUA_VERSION",
			directory .. "\\include",
			directory
		}
	end

	for _, dir in ipairs(directories) do
		local full_name = dir .. "\\lua.h"
		print("    checking for " .. full_name)
		if exists(full_name) then
			vars.LUA_INCDIR = dir
			print("       Found lua.h")
			return true
		end
	end

	if vars.LUA_INCDIR then
		die(S"lua.h not found in $LUA_INCDIR")
	end
	return false
end


local function get_runtime()
	local f
	vars.LUA_RUNTIME, f = pe.msvcrt(vars.LUA)
	if type(vars.LUA_RUNTIME) ~= "string" then
		-- analysis failed, issue a warning
		vars.LUA_RUNTIME = "MSVCR80"
		print("*** WARNING ***: could not analyse the runtime used, defaulting to "..vars.LUA_RUNTIME)
	else
		print("    "..f.." uses "..vars.LUA_RUNTIME..".DLL as runtime")
	end
	return true
end

local function get_architecture()
	-- detect processor arch interpreter was compiled for
	local proc = (pe.parse(vars.LUA) or {}).Machine
	if not proc then
		die("Could not detect processor architecture used in "..vars.LUA)
	end
	print("arch: " .. proc .. " -> " .. pe.const.Machine[proc])
	proc = pe.const.Machine[proc]  -- collect name from constant value
	if proc == "IMAGE_FILE_MACHINE_I386" then
		proc = "x86"
	elseif proc == "IMAGE_FILE_MACHINE_ARM64" then
		proc = "arm64"
	else
		proc = "x86_64"
	end
	return proc
end

-- get a string value from windows registry.
local function get_registry(key, value)
	local keys = {key}
	local key64, replaced = key:gsub("(%u+\\Software\\)", "%1Wow6432Node\\", 1)

	if replaced == 1 then
		keys = {key64, key}
	end

	for _, k in ipairs(keys) do
		local h = io.popen('reg query "'..k..'" /v '..value..' 2>NUL')
		local output = h:read("*a")
		h:close()

		local v = output:match("REG_SZ%s+([^\n]+)")
		if v then
			return v
		end
	end
	return nil
end

local function get_visual_studio_directory_from_registry()
	assert(type(vars.LUA_RUNTIME)=="string", "requires vars.LUA_RUNTIME to be set before calling this function.")
	local major, minor = vars.LUA_RUNTIME:match('VCR%u*(%d+)(%d)$') -- MSVCR<x><y> or VCRUNTIME<x><y>
	if not major then 
    print(S[[    Cannot auto-detect Visual Studio version from $LUA_RUNTIME]])
    return nil 
  end
	local keys = {
		"HKLM\\Software\\Microsoft\\VisualStudio\\%d.%d\\Setup\\VC",
		"HKLM\\Software\\Microsoft\\VCExpress\\%d.%d\\Setup\\VS"
	}
	for _, key in ipairs(keys) do
    local versionedkey = key:format(major, minor)
		local vcdir = get_registry(versionedkey, "ProductDir")
    print("    checking: "..versionedkey)
		if vcdir then 
      print("        Found: "..vcdir)
      return vcdir 
    end
	end
	return nil
end

local function get_visual_studio_directory_from_vswhere()
	assert(type(vars.LUA_RUNTIME)=="string", "requires vars.LUA_RUNTIME to be set before calling this function.")
	local major, minor = vars.LUA_RUNTIME:match('VCR%u*(%d+)(%d)$')
	if not major then
		print(S[[    Cannot auto-detect Visual Studio version from $LUA_RUNTIME]])
		return nil
	end
	if tonumber(major) < 14 then
		return nil
	end
	local program_dir = os.getenv('PROGRAMFILES(X86)')
	if not program_dir then
		return nil
	end
	local vswhere = program_dir.."\\Microsoft Visual Studio\\Installer\\vswhere.exe"
	if not exists(vswhere) then
		return nil
	end
	local f, msg = io.popen('"'..vswhere..'" -products * -property installationPath')
	if not f then return nil, "failed to run vswhere: "..msg end
	local vsdir = nil
	while true do
		local l, err = f:read()
		if not l then
			if err then
				f:close()
				return nil, err
			else
				break
			end
		end
		vsdir = l
	end
	f:close()
	if not vsdir then
		return nil
	end
	print("    Visual Studio 2017 or higher found in: "..vsdir)
	return vsdir
end

local function get_windows_sdk_directory()
	assert(type(vars.LUA_RUNTIME) == "string", "requires vars.LUA_RUNTIME to be set before calling this function.")
	-- Only v7.1 and v6.1 shipped with compilers
	-- Other versions requires a separate  installation of Visual Studio.
	-- see https://github.com/luarocks/luarocks/pull/443#issuecomment-152792516
	local wsdks = {
		["MSVCR100"] = "v7.1", -- shipped with Visual Studio 2010 compilers.
		["MSVCR100D"] = "v7.1", -- shipped with Visual Studio 2010 compilers.
		["MSVCR90"] = "v6.1", -- shipped with Visual Studio 2008 compilers.
		["MSVCR90D"] = "v6.1", -- shipped with Visual Studio 2008 compilers.
	}
	local wsdkver = wsdks[vars.LUA_RUNTIME]
	if not wsdkver then
    print(S[[    Cannot auto-detect Windows SDK version from $LUA_RUNTIME]])
		return nil
	end

	local key = "HKLM\\Software\\Microsoft\\Microsoft SDKs\\Windows\\"..wsdkver
  print("   checking: "..key)
  local dir = get_registry(key, "InstallationFolder")
  if dir then
    print("        Found: "..dir)
    return dir
  end
  print("        No SDK found")
	return nil
end

-- returns the batch command to setup msvc compiler path.
-- or an empty string (eg. "") if not found
local function get_msvc_env_setup_cmd()
  print(S[[Looking for Microsoft toolchain matching runtime $LUA_RUNTIME and architecture $UNAME_M]])

	assert(type(vars.UNAME_M) == "string", "requires vars.UNAME_M to be set before calling this function.")
	local x64 = vars.UNAME_M=="x86_64"

	-- 1. try visual studio command line tools of VS 2017 or higher
	local vsdir, err = get_visual_studio_directory_from_vswhere()
	if err then
		print("    Error when finding Visual Studio directory from vswhere: "..err)
	end
	if vsdir then
		local vcvarsall = vsdir .. '\\VC\\Auxiliary\\Build\\vcvarsall.bat'
		if exists(vcvarsall) then
			local vcvarsall_args = { x86 = "", x86_64 = " x64", arm64 = " x86_arm64" }
			assert(vcvarsall_args[vars.UNAME_M], "vars.UNAME_M: only x86, x86_64 and arm64 are supported")
			return ('call "%s"%s > NUL'):format(vcvarsall, vcvarsall_args[vars.UNAME_M])
		end
	end

	-- 2. try visual studio command line tools
	local vcdir = get_visual_studio_directory_from_registry()
	if vcdir then
		local vcvars_bats = {
			x86 = {
				"bin\\vcvars32.bat", -- prefers native compiler
				"bin\\amd64_x86\\vcvarsamd64_x86.bat"-- then cross compiler
			},
			x86_64 = {
				"bin\\amd64\\vcvars64.bat", -- prefers native compiler
				"bin\\x86_amd64\\vcvarsx86_amd64.bat" -- then cross compiler
			},
			arm64 = {
				"bin\\x86_arm64\\vcvarsx86_arm64.bat" -- need to use cross compiler"
			}
		}
		assert(vcvars_bats[vars.UNAME_M], "vars.UNAME_M: only x86, arm64 and x86_64 are supported")
		for _, bat in ipairs(vcvars_bats[vars.UNAME_M]) do
			local full_path = vcdir .. bat
			if exists(full_path) then
				return ('call "%s" > NUL'):format(full_path)
			end
		end

		-- try vcvarsall.bat in case MS changes the undocumented bat files above.
		-- but this way we don't know if specified compiler is installed...
		local vcvarsall = vcdir .. 'vcvarsall.bat'
		if exists(vcvarsall) then
			local vcvarsall_args = { x86 = "", x86_64 = " amd64", arm64 = " x86_arm64" }
			return ('call "%s"%s > NUL'):format(vcvarsall, vcvarsall_args[vars.UNAME_M])
		end
	end

	-- 3. try for Windows SDKs command line tools.
	local wsdkdir = get_windows_sdk_directory()
	if wsdkdir then
		local setenv = wsdkdir.."Bin\\SetEnv.cmd"
		if exists(setenv) then
			return ('call "%s" /%s > NUL'):format(setenv, x64 and "x64" or "x86")
		end
	end

	-- finally, we can't detect more, just don't setup the msvc compiler in luarocks.bat.
	return ""
end

local function get_possible_lua_directories()
	if vars.LUA_PREFIX then
		return {vars.LUA_PREFIX}
	end

	-- No prefix given, so use PATH.
	local path = os.getenv("PATH") or ""
	local directories = {}
	for dir in path:gmatch("[^;]+") do
		-- Remove trailing backslashes, but not from a drive letter like `C:\`.
		dir = dir:gsub("([^:])\\+$", "%1")
		-- Remove trailing `bin` subdirectory, the searcher will check there anyway.
		if dir:upper():match("[:\\]BIN$") then
			dir = dir:sub(1, -5)
		end
		table.insert(directories, dir)
	end
	-- Finally add some other default paths.
	table.insert(directories, [[c:\lua5.1.2]])
	table.insert(directories, [[c:\lua]])
	table.insert(directories, [[c:\kepler\1.1]])
	return directories
end

local function strip_bin(bindir)
	bindir = bindir:gsub("[/\\]*$", "")
	if bindir:upper():match("[/\\]BIN") then
		bindir = bindir:sub(1, -5):gsub("[/\\]*$", "")
	end
	return bindir
end

local function look_for_lua_install ()
	print("Looking for Lua interpreter")
	if vars.LUA_BINDIR and vars.LUA_LIBDIR and vars.LUA_INCDIR then
		if look_for_interpreter(vars.LUA_BINDIR) and 
			look_for_link_libraries(vars.LUA_LIBDIR) and
			look_for_headers(vars.LUA_INCDIR)
		then
			if get_runtime() then
				print("Runtime check completed.")
				vars.LUA_DIR = vars.LUA_PREFIX or strip_bin(vars.LUA_BINDIR)
				return true
			end
		end
		return false
	end

	for _, directory in ipairs(get_possible_lua_directories()) do
		print("    checking " .. directory)
		if exists(directory) then
			if look_for_interpreter(directory) then
				print("Interpreter found, now looking for link libraries...")
				if look_for_link_libraries(directory) then
					print("Link library found, now looking for headers...")
					if look_for_headers(directory) then
						print("Headers found, checking runtime to use...")
						if get_runtime() then
							print("Runtime check completed.")
							vars.LUA_DIR = directory
							return true
						end
					end
				end
			end
		end
	end
	return false
end

-- backup config[x.x].lua[.bak]
local function backup_config_files()
  local temppath
  while not temppath do
    temppath = os.getenv("temp").."\\LR-config-backup-"..tostring(math.random(10000))
    if exists(temppath) then temppath = nil end
  end
  vars.CONFBACKUPDIR = temppath
  mkdir(vars.CONFBACKUPDIR)
  exec(S[[COPY "$PREFIX\config*.*" "$CONFBACKUPDIR" >NUL]])
end

-- restore previously backed up config files
local function restore_config_files()
  if not vars.CONFBACKUPDIR then return end -- there is no backup to restore
  exec(S[[COPY "$CONFBACKUPDIR\config*.*" "$PREFIX" >NUL]])
  -- cleanup
  exec(S[[RD /S /Q "$CONFBACKUPDIR"]])
  vars.CONFBACKUPDIR = nil
end

-- Find GCC based toolchain
local find_gcc_suite = function()

    -- read output os-command
    local read_output = function(cmd)
        local f = io.popen("type NUL && " .. cmd .. ' 2>NUL')
        if not f then return nil, "failed to open command: " .. tostring(cmd) end
        local lines = {}
        while true do
            local l = f:read()
            if not l then
                f:close()
                return lines
            end
            table.insert(lines, l)
        end
    end
    
    -- returns: full filename, path, filename
    local find_file = function(mask, path)
        local cmd
        if path then
            cmd = 'where.exe /R "' .. path .. '" ' .. mask
        else
            cmd = 'where.exe ' .. mask
        end
        local files, err = read_output(cmd)
        if not files or not files[1] then
            return nil, "couldn't find '".. mask .. "', " .. (err or "not found")
        end
        local path, file = string.match(files[1], "^(.+)%\\([^%\\]+)$")
        return files[1], path, file
    end

    local first_one = "*gcc.exe"  -- first file we're assuming to point to the compiler suite
    local full, path, filename = find_file(first_one, nil)
    if not full then
        return nil, path
    end
    vars.MINGW_BIN_PATH = path

    local result = {
        gcc = full
    }
    for i, name in ipairs({"make", "ar", "windres", "ranlib"}) do
        result[name] = find_file(name..".exe", path)
        if not result[name] then
            result[name] = find_file("*"..name.."*.exe", path)
        end
    end

    vars.MINGW_MAKE = (result.make and '[['..result.make..']]') or "nil,  -- not found by installer"
    vars.MINGW_CC = (result.gcc and '[['..result.gcc..']]') or "nil,  -- not found by installer"
    vars.MINGW_RC = (result.windres and '[['..result.windres..']]') or "nil,  -- not found by installer"
    vars.MINGW_LD = (result.gcc and '[['..result.gcc..']]') or "nil,  -- not found by installer"
    vars.MINGW_AR = (result.ar and '[['..result.ar..']]') or "nil,  -- not found by installer"
    vars.MINGW_RANLIB = (result.ranlib and '[['..result.ranlib..']]') or "nil,  -- not found by installer"
    return true
end

-- ***********************************************************
-- Installer script start
-- ***********************************************************

-- Poor man's command-line parsing
local config = {}
local with_arg = { -- options followed by an argument, others are flags
	["/P"] = true,
	["/CONFIG"] = true,
	["/TREE"] = true,
	["/SCRIPTS"] = true,
	["/LUAMOD"] = true,
	["/CMOD"] = true,
	["/LV"] = true,
	["/LUA"] = true,
	["/INC"] = true,
	["/BIN"] = true,
	["/LIB"] = true,
}
-- reconstruct argument values with spaces and double quotes
-- this will be damaged by the batch construction at the start of this file
local oarg = arg  -- retain old table
if #arg > 0 then
	local farg = table.concat(arg, " ") .. " "
	arg = {}
	farg = farg:gsub('%"', "")
	local i = 0
	while #farg>0 do
		i = i + 1
		if (farg:sub(1,1) ~= "/") and ((arg[i-1] or ""):sub(1,1) ~= "/") then
			i = i - 1       -- continued previous arg
			if i == 0 then i = 1 end
		end
		if arg[i] then
			arg[i] = arg[i] .. " "
		else
			arg[i] = ""
		end
		local v,r = farg:match("^(.-)%s(.*)$")
		arg[i], farg = arg[i]..v, r
		while farg:sub(1,1) == " " do farg = farg:sub(2,-1) end	-- remove prefix spaces
	end
end
for k,v in pairs(oarg) do if k < 1 then arg[k] = v end end -- copy 0 and negative indexes

-- build config option table with name and value elements
local i = 1
while i <= #arg do
	local opt = arg[i]
	if with_arg[opt:upper()] then
		local value = arg[i + 1]
		if not value then
			die("Missing value for option "..opt)
		end
		config[#config + 1] = { name = opt, value = value }
		i = i + 1
	else
		config[#config + 1] = { name = opt }
	end
	i = i + 1
end

print(S"LuaRocks $VERSION.x installer.\n")

parse_options(config)

print([[

========================
== Checking system... ==
========================

]])

check_flags()

if not permission() then
	if not NOADMIN then
		-- must elevate the process with admin privileges
        if not exec("PowerShell /? >NUL 2>&1") then
          -- powershell is not available, so error out
          die("No administrative privileges detected and cannot auto-elevate. Please run with admin privileges or use the /NOADMIN switch")
        end
		print("Need admin privileges, now elevating a new process to continue installing...")
		local runner = os.getenv("TEMP").."\\".."LuaRocks_Installer.bat"
		local f = io.open(runner, "w")
		f:write("@echo off\n")
		f:write("CHDIR /D "..arg[0]:match("(.+)%\\.-$").."\n")  -- return to current dir, elevation changes current path
		f:write('"'..arg[-1]..'" "'..table.concat(arg, '" "', 0)..'"\n')
		f:write("ECHO Press any key to close this window...\n")
		f:write("PAUSE > NUL\n")
		f:write('DEL "'..runner..'"')  -- temp batch file deletes itself
		f:close()
		-- run the created temp batch file in elevated mode
		exec("PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('"..runner.."', '', '', 'runas')\n")
		print("Now exiting unprivileged installer")
       	os.exit()  -- exit here, the newly created elevated process will do the installing
	else
		print("Attempting to install without admin privileges...")
	end
else
	print("Admin privileges available for installing")
end

vars.PREFIX = vars.PREFIX or os.getenv("PROGRAMFILES")..[[\LuaRocks]]
vars.BINDIR = vars.PREFIX
vars.LIBDIR = vars.PREFIX
vars.LUADIR = S"$PREFIX\\lua"
vars.INCDIR = S"$PREFIX\\include"
vars.LUA_SHORTV = vars.LUA_VERSION:gsub("%.", "")

if INSTALL_LUA then
	vars.LUA = vars.BINDIR .. "\\lua5.1.exe"
	vars.LUA_DIR = vars.PREFIX
	vars.LUA_BINDIR = vars.BINDIR
	vars.LUA_LIBDIR = vars.LIBDIR
	vars.LUA_INCDIR = vars.INCDIR
	vars.LUALIB = "lua5.1.lib"
	vars.LUA_RUNTIME = "MSVCR80"
	vars.UNAME_M = "x86"
else
	if not look_for_lua_install() then
		die("Could not find Lua. See /? for options for specifying the location of Lua, or installing a bundled copy of Lua 5.1.")
	end
    vars.UNAME_M = get_architecture()  -- can only do when installation was found
end

assert(vars.LUA)
assert(vars.LUA_DIR)

-- check location of system tree
if not vars.TREE_ROOT then
  -- no system tree location given, so we need to construct a default value
  if vars.LUA_BINDIR:lower():match([[([\/]+bin[\/]*)$]]) then
    -- lua binary is located in a 'bin' subdirectory, so assume
    -- default Lua layout and match rocktree on top
    vars.TREE_ROOT = vars.LUA_BINDIR:lower():gsub([[[\/]+bin[\/]*$]], [[\]])
  else
    -- no 'bin', so use a named tree next to the Lua executable
    vars.TREE_ROOT = vars.LUA_BINDIR .. [[\systree]]
  end
end

vars.SYSCONFDIR = vars.SYSCONFDIR or vars.PREFIX
vars.SYSCONFFILENAME = S"config-$LUA_VERSION.lua"
vars.CONFIG_FILE = vars.SYSCONFDIR.."\\"..vars.SYSCONFFILENAME
if SELFCONTAINED then
	vars.SYSCONFDIR = vars.PREFIX
	vars.TREE_ROOT = vars.PREFIX..[[\systree]]
	REGISTRY = false
end
if USE_MINGW then
    vars.COMPILER_ENV_CMD = ""
    local found, err = find_gcc_suite()
    if not found then
        die("Failed to find MinGW/gcc based toolchain, make sure it is in your path: " .. tostring(err))
    end
else
    vars.COMPILER_ENV_CMD = (USE_MSVC_MANUAL and "") or get_msvc_env_setup_cmd()
end

print(S[[

==========================
== System check results ==
==========================

Will configure LuaRocks with the following paths:
LuaRocks        : $PREFIX
Config file     : $CONFIG_FILE
Rocktree        : $TREE_ROOT

Lua interpreter : $LUA
    binaries    : $LUA_BINDIR
    libraries   : $LUA_LIBDIR
    includes    : $LUA_INCDIR
    architecture: $UNAME_M
    binary link : $LUALIB with runtime $LUA_RUNTIME.dll
]])

if USE_MINGW then
  print(S[[Compiler        : MinGW/gcc (make sure it is in your path before using LuaRocks)]])
  print(S[[                  in: $MINGW_BIN_PATH]])
else
  if vars.COMPILER_ENV_CMD == "" then
    print("Compiler        : Microsoft (make sure it is in your path before using LuaRocks)")
  else
    print(S[[Compiler        : Microsoft, using; $COMPILER_ENV_CMD]])
  end
end

if PROMPT then
	print("\nPress <ENTER> to start installing, or press <CTRL>+<C> to abort. Use install /? for installation options.")
	io.read()
end

print([[

============================
== Installing LuaRocks... ==
============================

]])

-- ***********************************************************
-- Install LuaRocks files
-- ***********************************************************

if exists(vars.PREFIX) then
  if not FORCE then
    die(S"$PREFIX exists. Use /F to force removal and reinstallation.")
  else
    backup_config_files()
    print(S"Removing $PREFIX...")
    exec(S[[RD /S /Q "$PREFIX"]])
    print()
  end
end

print(S"Installing LuaRocks in $PREFIX...")
if not exists(vars.BINDIR) then
	if not mkdir(vars.BINDIR) then
		die()
	end
end

if INSTALL_LUA then
	-- Copy the included Lua interpreter binaries
	if not exists(vars.LUA_BINDIR) then
		mkdir(vars.LUA_BINDIR)
	end
	if not exists(vars.LUA_INCDIR) then
		mkdir(vars.LUA_INCDIR)
	end
	exec(S[[COPY win32\lua5.1\bin\*.* "$LUA_BINDIR" >NUL]])
	exec(S[[COPY win32\lua5.1\include\*.* "$LUA_INCDIR" >NUL]])
	print(S"Installed the LuaRocks bundled Lua interpreter in $LUA_BINDIR")
end

-- Copy the LuaRocks binaries
if not exists(S[[$BINDIR\tools]]) then
	if not mkdir(S[[$BINDIR\tools]]) then
		die()
	end
end
if not exec(S[[COPY win32\tools\*.* "$BINDIR\tools" >NUL]]) then
	die()
end
-- Copy LR bin helper files
if not exec(S[[COPY win32\*.* "$BINDIR" >NUL]]) then
	die()
end
-- Copy the LuaRocks lua source files
if not exists(S[[$LUADIR\luarocks]]) then
	if not mkdir(S[[$LUADIR\luarocks]]) then
		die()
	end
end
if not exec(S[[XCOPY /S src\luarocks\*.* "$LUADIR\luarocks" >NUL]]) then
	die()
end
-- Create start scripts
if not exec(S[[COPY src\bin\*.* "$BINDIR" >NUL]]) then
	die()
end
for _, c in ipairs{"luarocks", "luarocks-admin"} do
	-- rename unix-lua scripts to .lua files
	if not exec( (S[[RENAME "$BINDIR\%s" %s.lua]]):format(c, c) ) then
		die()
	end
	-- create a bootstrap batch file for the lua file, to start them
	exec(S[[DEL /F /Q "$BINDIR\]]..c..[[.bat" 2>NUL]])
	local f = io.open(vars.BINDIR.."\\"..c..".bat", "w")
	f:write(S[[
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
$COMPILER_ENV_CMD
SET "LUA_PATH=$LUADIR\?.lua;$LUADIR\?\init.lua;%LUA_PATH%"
IF NOT "%LUA_PATH_5_2%"=="" (
   SET "LUA_PATH_5_2=$LUADIR\?.lua;$LUADIR\?\init.lua;%LUA_PATH_5_2%"
)
IF NOT "%LUA_PATH_5_3%"=="" (
   SET "LUA_PATH_5_3=$LUADIR\?.lua;$LUADIR\?\init.lua;%LUA_PATH_5_3%"
)
SET "PATH=$BINDIR;%PATH%"
"$LUA" "$BINDIR\]]..c..[[.lua" %*
SET EXITCODE=%ERRORLEVEL%
IF NOT "%EXITCODE%"=="2" GOTO EXITLR

REM Permission denied error, try and auto elevate...
REM already an admin? (checking to prevent loops)
NET SESSION >NUL 2>&1
IF "%ERRORLEVEL%"=="0" GOTO EXITLR

REM Do we have PowerShell available?
PowerShell /? >NUL 2>&1
IF NOT "%ERRORLEVEL%"=="0" GOTO EXITLR

:GETTEMPNAME
SET TMPFILE=%TEMP%\LuaRocks-Elevator-%RANDOM%.bat
IF EXIST "%TMPFILE%" GOTO :GETTEMPNAME 

ECHO @ECHO OFF                                  >  "%TMPFILE%"
ECHO CHDIR /D %CD%                              >> "%TMPFILE%"
ECHO ECHO %0 %*                                 >> "%TMPFILE%"
ECHO ECHO.                                      >> "%TMPFILE%"
ECHO CALL %0 %*                                 >> "%TMPFILE%"
ECHO ECHO.                                      >> "%TMPFILE%"
ECHO ECHO Press any key to close this window... >> "%TMPFILE%"
ECHO PAUSE ^> NUL                               >> "%TMPFILE%"
ECHO DEL "%TMPFILE%"                            >> "%TMPFILE%"

ECHO Now retrying as a privileged user...
PowerShell -Command (New-Object -com 'Shell.Application').ShellExecute('%TMPFILE%', '', '', 'runas')

:EXITLR
exit /b %EXITCODE% 
]])
	f:close()
	print(S"Created LuaRocks command: $BINDIR\\"..c..".bat")
end

-- ***********************************************************
-- Configure LuaRocks
-- ***********************************************************

restore_config_files()
print()
print("Configuring LuaRocks...")

-- Create hardcoded.lua

local hardcoded_lua = S[[$LUADIR\luarocks\core\hardcoded.lua]]

os.remove(hardcoded_lua)

vars.SYSTEM = USE_MINGW and "mingw" or "windows"

local f = io.open(hardcoded_lua, "w")
f:write(S[=[
return {
   LUA_DIR=[[$LUA_DIR]],
   LUA_INCDIR=[[$LUA_INCDIR]],
   LUA_LIBDIR=[[$LUA_LIBDIR]],
   LUA_BINDIR=[[$LUA_BINDIR]],
   LUA_VERSION=[[$LUA_VERSION]],
   LUA=[[$LUA]],
   SYSTEM = [[$SYSTEM]],
   PROCESSOR = [[$UNAME_M]],
   PREFIX = [[$PREFIX]],
   SYSCONFDIR = [[$SYSCONFDIR]],
   WIN_TOOLS = [[$PREFIX/tools]],
]=])
if FORCE_CONFIG then
	f:write("   FORCE_CONFIG = true,\n")
end
f:write("}\n")
f:close()
print(S([[Created LuaRocks hardcoded settings file: $LUADIR\luarocks\core\hardcoded.lua]]))

-- create config file
if not exists(vars.SYSCONFDIR) then
	mkdir(vars.SYSCONFDIR)
end
if exists(vars.CONFIG_FILE) then
	local nname = backup(vars.CONFIG_FILE, vars.SYSCONFFILENAME..".bak")
	print("***************")
	print(S"*** WARNING *** LuaRocks config file already exists: '$CONFIG_FILE'. The old file has been renamed to '"..nname.."'")
	print("***************")
end
local f = io.open(vars.CONFIG_FILE, "w")
f:write([=[
rocks_trees = {
]=])
if FORCE_CONFIG then
	f:write("    home..[[/luarocks]],\n")
end
f:write(S"    { name = [[user]],\n")
f:write(S"         root    = home..[[/luarocks]],\n")
f:write(S"    },\n")
f:write(S"    { name = [[system]],\n")
f:write(S"         root    = [[$TREE_ROOT]],\n")
if vars.TREE_BIN then
  f:write(S"         bin_dir = [[$TREE_BIN]],\n")
end
if vars.TREE_CMODULE then
  f:write(S"         lib_dir = [[$TREE_CMODULE]],\n")
end
if vars.TREE_LMODULE then
  f:write(S"         lua_dir = [[$TREE_LMODULE]],\n")
end
f:write(S"    },\n")
f:write("}\n")
f:write("variables = {\n")
if USE_MINGW and vars.LUA_RUNTIME == "MSVCRT" then
	f:write("    MSVCRT = 'm',   -- make MinGW use MSVCRT.DLL as runtime\n")
else
	f:write("    MSVCRT = '"..vars.LUA_RUNTIME.."',\n")
end
if USE_MINGW then
        f:write(S[[
    CC = $MINGW_CC,
    MAKE = $MINGW_MAKE,
    RC = $MINGW_RC,
    LD = $MINGW_LD,
    AR = $MINGW_AR,
    RANLIB = $MINGW_RANLIB,
]])
end
f:write("}\n")
f:write("verbose = false   -- set to 'true' to enable verbose output\n")
f:write("fs_use_modules = false   -- prevent LuaRocks itself from using installed modules and blocking their files from removal \n")
f:close()

print(S"Created LuaRocks config file: $CONFIG_FILE")


print()
print("Creating rocktrees...")
if not exists(vars.TREE_ROOT) then
	mkdir(vars.TREE_ROOT)
	print(S[[Created system rocktree    : "$TREE_ROOT"]])
else
	print(S[[System rocktree exists     : "$TREE_ROOT"]])
end

vars.APPDATA = os.getenv("APPDATA")
vars.LOCAL_TREE = vars.APPDATA..[[\LuaRocks]]
if not exists(vars.LOCAL_TREE) then
	mkdir(vars.LOCAL_TREE)
	print(S[[Created local user rocktree: "$LOCAL_TREE"]])
else
	print(S[[Local user rocktree exists : "$LOCAL_TREE"]])
end

-- Load registry information
if REGISTRY then
	-- expand template with correct path information
	print()
	print([[Loading registry information for ".rockspec" files]])
	exec( S[[win32\lua5.1\bin\lua5.1.exe "$PREFIX\LuaRocks.reg.lua" "$PREFIX\LuaRocks.reg.template"]] )
	exec( S[[regedit /S "$PREFIX\\LuaRocks.reg"]] )
end

-- ***********************************************************
-- Cleanup
-- ***********************************************************
-- remove registry related files, no longer needed
exec( S[[del "$PREFIX\LuaRocks.reg.*" >NUL]] )

-- ***********************************************************
-- Exit handlers 
-- ***********************************************************
vars.TREE_BIN     = vars.TREE_BIN     or vars.TREE_ROOT..[[\bin]]
vars.TREE_LMODULE = vars.TREE_LMODULE or vars.TREE_ROOT..[[\share\lua\]]..vars.LUA_VERSION
vars.TREE_CMODULE = vars.TREE_CMODULE or vars.TREE_ROOT..[[\lib\lua\]]..vars.LUA_VERSION
print(S[[

============================
== LuaRocks is installed! ==
============================


You may want to add the following elements to your paths;
Lua interpreter;
  PATH     :   $LUA_BINDIR
  PATHEXT  :   .LUA
LuaRocks;
  PATH     :   $PREFIX
  LUA_PATH :   $PREFIX\lua\?.lua;$PREFIX\lua\?\init.lua
Local user rocktree (Note: %APPDATA% is user dependent);
  PATH     :   %APPDATA%\LuaRocks\bin
  LUA_PATH :   %APPDATA%\LuaRocks\share\lua\$LUA_VERSION\?.lua;%APPDATA%\LuaRocks\share\lua\$LUA_VERSION\?\init.lua
  LUA_CPATH:   %APPDATA%\LuaRocks\lib\lua\$LUA_VERSION\?.dll
System rocktree
  PATH     :   $TREE_BIN
  LUA_PATH :   $TREE_LMODULE\?.lua;$TREE_LMODULE\?\init.lua
  LUA_CPATH:   $TREE_CMODULE\?.dll

Note that the %APPDATA% element in the paths above is user specific and it MUST be replaced by its actual value.
For the current user that value is: $APPDATA.

]])
os.exit(0)
