--
-- d/actions/visuald.lua
-- Generate a VisualD .visualdproj project.
-- Copyright (c) 2012-2015 Manu Evans and the Premake project
--

	local p = premake
	local m = p.modules.d

	m.visuald = {}

	require ("vstudio")

	local vstudio = p.vstudio
	local workspace = p.workspace
	local project = p.project
	local config = p.config
	local tree = p.tree

--
-- Patch the vstudio actions with D support...
--

	for k,v in pairs({ "vs2005", "vs2008", "vs2010", "vs2012", "vs2013", "vs2015", "vs2017", "vs2019" }) do
		local vs = p.action.get(v)
		if vs ~= nil then
			table.insert( vs.valid_languages, p.D )
			vs.valid_tools.dc = { "dmd", "gdc", "ldc" }

			p.override(vs, "onProject", function(oldfn, prj)
				oldfn(prj)
				if project.isd(prj) then
					p.generate(prj, ".visualdproj", m.visuald.generate)
				end
			end)
		end
	end


--
-- Patch a bunch of other functions
--

	p.override(project, "isnative", function(oldfn, prj)
		return project.isd(prj) or oldfn(prj)
	end)

	p.override(vstudio, "projectfile", function(oldfn, prj)
		if project.isd(prj) then
			return p.filename(prj, ".visualdproj")
		end
		return oldfn(prj)
	end)

	p.override(vstudio, "tool", function(oldfn, prj)
		if project.isd(prj) then
			return "002A2DE9-8BB6-484D-9802-7E4AD4084715"
		end
		return oldfn(prj)
	end)


--
-- Generate a Visual D project.
--

	m.elements.project = function(prj)
		return {
			m.visuald.header,
			m.visuald.globals,
			m.visuald.projectConfigurations,
			m.visuald.files,
		}

	end

	function m.visuald.generate(prj)
		p.eol("\r\n")
		p.indent(" ")

		p.callArray(m.elements.project, prj)

		_p('</DProject>')
	end


	function m.visuald.header(prj)
		-- for some reason Visual D projects don't seem to have an xml header
		--_p('<?xml version="1.0" encoding="utf-8"?>')
		_p('<DProject>')
	end

	function m.visuald.globals(prj)
		_p(1,'<ProjectGuid>{%s}</ProjectGuid>', prj.uuid)
	end


