--
-- path.lua
-- Path manipulation functions.
-- Copyright (c) 2002-2010 Jason Perkins and the Premake project
--


--
-- Retrieve the filename portion of a path, without any extension.
--

	function path.getbasename(p)
		local name = path.getname(p)
		local i = name:findlast(".", true)
		if (i) then
			return name:sub(1, i - 1)
		else
			return name
		end
	end

--
-- Retrieve the path, without any extension.
--

	function path.removeext(name)
		local i = name:findlast(".", true)
		if (i) then
			return name:sub(1, i - 1)
		else
			return name
		end
	end

--
-- Retrieve the directory portion of a path, or an empty string if
-- the path does not include a directory.
--

	function path.getdirectory(p)
		local i = p:findlast("/", true)
		if (i) then
			if i > 1 then i = i - 1 end
			return p:sub(1, i)
		else
			return "."
		end
	end


--
-- Retrieve the drive letter, if a Windows path.
--

	function path.getdrive(p)
		local ch1 = p:sub(1,1)
		local ch2 = p:sub(2,2)
		if ch2 == ":" then
			return ch1
		end
	end



--
-- Retrieve the file extension.
--

	function path.getextension(p)
		local i = p:findlast(".", true)
		if (i) then
			return p:sub(i)
		else
			return ""
		end
	end



--
-- Retrieve the filename portion of a path.
--

	function path.getname(p)
		local i = p:findlast("[/\\]")
		if (i) then
			return p:sub(i + 1)
		else
			return p
		end
	end



--
-- Returns the common base directory of two paths.
--

	function path.getcommonbasedir(a, b)
		a = path.getdirectory(a)..'/'
		b = path.getdirectory(b)..'/'

		-- find the common leading directories
		local idx = 0
		while (true) do
			local tst = a:find('/', idx + 1, true)
			if tst then
				if a:sub(1,tst) == b:sub(1,tst) then
					idx = tst
				else
					break
				end
			else
				break
			end
		end
		-- idx is the index of the last sep before path string 'a' ran out or didn't match.
		local result = ''
		if idx > 1 then
			result = a:sub(1, idx - 1)	-- Remove the trailing slash to be consistent with other functions.
		end
		return result
	end


--
-- Returns true if the filename has a particular extension.
--
-- @param fname
--    The file name to test.
-- @param extensions
--    The extension(s) to test. Maybe be a string or table.
--

	function path.hasextension(fname, extensions)
		local fext = path.getextension(fname):lower()
		if type(extensions) == "table" then
			for _, extension in pairs(extensions) do
				if fext == extension then
					return true
				end
			end
			return false
		else
			return (fext == extensions)
		end
	end

