--
-- gmake2.lua
-- (c) 2016-2017 Jason Perkins, Blizzard Entertainment and the Premake project
--

	local p       = premake
	local project = p.project

	p.modules.gmake2 = {}
	p.modules.gmake2._VERSION = p._VERSION
	local gmake2 = p.modules.gmake2

--
-- Write out the default configuration rule for a workspace or project.
--
-- @param target
--    The workspace or project object for which a makefile is being generated.
--

	function gmake2.defaultconfig(target)
		-- find the right configuration iterator function for this object
		local eachconfig = iif(target.project, project.eachconfig, p.workspace.eachconfig)
		local defaultconfig = nil

		-- find the right default configuration platform, grab first configuration that matches
		if target.defaultplatform then
			for cfg in eachconfig(target) do
				if cfg.platform == target.defaultplatform then
					defaultconfig = cfg
					break
				end
			end
		end

		-- grab the first configuration and write the block
		if not defaultconfig then
			local iter = eachconfig(target)
			defaultconfig = iter()
		end

		if defaultconfig then
			_p('ifndef config')
			_x('  config=%s', defaultconfig.shortname)
			_p('endif')
			_p('')
		end
	end


---
-- Escape a string so it can be written to a makefile.
---

	function gmake2.esc(value)
		result = value:gsub("\\", "\\\\")
		result = result:gsub("\"", "\\\"")
		result = result:gsub(" ", "\\ ")
		result = result:gsub("%(", "\\(")
		result = result:gsub("%)", "\\)")

		-- leave $(...) shell replacement sequences alone
		result = result:gsub("$\\%((.-)\\%)", "$(%1)")
		return result
	end


--
-- Get the makefile file name for a workspace or a project. If this object is the
-- only one writing to a location then I can use "Makefile". If more than one object
-- writes to the same location I use name + ".make" to keep it unique.
--

	function gmake2.getmakefilename(this, searchprjs)
		local count = 0
		for wks in p.global.eachWorkspace() do
			if wks.location == this.location then
				count = count + 1
			end

			if searchprjs then
				for _, prj in ipairs(wks.projects) do
					if prj.location == this.location then
						count = count + 1
					end
				end
			end
		end

		if count == 1 then
			return "Makefile"
		else
			return ".make"
		end
	end


--
-- Output a makefile header.
--
-- @param target
--    The workspace or project object for which the makefile is being generated.
--

	function gmake2.header(target)
		local kind = iif(target.project, "project", "workspace")

		_p('# %s %s makefile autogenerated by Premake', p.action.current().shortname, kind)
		_p('')

		gmake2.defaultconfig(target)

		_p('ifndef verbose')
		_p('  SILENT = @')
		_p('endif')
		_p('')
	end


--
-- Rules for file ops based on the shell type. Can't use defines and $@ because
-- it screws up the escaping of spaces and parethesis (anyone know a fix?)
--

	function gmake2.mkdir(dirname)
		_p('ifeq (posix,$(SHELLTYPE))')
		_p('\t$(SILENT) mkdir -p %s', dirname)
		_p('else')
		_p('\t$(SILENT) mkdir $(subst /,\\\\,%s)', dirname)
		_p('endif')
	end

	function gmake2.mkdirRules(dirname)
		_p('%s:', dirname)
		_p('\t@echo Creating %s', dirname)
		gmake2.mkdir(dirname)
		_p('')
	end

--
-- Format a list of values to be safely written as part of a variable assignment.
--

	function gmake2.list(value, quoted)
		quoted = false
		if #value > 0 then
			if quoted then
				local result = ""
				for _, v in ipairs (value) do
					if #result then
						result = result .. " "
					end
					result = result .. p.quoted(v)
				end
				return result
			else
				return " " .. table.concat(value, " ")
			end
		else
			return ""
		end
	end