--
-- Write out the list of project configurations, which pairs build
-- configurations with architectures.
--

	function m.visuald.projectConfigurations(prj)
		-- build a list of all architectures used in this project

		for cfg in project.eachconfig(prj) do
			local prjPlatform = p.esc(vstudio.projectPlatform(cfg))
			local slnPlatform = vstudio.solutionPlatform(cfg)
			local is64bit = slnPlatform == "x64" -- TODO: this seems like a hack

			_p(1,'<Config name="%s" platform="%s">', prjPlatform, slnPlatform)

			_p(2,'<obj>0</obj>')
			_p(2,'<link>0</link>')

			local isWindows = false
			local isDebug = string.find(cfg.buildcfg, 'Debug') ~= nil
			local isOptimised = config.isOptimizedBuild(cfg)

			if cfg.kind == p.CONSOLEAPP then
				_p(2,'<lib>0</lib>')
				_p(2,'<subsystem>1</subsystem>')
			elseif cfg.kind == p.STATICLIB then
				_p(2,'<lib>1</lib>')
				_p(2,'<subsystem>0</subsystem>')
			elseif cfg.kind == p.SHAREDLIB then
				_p(2,'<lib>2</lib>')
				_p(2,'<subsystem>0</subsystem>') -- SHOULD THIS BE '2' (windows)??
			else
				_p(2,'<lib>0</lib>')
				_p(2,'<subsystem>2</subsystem>')
				isWindows = true
			end

			_p(2,'<multiobj>0</multiobj>')
			_p(2,'<singleFileCompilation>0</singleFileCompilation>')
			_p(2,'<oneobj>0</oneobj>')
			_p(2,'<trace>%s</trace>', iif(cfg.flags.Profile, '1', '0'))
			_p(2,'<quiet>%s</quiet>', iif(cfg.flags.Quiet, '1', '0'))
			_p(2,'<verbose>%s</verbose>', iif(cfg.flags.Verbose, '1', '0'))
			_p(2,'<vtls>0</vtls>')
			_p(2,'<symdebug>%s</symdebug>', iif(cfg.symbols == p.ON or cfg.symbols == "FastLink" or cfg.symbols == "Full", iif(cfg.flags.SymbolsLikeC, '2', '1'), '0'))
			_p(2,'<optimize>%s</optimize>', iif(isOptimised, '1', '0'))
			_p(2,'<cpu>0</cpu>')
			_p(2,'<isX86_64>%s</isX86_64>', iif(is64bit, '1', '0'))
			_p(2,'<isLinux>0</isLinux>')
			_p(2,'<isOSX>0</isOSX>')
			_p(2,'<isWindows>%s</isWindows>', iif(isWindows, '1', '0'))
			_p(2,'<isFreeBSD>0</isFreeBSD>')
			_p(2,'<isSolaris>0</isSolaris>')
			_p(2,'<scheduler>0</scheduler>')
			_p(2,'<useDeprecated>%s</useDeprecated>', iif(cfg.deprecatedfeatures == "Allow", '1', '0'))
			_p(2,'<errDeprecated>0</errDeprecated>')
			_p(2,'<useAssert>0</useAssert>')
			_p(2,'<useInvariants>0</useInvariants>')
			_p(2,'<useIn>0</useIn>')
			_p(2,'<useOut>0</useOut>')
			_p(2,'<useArrayBounds>0</useArrayBounds>')
			_p(2,'<noboundscheck>%s</noboundscheck>', iif(cfg.boundscheck == "Off", '1', '0'))
			_p(2,'<useSwitchError>0</useSwitchError>')
			_p(2,'<useUnitTests>%s</useUnitTests>', iif(cfg.flags.UnitTest, '1', '0'))
			_p(2,'<useInline>%s</useInline>', iif(cfg.flags.Inline or isOptimised, '1', '0'))
			_p(2,'<release>%s</release>', iif(cfg.flags.Release or not isDebug, '1', '0'))
			_p(2,'<preservePaths>0</preservePaths>')

			_p(2,'<warnings>%s</warnings>', iif(cfg.flags.FatalCompileWarnings, '1', '0'))
			_p(2,'<infowarnings>%s</infowarnings>', iif(cfg.warnings and cfg.warnings ~= "Off", '1', '0'))

			_p(2,'<checkProperty>0</checkProperty>')
			_p(2,'<genStackFrame>0</genStackFrame>')
			_p(2,'<pic>%s</pic>', iif(cfg.pic == "On", '1', '0'))
			_p(2,'<cov>%s</cov>', iif(cfg.flags.CodeCoverage, '1', '0'))
			_p(2,'<nofloat>%s</nofloat>', iif(cfg.floatingpoint and cfg.floatingpoint == "None", '1', '0'))
			_p(2,'<Dversion>2</Dversion>')
			_p(2,'<ignoreUnsupportedPragmas>0</ignoreUnsupportedPragmas>')

			local compiler = { dmd="0", gdc="1", ldc="2" }
			local compilerName, err = p.api.checkValue(p.fields.toolset, _OPTIONS.dc or cfg.toolset or "dmd")
			if err then
				error { msg=err }
			end

			m.visuald.element(2, "compiler", compiler[compilerName])

			m.visuald.element(2, "otherDMD", '0')
			m.visuald.element(2, "program", '$(DMDInstallDir)windows\\bin\\dmd.exe')

			local impdirs
			if #cfg.importdirs > 0 then
				impdirs = vstudio.path(cfg, cfg.importdirs)
			end
			m.visuald.element(2, "imppath", impdirs)

			m.visuald.element(2, "fileImppath")
			m.visuald.element(2, "outdir", path.translate(project.getrelative(cfg.project, cfg.buildtarget.directory)))
			m.visuald.element(2, "objdir", path.translate(project.getrelative(cfg.project, cfg.objdir)))
			m.visuald.element(2, "objname")
			m.visuald.element(2, "libname")

			m.visuald.element(2, "doDocComments", iif(cfg.flags.Documentation, '1', '0'))
			m.visuald.element(2, "docdir", cfg.docdir)
			m.visuald.element(2, "docname", cfg.docname)
			m.visuald.element(2, "modules_ddoc")
			m.visuald.element(2, "ddocfiles")

			m.visuald.element(2, "doHdrGeneration", iif(cfg.flags.GenerateHeader, '1', '0'))
			m.visuald.element(2, "hdrdir", cfg.headerdir)
			m.visuald.element(2, "hdrname", cfg.headername)

			m.visuald.element(2, "doXGeneration", iif(cfg.flags.GenerateJSON, '1', '0'))
			m.visuald.element(2, "xfilename", '$(IntDir)\\$(TargetName).json')

			m.visuald.element(2, "debuglevel", iif(cfg.debuglevel, tostring(cfg.debuglevel), '0'))
			m.visuald.element(2, "debugids", cfg.debugconstants)
			m.visuald.element(2, "versionlevel", iif(cfg.versionlevel, tostring(cfg.versionlevel), '0'))
			m.visuald.element(2, "versionids", cfg.versionconstants)

			_p(2,'<dump_source>0</dump_source>')
			_p(2,'<mapverbosity>0</mapverbosity>')
			_p(2,'<createImplib>%s</createImplib>', iif(cfg.kind ~= p.SHAREDLIB or cfg.flags.NoImportLib, '0', '1'))
			_p(2,'<defaultlibname />')
			_p(2,'<debuglibname />')
			_p(2,'<moduleDepsFile />')

			_p(2,'<run>0</run>')
			_p(2,'<runargs />')