--
-- Returns true if the filename represents a C/C++ source code file. This check
-- is used to prevent passing non-code files to the compiler in makefiles. It is
-- not foolproof, but it has held up well. I'm open to better suggestions.
--

	function path.iscfile(fname)
		return path.hasextension(fname, { ".c", ".m" })
	end

	function path.iscppfile(fname)
		return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c", ".m", ".mm" })
	end

	function path.iscxfile(fname)
		return path.hasextension(fname, ".cx")
	end

	function path.isobjcfile(fname)
		return path.hasextension(fname, { ".m", ".mm" })
	end

	function path.iscppheader(fname)
		return path.hasextension(fname, { ".h", ".hh", ".hpp", ".hxx" })
	end

	function path.iscppmodule(fname)
		return path.hasextension(fname, { ".ixx", ".cppm" })
	end

	function path.isappxmanifest(fname)
		return path.hasextension(fname, ".appxmanifest")
	end

	function path.isandroidbuildfile(fname)
		return path.getname(fname) == "AndroidManifest.xml"
	end

	function path.isnatvis(fname)
		return path.hasextension(fname, ".natvis")
	end

	function path.isasmfile(fname)
		return path.hasextension(fname, { ".asm", ".s", ".S" })
	end

	function path.isvalafile(fname)
		return path.hasextension(fname, ".vala")
	end

	function path.isgresource(fname)
		local ending = ".gresource.xml"
		return ending == "" or fname:sub(-#ending) == ending
	end

	function path.isswiftfile(fname)
		return path.hasextension(fname, ".swift")
	end

	function path.issourcefile(fname)
		return path.iscfile(fname)
			or path.iscppfile(fname)
			or path.iscxfile(fname)
			or path.isasmfile(fname)
			or path.isvalafile(fname)
			or path.isswiftfile(fname)
	end

	function path.issourcefilevs(fname)
		return path.hasextension(fname, { ".cc", ".cpp", ".cxx", ".c++", ".c" })
			or path.iscxfile(fname)
			or path.iscppmodule(fname)
	end

--
-- Returns true if the filename represents a compiled object file. This check
-- is used to support object files in the "files" list for archiving.
--

	function path.isobjectfile(fname)
		return path.hasextension(fname, { ".o", ".obj" })
	end

--
-- Returns true if the filename represents a Windows resource file. This check
-- is used to prevent passing non-resources to the compiler in makefiles.
--

	function path.isresourcefile(fname)
		return path.hasextension(fname, ".rc")
	end


--
-- Returns true if the filename represents a Windows image file. 
--

	function path.isimagefile(fname)
		local extensions = { ".png" }
		local ext = path.getextension(fname):lower()
		return table.contains(extensions, ext)
	end

--
-- Join one or more pieces of a path together into a single path.
--
-- @param ...
--    One or more path strings.
-- @return
--    The joined path.
--

	function path.join(...)
		local arg={...}
		local numargs = select("#", ...)
		if numargs == 0 then
			return "";
		end

		local allparts = {}
		for i = numargs, 1, -1 do
			local part = select(i, ...)
			if part and #part > 0 and part ~= "." then
				-- trim off trailing slashes
				while part:endswith("/") do
					part = part:sub(1, -2)
				end

				table.insert(allparts, 1, part)
				if path.isabsolute(part) then
					break
				end
			end
		end

		return table.concat(allparts, "/")
	end


--
-- Takes a path which is relative to one location and makes it relative
-- to another location instead.
--

	function path.rebase(p, oldbase, newbase)
		p = path.getabsolute(path.join(oldbase, p))
		p = path.getrelative(newbase, p)
		return p
	end


--
-- Convert the separators in a path from one form to another. If `sep`
-- is nil, then a platform-specific separator is used.
--

	function path.translate(p, sep)
		if (type(p) == "table") then
			local result = { }
			for _, value in ipairs(p) do
				table.insert(result, path.translate(value))
			end
			return result
		else
			if (not sep) then
				if (os.is("windows")) then
					sep = "\\"
				else
					sep = "/"
				end
			end
			local result = p:gsub("[/\\]", sep)
			return result
		end
	end


--
-- Converts from a simple wildcard syntax, where * is "match any"
-- and ** is "match recursive", to the corresponding Lua pattern.
--
-- @param pattern
--    The wildcard pattern to convert.
-- @returns
--    The corresponding Lua pattern.
--

	function path.wildcards(pattern)
		-- Escape characters that have special meanings in Lua patterns
		pattern = pattern:gsub("([%+%.%-%^%$%(%)%%])", "%%%1")

		-- Replace wildcard patterns with special placeholders so I don't
		-- have competing star replacements to worry about
		pattern = pattern:gsub("%*%*", "\001")
		pattern = pattern:gsub("%*", "\002")

		-- Replace the placeholders with their Lua patterns
		pattern = pattern:gsub("\001", ".*")
		pattern = pattern:gsub("\002", "[^/]*")

		return pattern
	end

--
-- remove any dot ("./", "../") patterns from the start of the path
--
	function path.trimdots(p)
		local changed
		repeat
			changed = true
			if p:startswith("./") then
				p = p:sub(3)
			elseif p:startswith("../") then
				p = p:sub(4)
			else
				changed = false
			end
		until not changed

		return p
	end

--
-- Takes a path which is relative to one location and makes it relative
-- to another location instead.
--

	function path.rebase(p, oldbase, newbase)
		p = path.getabsolute(path.join(oldbase, p))
		p = path.getrelative(newbase, p)
		return p
	end



--
-- Replace the file extension.
--

	function path.replaceextension(p, newext)
		local ext = path.getextension(p)

		if not ext then
			return p
		end

		if #newext > 0 and not newext:findlast(".", true) then
			newext = "."..newext
		end

		return p:match("^(.*)"..ext.."$")..newext
	end