--
-- Convert an arbitrary string (project name) to a make variable name.
--

	function gmake2.tovar(value)
		value = value:gsub("[ -]", "_")
		value = value:gsub("[()]", "")
		return value
	end



	function gmake2.path(cfg, value)
		cfg = cfg.project or cfg
		local dirs = path.translate(project.getrelative(cfg, value))

		if type(dirs) == 'table' then
			dirs = table.filterempty(dirs)
		end

		return dirs
	end


	function gmake2.getToolSet(cfg)
		local default = iif(cfg.system == p.MACOSX, "clang", "gcc")
		local toolset = p.tools[_OPTIONS.cc or cfg.toolset or default]
		if not toolset then
			error("Invalid toolset '" .. cfg.toolset .. "'")
		end
		return toolset
	end


	function gmake2.outputSection(prj, callback)
		local root = {}

		for cfg in project.eachconfig(prj) do
			-- identify the toolset used by this configurations (would be nicer if
			-- this were computed and stored with the configuration up front)

			local toolset = gmake2.getToolSet(cfg)

			local settings = {}
			local funcs = callback(cfg)
			for i = 1, #funcs do
				local c = p.capture(function ()
					funcs[i](cfg, toolset)
				end)
				if #c > 0 then
					table.insert(settings, c)
				end
			end

			if not root.settings then
				root.settings = table.arraycopy(settings)
			else
				root.settings = table.intersect(root.settings, settings)
			end

			root[cfg] = settings
		end

		if #root.settings > 0 then
			for _, v in ipairs(root.settings) do
				p.outln(v)
			end
			p.outln('')
		end

		local first = true
		for cfg in project.eachconfig(prj) do
			local settings = table.difference(root[cfg], root.settings)
			if #settings > 0 then
				if first then
					_x('ifeq ($(config),%s)', cfg.shortname)
					first = false
				else
					_x('else ifeq ($(config),%s)', cfg.shortname)
				end

				for k, v in ipairs(settings) do
					p.outln(v)
				end

				_p('')
			end
		end

		if not first then
			p.outln('endif')
			p.outln('')
		end
	end


	-- convert a rule property into a string

	function gmake2.expandRuleString(rule, prop, value)
		-- list?
		if type(value) == "table" then
			if #value > 0 then
				if prop.switch then
					return prop.switch .. table.concat(value, " " .. prop.switch)
				else
					prop.separator = prop.separator or " "
					return table.concat(value, prop.separator)
				end
			else
				return nil
			end
		end

		-- bool just emits the switch
		if prop.switch and type(value) == "boolean" then
			if value then
				return prop.switch
			else
				return nil
			end
		end

		local switch = prop.switch or ""

		-- enum?
		if prop.values then
			value = table.findKeyByValue(prop.values, value)
			if value == nil then
				value = ""
			end
		end

		-- primitive
		value = tostring(value)
		if #value > 0 then
			return switch .. value
		else
			return nil
		end
	end


	function gmake2.prepareEnvironment(rule, environ, cfg)
		for _, prop in ipairs(rule.propertydefinition) do
			local fld = p.rule.getPropertyField(rule, prop)
			local value = cfg[fld.name]
			if value ~= nil then

				if fld.kind == "path" then
					value = gmake2.path(cfg, value)
				elseif fld.kind == "list:path" then
					value = gmake2.path(cfg, value)
				end

				value = gmake2.expandRuleString(rule, prop, value)
				if value ~= nil and #value > 0 then
					environ[prop.name] = p.esc(value)
				end
			end
		end
	end


---------------------------------------------------------------------------
--
-- Handlers for the individual makefile elements that can be shared
-- between the different language projects.
--
---------------------------------------------------------------------------

	function gmake2.phonyRules(prj)
		_p('.PHONY: clean prebuild')
		_p('')
	end


	function gmake2.shellType()
		_p('SHELLTYPE := posix')
		_p('ifeq (.exe,$(findstring .exe,$(ComSpec)))')
		_p('\tSHELLTYPE := msdos')
		_p('endif')
		_p('')
	end


	function gmake2.target(cfg, toolset)
		p.outln('TARGETDIR = ' .. project.getrelative(cfg.project, cfg.buildtarget.directory))
		p.outln('TARGET = $(TARGETDIR)/' .. cfg.buildtarget.name)
	end


	function gmake2.objdir(cfg, toolset)
		p.outln('OBJDIR = ' .. project.getrelative(cfg.project, cfg.objdir))
	end


	function gmake2.settings(cfg, toolset)
		if #cfg.makesettings > 0 then
			for _, value in ipairs(cfg.makesettings) do
				p.outln(value)
			end
		end

		local value = toolset.getmakesettings(cfg)
		if value then
			p.outln(value)
		end
	end


	function gmake2.buildCmds(cfg, event)
		_p('define %sCMDS', event:upper())
		local steps = cfg[event .. "commands"]
		local msg = cfg[event .. "message"]
		if #steps > 0 then
			steps = os.translateCommandsAndPaths(steps, cfg.project.basedir, cfg.project.location)
			msg = msg or string.format("Running %s commands", event)
			_p('\t@echo %s', msg)
			_p('\t%s', table.implode(steps, "", "", "\n\t"))
		end
		_p('endef')
	end


	function gmake2.preBuildCmds(cfg, toolset)
		gmake2.buildCmds(cfg, "prebuild")
	end


	function gmake2.preLinkCmds(cfg, toolset)
		gmake2.buildCmds(cfg, "prelink")
	end


	function gmake2.postBuildCmds(cfg, toolset)
		gmake2.buildCmds(cfg, "postbuild")
	end


	function gmake2.targetDirRules(cfg, toolset)
		gmake2.mkdirRules("$(TARGETDIR)")
	end


	function gmake2.objDirRules(cfg, toolset)
		gmake2.mkdirRules("$(OBJDIR)")
	end


	function gmake2.preBuildRules(cfg, toolset)
		_p('prebuild: | $(OBJDIR)')
		_p('\t$(PREBUILDCMDS)')
		_p('')
	end



	include("gmake2_cpp.lua")
	include("gmake2_csharp.lua")
	include("gmake2_makefile.lua")
	include("gmake2_utility.lua")
	include("gmake2_workspace.lua")

	return gmake2
