--
-- d/tools/dmd.lua
-- Provides dmd-specific configuration strings.
-- Copyright (c) 2013-2015 Andrew Gough, Manu Evans, and the Premake project
--

	local tdmd = {}

	local p = premake
	local project = p.project
	local config = p.config
	local d = p.modules.d

--
-- Set default tools
--
	tdmd.gcc = {}
	tdmd.gcc.dc = "dmd"

	tdmd.optlink = {}
	tdmd.optlink.dc = "dmd"


-- /////////////////////////////////////////////////////////////////////////
-- dmd + GCC toolchain
-- /////////////////////////////////////////////////////////////////////////

--
-- Return a list of LDFLAGS for a specific configuration.
--

	tdmd.gcc.ldflags = {
		architecture = {
			x86 = { "-m32" },
			x86_64 = { "-m64" },
		},
		kind = {
			SharedLib = "-shared",
			StaticLib = "-lib",
		}
	}

	function tdmd.gcc.getldflags(cfg)
		local flags = config.mapFlags(cfg, tdmd.gcc.ldflags)
		return flags
	end


--
-- Return a list of decorated additional libraries directories.
--

	tdmd.gcc.libraryDirectories = {
		architecture = {
			x86 = "-L-L/usr/lib",
			x86_64 = "-L-L/usr/lib64",
		}
	}

	function tdmd.gcc.getLibraryDirectories(cfg)
		local flags = config.mapFlags(cfg, tdmd.gcc.libraryDirectories)

		-- Scan the list of linked libraries. If any are referenced with
		-- paths, add those to the list of library search paths
		for _, dir in ipairs(config.getlinks(cfg, "system", "directory")) do
			table.insert(flags, '-L-L' .. project.getrelative(cfg.project, dir))
		end

		return flags
	end


--
-- Return the list of libraries to link, decorated with flags as needed.
--

	function tdmd.gcc.getlinks(cfg, systemonly)
		local result = {}

		local links
		if not systemonly then
			links = config.getlinks(cfg, "siblings", "object")
			for _, link in ipairs(links) do
				-- skip external project references, since I have no way
				-- to know the actual output target path
				if not link.project.external then
					if link.kind == p.STATICLIB then
						-- Don't use "-l" flag when linking static libraries; instead use
						-- path/libname.a to avoid linking a shared library of the same
						-- name if one is present
						table.insert(result, "-L" .. project.getrelative(cfg.project, link.linktarget.abspath))
					else
						table.insert(result, "-L-l" .. link.linktarget.basename)
					end
				end
			end
		end

		-- The "-l" flag is fine for system libraries
		links = config.getlinks(cfg, "system", "fullpath")
		for _, link in ipairs(links) do
			if path.isframework(link) then
				table.insert(result, "-framework " .. path.getbasename(link))
			elseif path.isobjectfile(link) then
				table.insert(result, "-L" .. link)
			else
				table.insert(result, "-L-l" .. path.getbasename(link))
			end
		end

		return result
	end


-- /////////////////////////////////////////////////////////////////////////
-- tdmd + OPTLINK toolchain
-- /////////////////////////////////////////////////////////////////////////

--
-- Return a list of LDFLAGS for a specific configuration.
--

	tdmd.optlink.ldflags = {
		architecture = {
			x86 = { "-m32" },
			x86_64 = { "-m64" },
		},
		kind = {
			SharedLib = "-shared",
			StaticLib = "-lib",
		}
	}

	function tdmd.optlink.getldflags(cfg)
		local flags = config.mapFlags(cfg, tdmd.optlink.ldflags)
		return flags
	end


--
-- Return a list of decorated additional libraries directories.
--

	function tdmd.optlink.getLibraryDirectories(cfg)
		local flags = {}

		-- Scan the list of linked libraries. If any are referenced with
		-- paths, add those to the list of library search paths
		for _, dir in ipairs(config.getlinks(cfg, "system", "directory")) do
			table.insert(flags, '-Llib "' .. project.getrelative(cfg.project, dir) .. '"')
		end

		return flags
	end


