-- Copyright (C) 1997-2015 Sam Lantinga <slouken@libsdl.org>
--
-- This software is provided 'as-is', without any express or implied
-- warranty.  In no event will the authors be held liable for any damages
-- arising from the use of this software.
--
-- Permission is granted to anyone to use this software for any purpose,
-- including commercial applications, and to alter it and redistribute it
-- freely.
--
-- Meta-build system using premake created and maintained by
-- Benjamin Henning <b.henning@digipen.edu>

--[[
premake4.lua

	This script sets up the entire premake system. It's responsible for executing
	all of the definition scripts for the SDL2 library and the entire test suite,
	or demos for the iOS platform. It handles each specific platform and uses the
	setup state to generate both the configuration header file needed to build
	SDL2 and the premake lua script to generate the target project files.
]]

-- string utility functions
dofile "util/sdl_string.lua"
-- utility file wrapper for some useful functions
dofile "util/sdl_file.lua"
-- system for defining SDL projects
dofile "util/sdl_projects.lua"
-- offers a utility function for finding dependencies specifically on windows
dofile "util/sdl_depends.lua"
-- system for generating a *config.h file used to build the SDL2 library
dofile "util/sdl_gen_config.lua"
-- functions to handle complicated dependency checks using CMake-esque functions
dofile "util/sdl_check_compile.lua"
-- a list of dependency functions for the SDL2 project and any other projects
dofile "util/sdl_dependency_checkers.lua"

-- the following are various options for configuring the meta-build system
newoption {
	trigger = "to",
	value   = "path",
	description = "Set the base output directory for the generated and executed lua file."
}

newoption {
	trigger = "mingw",
	description = "Runs the premake generation script targeted to MinGW."
}

newoption {
	trigger = "cygwin",
	description = "Runs the premake generation script targeted to Cygwin."
}

newoption {
	trigger = "ios",
	description = "Runs the premake generation script targeted to iOS."
}

-- determine the localized destination path
local baseLoc = "./"
if _OPTIONS["to"] then
	baseLoc = _OPTIONS["to"]:gsub("\\", "/")
end

local deps = SDL_getDependencies()
for _,v in ipairs(deps) do
	newoption {
		trigger = v:lower(),
		description = "Force on the dependency: " .. v
	}
end

-- clean action
if _ACTION == "clean" then
	-- this is kept the way it is because premake's default method of cleaning the
	-- build tree is not very good standalone, whereas the following correctly
	-- cleans every build option
	print("Cleaning the build environment...")
	os.rmdir(baseLoc .. "/SDL2")
	os.rmdir(baseLoc .. "/SDL2main")
	os.rmdir(baseLoc .. "/SDL2test")
	os.rmdir(baseLoc .. "/tests")
	os.rmdir(baseLoc .. "/Demos")
	os.rmdir(baseLoc .. "/ipch") -- sometimes shows up
	os.remove(baseLoc .. "/SDL.sln")
	os.remove(baseLoc .. "/SDL.suo")
	os.remove(baseLoc .. "/SDL.v11.suo")
	os.remove(baseLoc .. "/SDL.sdf")
	os.remove(baseLoc .. "/SDL.ncb")
	os.remove(baseLoc .. "/SDL-gen.lua")
	os.remove(baseLoc .. "/SDL_config_premake.h")
	os.remove(baseLoc .. "/Makefile")
	os.rmdir(baseLoc .. "/SDL.xcworkspace")
	os.exit()
end