--			_p(2,'<runCv2pdb>%s</runCv2pdb>', iif(cfg.symbols == p.ON, '1', '0'))
			_p(2,'<runCv2pdb>1</runCv2pdb>') -- we will just leave this always enabled, since it's ignored if no debuginfo is written
			_p(2,'<pathCv2pdb>$(VisualDInstallDir)cv2pdb\\cv2pdb.exe</pathCv2pdb>')
			_p(2,'<cv2pdbPre2043>0</cv2pdbPre2043>')
			_p(2,'<cv2pdbNoDemangle>0</cv2pdbNoDemangle>')
			_p(2,'<cv2pdbEnumType>0</cv2pdbEnumType>')
			_p(2,'<cv2pdbOptions />')

			_p(2,'<objfiles />')
			_p(2,'<linkswitches />')

			local links
			local explicit = vstudio.needsExplicitLink(cfg)
			-- check to see if this project uses an external toolset. If so, let the
			-- toolset define the format of the links
			local toolset = config.toolset(cfg)
			if toolset then
				links = toolset.getlinks(cfg, not explicit)
			else
				local scope = iif(explicit, "all", "system")
				links = config.getlinks(cfg, scope, "fullpath")
			end
			m.visuald.element(2, "libfiles", table.concat(links, " "))

			m.visuald.element(2, "libpaths", cfg.libdirs)
			_p(2,'<deffile />')
			_p(2,'<resfile />')

			local target = config.gettargetinfo(cfg)
			_p(2,'<exefile>$(OutDir)\\%s</exefile>', target.name)

			_p(2,'<useStdLibPath>1</useStdLibPath>')

			local runtime = 0
			if not cfg.flags.OmitDefaultLibrary then
				if config.isDebugBuild(cfg) then
					runtime = iif(cfg.flags.StaticRuntime, "2", "4")
				else
					runtime = iif(cfg.flags.StaticRuntime, "1", "3")
				end
			end
			m.visuald.element(2, "cRuntime", runtime)

			local additionalOptions
			if #cfg.buildoptions > 0 then
				additionalOptions = table.concat(cfg.buildoptions, " ")
			end
			if #cfg.linkoptions > 0 then
				local linkOpts = table.implode(cfg.linkoptions, "-L", "", " ")
				if additionalOptions then
					additionalOptions = additionalOptions .. " " .. linkOpts
				else
					additionalOptions = linkOpts
				end
			end
			m.visuald.element(2, "additionalOptions", additionalOptions)

			if #cfg.prebuildcommands > 0 then
				_p(2,'<preBuildCommand>%s</preBuildCommand>',p.esc(table.implode(cfg.prebuildcommands, "", "", "\r\n")))
			else
				_p(2,'<preBuildCommand />')
			end

			if #cfg.postbuildcommands > 0 then
				_p(2,'<postBuildCommand>%s</postBuildCommand>',p.esc(table.implode(cfg.postbuildcommands, "", "", "\r\n")))
			else
				_p(2,'<postBuildCommand />')
			end

			_p(2,'<filesToClean>*.obj;*.cmd;*.build;*.json;*.dep;*.o</filesToClean>')

			_p(1,'</Config>')
		end
	end


--
-- Write out the source file tree.
--

	function m.visuald.files(prj)
		_p(1,'<Folder name="%s">', prj.name)

		local tr = project.getsourcetree(prj)

		tree.traverse(tr, {

			-- folders, virtual or otherwise, are handled at the internal nodes
			onbranchenter = function(node, depth)
				_p(depth, '<Folder name="%s">', node.name)
			end,

			onbranchexit = function(node, depth)
				_p(depth, '</Folder>')
			end,

			-- source files are handled at the leaves
			onleaf = function(node, depth)
				_p(depth, '<File path="%s" />', path.translate(node.relpath))

--				_p(depth, '<File path="%s">', path.translate(node.relpath))
--				m.visuald.fileConfiguration(prj, node, depth + 1)
--				_p(depth, '</File>')
			end

		}, false, 2)

		_p(1,'</Folder>')
	end

	function m.visuald.fileConfiguration(prj, node, depth)

		-- maybe we'll need this in the future...

	end


--
-- Output an individual project XML element.
--

	function m.visuald.element(depth, name, value, ...)
		local isTable = type(value) == "table"
		if not value or (isTable and #value == 0) then
			_p(depth, '<%s />', name)
		else
			if isTable then
				value = p.esc(table.implode(value, "", "", ";"))
				_p(depth, '<%s>%s</%s>', name, value, name)
			else
				if select('#',...) == 0 then
					value = p.esc(value)
				end
				_x(depth, string.format('<%s>%s</%s>', name, value, name), ...)
			end
		end
	end