--
-- Returns a list of linker flags for library names.
--

	function tdmd.optlink.getlinks(cfg)
		local result = {}

		local links = config.getlinks(cfg, "dependencies", "object")
		for _, link in ipairs(links) do
			-- skip external project references, since I have no way
			-- to know the actual output target path
			if not link.project.externalname then
				local linkinfo = config.getlinkinfo(link)
				if link.kind == p.STATICLIB then
					table.insert(result, project.getrelative(cfg.project, linkinfo.abspath))
				end
			end
		end

		-- The "-l" flag is fine for system libraries
		links = config.getlinks(cfg, "system", "basename")
		for _, link in ipairs(links) do
			if path.isobjectfile(link) then
				table.insert(result, link)
			elseif path.hasextension(link, p.systems[cfg.system].staticlib.extension) then
				table.insert(result, link)
			end
		end

		return result

	end


-- /////////////////////////////////////////////////////////////////////////
-- common dmd code (either toolchain)
-- /////////////////////////////////////////////////////////////////////////

	-- if we are compiling on windows, we need to specialise to OPTLINK as the linker
-- OR!!!			if cfg.system ~= p.WINDOWS then
	if string.match( os.getversion().description, "Windows" ) ~= nil then
		-- TODO: on windows, we may use OPTLINK or MSLINK (for Win64)...
--		printf("TODO: select proper linker for 32/64 bit code")

		p.tools.dmd = tdmd.optlink
	else
		p.tools.dmd = tdmd.gcc
	end

	local dmd = p.tools.dmd


--
-- Returns list of D compiler flags for a configuration.
--

	dmd.dflags = {
		architecture = {
			x86 = "-m32mscoff",
			x86_64 = "-m64",
		},
		flags = {
			OmitDefaultLibrary		= "-mscrtlib=",
			CodeCoverage			= "-cov",
			Color					= "-color",
			Documentation			= "-D",
			FatalWarnings			= "-w",
			GenerateHeader			= "-H",
			GenerateJSON			= "-X",
			GenerateMap				= "-map",
			LowMem					= "-lowmem",
			Profile					= "-profile",
			Quiet					= "-quiet",
			RetainPaths				= "-op",
			SymbolsLikeC			= "-gc",
			UnitTest				= "-unittest",
			Verbose					= "-v",
			ProfileGC				= "-profile=gc",
			StackFrame				= "-gs",
			StackStomp				= "-gx",
			AllInstantiate			= "-allinst",
			BetterC					= "-betterC",
			Main					= "-main",
			PerformSyntaxCheckOnly	= "-o-",
			ShowTLS					= "-vtls",
			ShowGC					= "-vgc",
			IgnorePragma			= "-ignore",
			ShowDependencies		= "-deps",
		},
		boundscheck = {
			Off = "-boundscheck=off",
			On = "-boundscheck=on",
			SafeOnly = "-boundscheck=safeonly",
		},
		checkaction = {
			D = "-checkaction=D",
			C = "-checkaction=C",
			Halt = "-checkaction=halt",
			Context = "-checkaction=context",
		},
		cppdialect = {
			["C++latest"] = "-extern-std=c++20", -- TODO: keep this up to date >_<
			["C++98"] = "-extern-std=c++98",
			["C++0x"] = "-extern-std=c++11",
			["C++11"] = "-extern-std=c++11",
			["C++1y"] = "-extern-std=c++14",
			["C++14"] = "-extern-std=c++14",
			["C++1z"] = "-extern-std=c++17",
			["C++17"] = "-extern-std=c++17",
			["C++2a"] = "-extern-std=c++20",
			["C++20"] = "-extern-std=c++20",
			["gnu++98"] = "-extern-std=c++98",
			["gnu++0x"] = "-extern-std=c++11",
			["gnu++11"] = "-extern-std=c++11",
			["gnu++1y"] = "-extern-std=c++14",
			["gnu++14"] = "-extern-std=c++14",
			["gnu++1z"] = "-extern-std=c++17",
			["gnu++17"] = "-extern-std=c++17",
			["gnu++2a"] = "-extern-std=c++20",
			["gnu++20"] = "-extern-std=c++20",
		},
		deprecatedfeatures = {
			Allow = "-d",
			Warn = "-dw",
			Error = "-de",
		},
		floatingpoint = {
			None = "-nofloat",
		},
		optimize = {
			On = "-O -inline",
			Full = "-O -inline",
			Size = "-O -inline",
			Speed = "-O -inline",
		},
		pic = {
			On = "-fPIC",
		},
		symbols = {
			On = "-g",
			FastLink = "-g",
			Full = "-g",
		},
		vectorextensions = {
			AVX = "-mcpu=avx",
			AVX2 = "-mcpu=avx2",
		},
		warnings = {
			Default = "-wi",
			High = "-wi",
			Extra = "-wi",
			Everything = "-wi",
		},
	}

	function dmd.getdflags(cfg)
		local flags = config.mapFlags(cfg, dmd.dflags)

		if config.isDebugBuild(cfg) then
			table.insert(flags, "-debug")
		else
			table.insert(flags, "-release")
		end

		if not cfg.flags.OmitDefaultLibrary then
			local releaseruntime = not config.isDebugBuild(cfg)
			local staticruntime = true
			if cfg.staticruntime == "Off" then
				staticruntime = false
			end
			if cfg.runtime == "Debug" then
				releaseruntime = false
			elseif cfg.runtime == "Release" then
				releaseruntime = true
			end

			if (cfg.staticruntime and cfg.staticruntime ~= "Default") or (cfg.runtime and cfg.runtime ~= "Default") then
				if staticruntime == true and releaseruntime == true then
					table.insert(flags, "-mscrtlib=libcmt")
				elseif staticruntime == true and releaseruntime == false then
					table.insert(flags, "-mscrtlib=libcmtd")
				elseif staticruntime == false and releaseruntime == true then
					table.insert(flags, "-mscrtlib=msvcrt")
				elseif staticruntime == false and releaseruntime == false then
					table.insert(flags, "-mscrtlib=msvcrtd")
				end
			end
		end

		if cfg.flags.Documentation then
			if cfg.docname then
				table.insert(flags, "-Df" .. p.quoted(cfg.docname))
			end
			if cfg.docdir then
				table.insert(flags, "-Dd" .. p.quoted(cfg.docdir))
			end
		end
		if cfg.flags.GenerateHeader then
			if cfg.headername then
				table.insert(flags, "-Hf" .. p.quoted(cfg.headername))
			end
			if cfg.headerdir then
				table.insert(flags, "-Hd" .. p.quoted(cfg.headerdir))
			end
		end

		if #cfg.preview > 0 then
			for _, opt in ipairs(cfg.preview) do
				table.insert(flags, "-preview=" .. opt)
			end
		end
		if #cfg.revert > 0 then
			for _, opt in ipairs(cfg.revert) do
				table.insert(flags, "-revert=" .. opt)
			end
		end
		if #cfg.transition > 0 then
			for _, opt in ipairs(cfg.transition) do
				table.insert(flags, "-transition=" .. opt)
			end
		end

		return flags
	end