-- only run through standard execution if not in help mode
if _OPTIONS["help"] == nil then
	-- load all of the project definitions
	local results = os.matchfiles("projects/**.lua")
	for _,dir in ipairs(results) do
		dofile(dir)
	end

	-- figure out which configuration template to use
	local premakeConfigHeader = baseLoc .. "/SDL_config_premake.h"
	-- minimal configuration is the default
	local premakeTemplateHeader = "./config/SDL_config_minimal.template.h"
	if SDL_getos() == "windows" or SDL_getos() == "mingw" then
		premakeTemplateHeader = "./config/SDL_config_windows.template.h"
	elseif SDL_getos() == "macosx" then
		premakeTemplateHeader = "./config/SDL_config_macosx.template.h"
	elseif SDL_getos() == "ios" then
		premakeTemplateHeader = "./config/SDL_config_iphoneos.template.h"
	elseif os.get() == "linux" then
		premakeTemplateHeader = "./config/SDL_config_linux.template.h"
	elseif SDL_getos() == "cygwin" then
		premakeTemplateHeader = "./config/SDL_config_cygwin.template.h"
	end

	local genFile = baseLoc .. "/SDL-gen.lua"
	local file = fileopen(genFile, "wt")
	print("Generating " .. genFile .. "...")
	-- begin generating the config header file
	startGeneration(premakeConfigHeader, premakeTemplateHeader)

	-- begin generating the actual premake script
	file:print(0, "-- Premake script generated by Simple DirectMedia Layer meta-build script")
	file:print(1, 'solution "SDL"')
	local platforms = { }
	local platformsIndexed = { }
		for n,p in pairs(projects) do
			if p.platforms and #p.platforms ~= 0 then
				for k,v in pairs(p.platforms) do
					platforms[v] = true
				end
			end
		end
		for n,v in pairs(platforms) do
			platformsIndexed[#platformsIndexed + 1] = n
		end
		file:print(2, implode(platformsIndexed, 'platforms {', '"', '"', ', ', '}'))
		file:print(2, 'configurations { "Debug", "Release" }')
		for n,p in pairs(projects) do
			if p.compat then
				local proj = {}
				if p.projectLocation ~= nil then
					proj.location = p.projectLocation .. "/" .. p.name
				else
					proj.location = p.name .. "/"
				end
				proj.includedirs = { path.getrelative(baseLoc,
					path.getdirectory(premakeConfigHeader)),
					path.getrelative(baseLoc, "../include") }
				proj.libdirs = { }
				proj.files = { }
				local links = { }
				local dbgCopyTable = { }
				local relCopyTable = { }
				-- custom links that shouldn't exist...
				-- (these should always happen before dependencies)
				if p.customLinks ~= nil then
					for k,lnk in pairs(p.customLinks) do
						table.insert(links, lnk)
					end
				end
				-- setup project dependencies
				local dependencyLocs = { }
				if p.projectDependencies ~= nil and #p.projectDependencies ~= 0 then
					for k,projname in pairs(p.projectDependencies) do
						local depproj = projects[projname]
						-- validation that it exists and can be linked to
						if depproj ~= nil and (depproj.kind == "SharedLib" or depproj.kind == "StaticLib") then
							if depproj.kind == "SharedLib" then
								local deplocation = nil
								if depproj.projectLocation ~= nil then
									deplocation = depproj.projectLocation .. "/" .. p.name
								else
									deplocation = depproj.name .. "/"
								end
								table.insert(dependencyLocs, { location = deplocation, name = projname })
							else -- static lib
								-- we are now dependent on everything the static lib is dependent on
								if depproj.customLinks ~= nil then
									for k,lnk in pairs(depproj.customLinks) do
										table.insert(links, lnk)
									end
								end
								-- also include links from dependencies
								for i,d in pairs(depproj.dependencyTree) do
									if d.links then
										for k,v in pairs(d.links) do
											local propPath = v:gsub("\\", "/")
											table.insert(links, propPath)
										end
									end
								end
							end
							-- finally, depend on the project itself
							table.insert(links, projname)
						elseif depproj == nil then
							print("Warning: Missing external dependency for project: ".. p.name ..
								". Be sure you setup project dependencies in a logical order.")
						else
							print("Warning: Cannot link " .. p.name .. " to second project " ..
								projname .. " because the second project is not a library.")
						end
					end
				end
				-- iterate across all root directories, matching source directories
				local dirs = createDirTable(p.sourcedir)
				-- but first, handle any files specifically set in the project, rather than
				-- its dependencies
				-- register c and h files in this directory
				if (p.files ~= nil and #p.files ~= 0) or (p.paths ~= nil and #p.paths ~= 0) then
					-- handle all lists of files
					if p.files ~= nil and #p.files ~= 0 then
						for k,filepat in pairs(p.files) do
							for k,f in pairs(os.matchfiles(p.sourcedir .. filepat)) do
								table.insert(proj.files, path.getrelative(baseLoc, f))
							end
						end
					end -- end props files if
					-- add all .c/.h files from each path
					-- handle all related paths
					if p.paths ~= nil and #p.paths ~= 0 then
						for j,filepat in ipairs(p.paths) do
							for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.c")) do
								table.insert(proj.files, path.getrelative(baseLoc, f))
							end
							for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.h")) do
								table.insert(proj.files, path.getrelative(baseLoc, f))
							end
							-- mac osx
							for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.m")) do
								table.insert(proj.files, path.getrelative(baseLoc, f))
							end
						end
					end -- end of props paths if
				end -- end of check for files/paths in main project
				-- if this project has any configuration flags, add them to the current file
				if p.config then
					addConfig(p.config)
				end
				-- now, handle files and paths for dependencies
				for i,props in ipairs(p.dependencyTree) do
					if props.compat then
						-- register c and h files in this directory
						-- handle all lists of files
						if props.files ~= nil and #props.files ~= 0 then
							for k,filepat in pairs(props.files) do
								for k,f in pairs(os.matchfiles(p.sourcedir .. filepat)) do
									table.insert(proj.files, path.getrelative(baseLoc, f))
								end
							end
						end -- end props files if
						-- add all .c/.h files from each path
						-- handle all related paths
						if props.paths ~= nil and #props.paths ~= 0 then
							for j,filepat in ipairs(props.paths) do
								for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.c")) do
									table.insert(proj.files, path.getrelative(baseLoc, f))
								end
								for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.h")) do
									table.insert(proj.files, path.getrelative(baseLoc, f))
								end
								-- mac osx
								for k,f in pairs(os.matchfiles(p.sourcedir .. filepat .. "*.m")) do
									table.insert(proj.files, path.getrelative(baseLoc, f))
								end
							end
						end -- end of props paths if
						-- if this dependency has any special configuration flags, add 'em
						if props.config then
							addConfig(props.config)
						end -- end of props config if check
					end -- end check for compatibility
				end -- end of props loop
				--local debugConfig = configuration("Debug")
				local debugConfig = {}
				local releaseConfig = {}
				debugConfig.defines = { "USING_PREMAKE_CONFIG_H", "_DEBUG" }
				releaseConfig.defines = { "USING_PREMAKE_CONFIG_H", "NDEBUG" }
				-- setup per-project defines
				if p.defines ~= nil then
					for k,def in pairs(p.defines) do
						table.insert(debugConfig.defines, def)
						table.insert(releaseConfig.defines, def)
					end
				end
				debugConfig.buildoptions = { }
				if SDL_getos() == "windows" then
					table.insert(debugConfig.buildoptions, "/MDd")
				end
				debugConfig.linkoptions = { }
				releaseConfig.buildoptions = {}
				releaseConfig.linkoptions = {}
				local baseBuildDir = "/Build"
				if os.get() == "windows" then
					baseBuildDir = "/Win32"
				end
				debugConfig.flags = { "Symbols" }
				debugConfig.targetdir = proj.location .. baseBuildDir .. "/Debug"
				releaseConfig.flags = { "OptimizeSpeed" }
				releaseConfig.targetdir = proj.location .. baseBuildDir .. "/Release"
				-- setup postbuild options
				local dbgPostbuildcommands = { }
				local relPostbuildcommands = { }
				-- handle copying depended shared libraries to correct folders
				if os.get() == "windows" then
					for k,deploc in pairs(dependencyLocs) do
						table.insert(dbgCopyTable, { src = deploc.location .. baseBuildDir .. "/Debug/" .. deploc.name .. ".dll",
							dst = debugConfig.targetdir .. "/" .. deploc.name .. ".dll" })
						table.insert(relCopyTable, { src = deploc.location .. baseBuildDir .. "/Release/" .. deploc.name .. ".dll",
							dst = releaseConfig.targetdir .. "/" .. deploc.name .. ".dll" })
					end
				end
				if p.copy ~= nil then
					for k,file in pairs(p.copy) do
						-- the following builds relative paths native to the current system for copying, other
						-- than the copy command itself, this is essentially cross-platform for paths
					
						-- all custom copies should be relative to the current working directory
						table.insert(dbgCopyTable, { src = path.getrelative(baseLoc, p.sourcedir .. "/" .. file), dst = debugConfig.targetdir .. "/" .. file })
						table.insert(relCopyTable, { src = path.getrelative(baseLoc, p.sourcedir .. "/" .. file), dst = releaseConfig.targetdir .. "/" .. file })
					end
				end
				for k,file in pairs(dbgCopyTable) do
					-- all copies should be relative to project location, based on platform
					local relLocation = "./"
					--if os.get() == "windows" then
						relLocation = proj.location
					--end
					local fromPath = "./" .. path.getrelative(relLocation, file.src)
					local toPath = "./" .. path.getrelative(relLocation, file.dst)
					local toPathParent = path.getdirectory(toPath)
					local copyCommand = "cp"
					local destCheck = "if [ ! -d \\\"" .. toPathParent .. "\\\" ]; then mkdir -p \\\"" .. toPathParent .. "\\\"; fi"
					if SDL_getos() ~= "windows" and fromPath:find("*") ~= nil then
						-- to path must be a directory for * copies
						toPath = path.getdirectory(toPath)
					end
					if SDL_getos() == "windows" then
						fromPath = path.translate(fromPath, "/"):gsub("/", "\\\\")
						toPath = path.translate(toPath, "/"):gsub("/", "\\\\")
						toPathParent = path.translate(toPathParent, "/"):gsub("/", "\\\\")
						copyCommand = "copy"
						destCheck = "if not exist \\\"" .. toPathParent .. "\\\" ( mkdir \\\"" .. toPathParent .. "\\\" )"
					else
						fromPath = path.translate(fromPath, nil):gsub("\\", "/")
						toPath = path.translate(toPath, nil):gsub("\\", "/")
					end
					-- command will check for destination directory to exist and, if it doesn't,
					-- it will make the directory and then copy over any assets
					local quotedFromPath = fromPath
					if SDL_getos() == "windows" or fromPath:find("*") == nil then
						quotedFromPath = '\\"' .. quotedFromPath .. '\\"'
					end
					table.insert(dbgPostbuildcommands, destCheck)
					table.insert(dbgPostbuildcommands,
						copyCommand .. " " ..
						quotedFromPath .. " \\\"" ..
						toPath .. "\\\"")
				end
				for k,file in pairs(relCopyTable) do
					-- all copies should be relative to project location, based on platform
					local relLocation = "./"
					relLocation = proj.location
					local fromPath = "./" .. path.getrelative(relLocation, file.src)
					local toPath = "./" .. path.getrelative(relLocation, file.dst)
					local toPathParent = path.getdirectory(toPath)
					local copyCommand = "cp"
					local destCheck = "if [ ! -d \\\"" .. toPathParent .. "\\\" ]; then mkdir -p \\\"" .. toPathParent .. "\\\"; fi"
					if SDL_getos() ~= "windows" and fromPath:find("*") ~= nil then
						-- to path must be a directory for * copies
						toPath = path.getdirectory(toPath)
					end
					if SDL_getos() == "windows" then
						fromPath = path.translate(fromPath, "/"):gsub("/", "\\\\")
						toPath = path.translate(toPath, "/"):gsub("/", "\\\\")
						toPathParent = path.translate(toPathParent, "/"):gsub("/", "\\\\")
						copyCommand = "copy"
						destCheck = "if not exist \\\"" .. toPathParent .. "\\\" ( mkdir \\\"" .. toPathParent .. "\\\" )"
					else
						fromPath = path.translate(fromPath, nil):gsub("\\", "/")
						toPath = path.translate(toPath, nil):gsub("\\", "/")
					end
					-- command will check for destination directory to exist and, if it doesn't,
					-- it will make the directory and then copy over any assets
					local quotedFromPath = fromPath
					if SDL_getos() == "windows" or fromPath:find("*") == nil then
						quotedFromPath = '\\"' .. quotedFromPath .. '\\"'
					end
					table.insert(relPostbuildcommands, destCheck)
					table.insert(relPostbuildcommands,
						copyCommand .. " " ..
						quotedFromPath .. " \\\"" ..
						toPath .. "\\\"")
				end
				debugConfig.postbuildcommands = dbgPostbuildcommands
				debugConfig.links = links
				releaseConfig.postbuildcommands = relPostbuildcommands
				releaseConfig.links = links -- release links?
				for i,d in pairs(p.dependencyTree) do
					if d.includes then
						for k,v in pairs(d.includes) do
							local propPath = v:gsub("\\", "/")
							proj.includedirs[propPath] = propPath
						end
					end
					if d.libs then
						for k,v in pairs(d.libs) do
							local propPath = v:gsub("\\", "/")
							proj.libdirs[propPath] = propPath
						end
					end
					if d.links then
						for k,v in pairs(d.links) do
							local propPath = v:gsub("\\", "/")
							debugConfig.links[#debugConfig.links + 1] = propPath
						end
					end
				end
				if #proj.files > 0 then
					file:print(1, 'project "' .. p.name .. '"')
					file:print(2, 'targetname "' .. p.name .. '"')
					-- note: commented out because I think this hack is unnecessary
					--if iOSMode and p.kind == "ConsoleApp" then
						-- hack for iOS where we cannot build "tools"/ConsoleApps in
						-- Xcode for iOS, so we convert them over to WindowedApps
					--	p.kind = "WindowedApp"
					--end
					file:print(2, 'kind "' .. p.kind .. '"')
					file:print(2, 'language "' .. p.language .. '"')
					file:print(2, 'location "' .. proj.location .. '"')
					file:print(2, 'flags { "NoExceptions" }') -- NoRTTI
					file:print(2, 'buildoptions { }')--"/GS-" }')
					file:print(2, implode(proj.includedirs, 'includedirs {', '"', '"', ', ', '}'))
					file:print(2, implode(proj.libdirs, 'libdirs {', '"', '"', ', ', '}'))
					file:print(2, implode(proj.files, 'files {', '"', '"', ', ', '}'))
					-- debug configuration
					file:print(2, 'configuration "Debug"')
					file:print(3, 'targetdir "' .. debugConfig.targetdir .. '"')
					-- debug dir is relative to the solution's location
					file:print(3, 'debugdir "' .. debugConfig.targetdir .. '"')
					file:print(3, implode(debugConfig.defines, 'defines {', '"', '"', ', ', '}'))
					file:print(3, implode(debugConfig.links, "links {", '"', '"', ', ', "}"))
					if SDL_getos() == "mingw" then
						-- static runtime
						file:print(3, 'linkoptions { "-lmingw32 -static-libgcc" }')
					end
					if SDL_getos() == "cygwin" then
						file:print(3, 'linkoptions { "-static-libgcc" }')
					end
					file:print(3, implode(debugConfig.flags, "flags {", '"', '"', ', ', "}"))
					file:print(3, implode(debugConfig.postbuildcommands, "postbuildcommands {", '"', '"', ', ', "}"))
					-- release configuration
					file:print(2, 'configuration "Release"')
					file:print(3, 'targetdir "' .. releaseConfig.targetdir .. '"')
					-- debug dir is relative to the solution's location
					file:print(3, 'debugdir "' .. releaseConfig.targetdir .. '"')
					file:print(3, implode(releaseConfig.defines, 'defines {', '"', '"', ', ', '}'))
					file:print(3, implode(releaseConfig.links, "links {", '"', '"', ', ', "}"))
					if SDL_getos() == "mingw" then
						-- static runtime
						file:print(3, 'linkoptions { "-lmingw32 -static-libgcc" }')
					end
					file:print(3, implode(releaseConfig.flags, "flags {", '"', '"', ', ', "}"))
					file:print(3, implode(releaseConfig.postbuildcommands, "postbuildcommands {", '"', '"', ', ', "}"))
				end -- end check for valid project (files to build)
			end -- end compatibility check for projects
		end -- end for loop for projects

	endGeneration() -- finish generating the config header file
	file:close()

	-- generation is over, now execute the generated file, setup the premake
	-- solution, and let premake execute the action and generate the project files
	dofile(genFile)
end -- end check for not being in help mode