--
-- Decorate versions for the DMD command line.
--

	function dmd.getversions(versions, level)
		local result = {}
		for _, version in ipairs(versions) do
			table.insert(result, '-version=' .. version)
		end
		if level then
			table.insert(result, '-version=' .. level)
		end
		return result
	end


--
-- Decorate debug constants for the DMD command line.
--

	function dmd.getdebug(constants, level)
		local result = {}
		for _, constant in ipairs(constants) do
			table.insert(result, '-debug=' .. constant)
		end
		if level then
			table.insert(result, '-debug=' .. level)
		end
		return result
	end


--
-- Decorate import file search paths for the DMD command line.
--

	function dmd.getimportdirs(cfg, dirs)
		local result = {}
		for _, dir in ipairs(dirs) do
			dir = project.getrelative(cfg.project, dir)
			table.insert(result, '-I' .. p.quoted(dir))
		end
		return result
	end


--
-- Decorate string import file search paths for the DMD command line.
--

	function dmd.getstringimportdirs(cfg, dirs)
		local result = {}
		for _, dir in ipairs(dirs) do
			dir = project.getrelative(cfg.project, dir)
			table.insert(result, '-J' .. p.quoted(dir))
		end
		return result
	end


--
-- Returns the target name specific to compiler
--

	function dmd.gettarget(name)
		return "-of" .. name
	end


--
-- Returns makefile-specific configuration rules.
--

	dmd.makesettings = {
	}

	function dmd.getmakesettings(cfg)
		local settings = config.mapFlags(cfg, dmd.makesettings)
		return table.concat(settings)
	end


--
-- Retrieves the executable command name for a tool, based on the
-- provided configuration and the operating environment.
--
-- @param cfg
--    The configuration to query.
-- @param tool
--    The tool to fetch, one of "dc" for the D compiler, or "ar" for the static linker.
-- @return
--    The executable command name for a tool, or nil if the system's
--    default value should be used.
--

	dmd.tools = {
		-- dmd will probably never support any foreign architectures...?
	}

	function dmd.gettoolname(cfg, tool)
		local names = dmd.tools[cfg.architecture] or dmd.tools[cfg.system] or {}
		local name = names[tool]
		return name or dmd[tool]
	end