--
-- _xcode8.lua
-- Define the Apple XCode 8.0 action and support functions.
--

	local premake = premake
	premake.xcode8 = { }

	local xcode  = premake.xcode
	local xcode8 = premake.xcode8

	function xcode8.XCBuildConfiguration_Target(tr, target, cfg)
		local cfgname = xcode.getconfigname(cfg)
		local installpaths = {
			ConsoleApp = "/usr/local/bin",
			WindowedApp = "$(HOME)/Applications",
			SharedLib = "/usr/local/lib",
			StaticLib = "/usr/local/lib",
			Bundle    = "$(LOCAL_LIBRARY_DIR)/Bundles",
		}

		-- options table to return
		local options = {
			ALWAYS_SEARCH_USER_PATHS = "NO",
			GCC_DYNAMIC_NO_PIC = "NO",
			GCC_MODEL_TUNING = "G5",
			INSTALL_PATH = installpaths[cfg.kind],
			PRODUCT_NAME = cfg.buildtarget.basename,
		}

		if not cfg.flags.Symbols then
			options.DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"
		end

		if cfg.kind ~= "StaticLib" and cfg.buildtarget.prefix ~= "" then
			options.EXECUTABLE_PREFIX = cfg.buildtarget.prefix
		end

		if cfg.targetextension then
			local ext = cfg.targetextension
			options.EXECUTABLE_EXTENSION = iif(ext:startswith("."), ext:sub(2), ext)
		end

		if cfg.flags.ObjcARC then
			options.CLANG_ENABLE_OBJC_ARC = "YES"
		end

		local outdir = path.getdirectory(cfg.buildtarget.bundlepath)
		if outdir ~= "." then
			options.CONFIGURATION_BUILD_DIR = outdir
		end

		if tr.infoplist then
			options.INFOPLIST_FILE = tr.infoplist.cfg.name
		end

		local infoplist_file = nil

		for _, v in ipairs(cfg.files) do
			-- for any file named *info.plist, use it as the INFOPLIST_FILE
			if (string.find (string.lower (v), 'info.plist') ~= nil) then
				infoplist_file = string.format('$(SRCROOT)/%s', v)
			end
		end

		if infoplist_file ~= nil then
			options.INFOPLIST_FILE = infoplist_file
		end

		local action = premake.action.current()
		local get_opt = function(opt, def)
			return (opt and #opt > 0) and opt or def
		end

		local iosversion = get_opt(cfg.iostargetplatformversion, action.xcode.iOSTargetPlatformVersion)
		local macosversion = get_opt(cfg.macostargetplatformversion, action.xcode.macOSTargetPlatformVersion)
		local tvosversion = get_opt(cfg.tvostargetplatformversion, action.xcode.tvOSTargetPlatformVersion)

		if iosversion then
			options.IPHONEOS_DEPLOYMENT_TARGET = iosversion
		elseif macosversion then
			options.MACOSX_DEPLOYMENT_TARGET = macosversion
		elseif tvosversion then
			options.TVOS_DEPLOYMENT_TARGET = tvosversion
		end

		if cfg.kind == "Bundle" and not cfg.options.SkipBundling then
			options.PRODUCT_BUNDLE_IDENTIFIER = "genie." .. cfg.buildtarget.basename:gsub("%s+", ".") --replace spaces with .
			local ext = cfg.targetextension
			if ext then
				options.WRAPPER_EXTENSION = iif(ext:startswith("."), ext:sub(2), ext)
			else
				options.WRAPPER_EXTENSION = "bundle"
			end
		end

		return options
	end

	function xcode8.XCBuildConfiguration_Project(tr, prj, cfg)
		local cfgname = xcode.getconfigname(cfg)
		local archs = {
			Native      = nil,
			x32         = "i386",
			x64         = "x86_64",
			Universal32 = "$(ARCHS_STANDARD_32_BIT)",
			Universal64 = "$(ARCHS_STANDARD_64_BIT)",
			Universal   = "$(ARCHS_STANDARD_32_64_BIT)",
		}

		-- build list of "other" C/C++ flags
		local checks = {
			["-ffast-math"]          = cfg.flags.FloatFast,
			["-ffloat-store"]        = cfg.flags.FloatStrict,
			["-fomit-frame-pointer"] = cfg.flags.NoFramePointer,
		}

		local cflags = { }
		for flag, check in pairs(checks) do
			if check then
				table.insert(cflags, flag)
			end
		end

		-- build list of "other" linked flags. All libraries that aren't frameworks
		-- are listed here, so I don't have to try and figure out if they are ".a"
		-- or ".dylib", which Xcode requires to list in the Frameworks section
		local ldflags = { }
		for _, lib in ipairs(premake.getlinks(cfg, "system")) do
			if not xcode.isframework(lib) then
				table.insert(ldflags, "-l" .. lib)
			end
		end

		-- options table to return
		local options = {
			ARCHS                              = archs[cfg.platform],
			CLANG_WARN__DUPLICATE_METHOD_MATCH = "YES",
			CLANG_WARN_BOOL_CONVERSION         = "YES",
			CLANG_WARN_CONSTANT_CONVERSION     = "YES",
			CLANG_WARN_EMPTY_BODY              = "YES",
			CLANG_WARN_ENUM_CONVERSION         = "YES",
			CLANG_WARN_INFINITE_RECURSION      = "YES",
			CLANG_WARN_INT_CONVERSION          = "YES",
			CLANG_WARN_SUSPICIOUS_MOVE         = "YES",
			CLANG_WARN_UNREACHABLE_CODE        = "YES",
			CONFIGURATION_TEMP_DIR             = "$(OBJROOT)",
			ENABLE_STRICT_OBJC_MSGSEND         = "YES",
			ENABLE_TESTABILITY                 = "YES",
			GCC_C_LANGUAGE_STANDARD            = "gnu99",
			GCC_NO_COMMON_BLOCKS               = "YES",
			GCC_PREPROCESSOR_DEFINITIONS       = cfg.defines,
			GCC_SYMBOLS_PRIVATE_EXTERN         = "NO",
			GCC_WARN_64_TO_32_BIT_CONVERSION   = "YES",
			GCC_WARN_ABOUT_RETURN_TYPE         = "YES",
			GCC_WARN_UNDECLARED_SELECTOR       = "YES",
			GCC_WARN_UNINITIALIZED_AUTOS       = "YES",
			GCC_WARN_UNUSED_FUNCTION           = "YES",
			GCC_WARN_UNUSED_VARIABLE           = "YES",
			HEADER_SEARCH_PATHS                = table.join(cfg.includedirs, cfg.systemincludedirs),
			LIBRARY_SEARCH_PATHS               = cfg.libdirs,
			OBJROOT                            = cfg.objectsdir,
			ONLY_ACTIVE_ARCH                   = "YES",
			OTHER_CFLAGS                       = table.join(cflags, cfg.buildoptions, cfg.buildoptions_c),
			OTHER_CPLUSPLUSFLAGS               = table.join(cflags, cfg.buildoptions, cfg.buildoptions_cpp),
			OTHER_LDFLAGS                      = table.join(ldflags, cfg.linkoptions),
			SDKROOT                            = xcode.toolset,
			USER_HEADER_SEARCH_PATHS           = cfg.userincludedirs,
		}

		if tr.entitlements then
			options.CODE_SIGN_ENTITLEMENTS = tr.entitlements.cfg.name
		end

		local targetdir = path.getdirectory(cfg.buildtarget.bundlepath)
		if targetdir ~= "." then
			options.CONFIGURATION_BUILD_DIR = "$(SYMROOT)"
			options.SYMROOT = targetdir
		end

		if cfg.flags.Symbols then
			options.COPY_PHASE_STRIP = "NO"
		end

		local excluded = xcode.cfg_excluded_files(prj, cfg)
		if #excluded > 0 then
			options.EXCLUDED_SOURCE_FILE_NAMES = excluded
		end

		if cfg.flags.NoExceptions then
			options.GCC_ENABLE_CPP_EXCEPTIONS = "NO"
		end

		if cfg.flags.NoRTTI then
			options.GCC_ENABLE_CPP_RTTI = "NO"
		end

		if cfg.flags.Symbols and not cfg.flags.NoEditAndContinue then
			options.GCC_ENABLE_FIX_AND_CONTINUE = "YES"
		end

		if cfg.flags.NoExceptions then
			options.GCC_ENABLE_OBJC_EXCEPTIONS = "NO"
		end

		if cfg.flags.Optimize or cfg.flags.OptimizeSize then
			options.GCC_OPTIMIZATION_LEVEL = "s"
		elseif cfg.flags.OptimizeSpeed then
			options.GCC_OPTIMIZATION_LEVEL = 3
		else
			options.GCC_OPTIMIZATION_LEVEL = 0
		end

		if cfg.pchheader and not cfg.flags.NoPCH then
			options.GCC_PRECOMPILE_PREFIX_HEADER = "YES"

			-- Visual Studio requires the PCH header to be specified in the same way
			-- it appears in the #include statements used in the source code; the PCH
			-- source actual handles the compilation of the header. GCC compiles the
			-- header file directly, and needs the file's actual file system path in
			-- order to locate it.

			-- To maximize the compatibility between the two approaches, see if I can
			-- locate the specified PCH header on one of the include file search paths
			-- and, if so, adjust the path automatically so the user doesn't have
			-- add a conditional configuration to the project script.

			local pch = cfg.pchheader
			for _, incdir in ipairs(cfg.includedirs) do

				-- convert this back to an absolute path for os.isfile()
				local abspath = path.getabsolute(path.join(cfg.project.location, incdir))

				local testname = path.join(abspath, pch)
				if os.isfile(testname) then
					pch = path.getrelative(cfg.location, testname)
					break
				end
			end

			options.GCC_PREFIX_HEADER = pch
		end

		if cfg.flags.FatalWarnings then
			options.GCC_TREAT_WARNINGS_AS_ERRORS = "YES"
		end

		if cfg.kind == "Bundle" then
			options.MACH_O_TYPE = "mh_bundle"
		end

		if cfg.flags.StaticRuntime then
			options.STANDARD_C_PLUS_PLUS_LIBRARY_TYPE = "static"
		end

		if cfg.flags.PedanticWarnings or cfg.flags.ExtraWarnings then
			options.WARNING_CFLAGS = "-Wall"
		end

		if cfg.flags.Cpp11 then
			options.CLANG_CXX_LANGUAGE_STANDARD = "c++11"
		elseif cfg.flags.Cpp14 or cfg.flags.CppLatest then
			options.CLANG_CXX_LANGUAGE_STANDARD = "c++14"
		elseif cfg.flags.Cpp17 then
			-- XCode8 does not support C++17, but other actions use this as
			-- base that *do* support C++17, so check if this is the current
			-- action before erroring.
			if premake.action.current() == premake.action.get("xcode8") then
				error("XCode8 does not support C++17.")
			end
		end

		for _, val in ipairs(premake.xcode.parameters) do
			local eqpos = string.find(val, "=")
			if eqpos ~= nil then
				local key = string.trim(string.sub(val, 1, eqpos - 1))
				local value = string.trim(string.sub(val, eqpos + 1))
				options[key] = value
			end
		end

		return options
	end

	function xcode8.project(prj)
		local tr = xcode.buildprjtree(prj)
		xcode.Header(tr, 48)
		xcode.PBXBuildFile(tr)
		xcode.PBXContainerItemProxy(tr)
		xcode.PBXFileReference(tr,prj)
		xcode.PBXFrameworksBuildPhase(tr)
		xcode.PBXGroup(tr)
		xcode.PBXNativeTarget(tr)
		xcode.PBXProject(tr, "8.0")
		xcode.PBXReferenceProxy(tr)
		xcode.PBXResourcesBuildPhase(tr)
		xcode.PBXShellScriptBuildPhase(tr)
		xcode.PBXCopyFilesBuildPhase(tr)
		xcode.PBXSourcesBuildPhase(tr,prj)
		xcode.PBXVariantGroup(tr)
		xcode.PBXTargetDependency(tr)
		xcode.XCBuildConfiguration(tr, prj, {
			ontarget = xcode8.XCBuildConfiguration_Target,
			onproject = xcode8.XCBuildConfiguration_Project,
		})
		xcode.XCBuildConfigurationList(tr)
		xcode.Footer(tr)
	end


--
-- xcode8 action
--

	newaction
	{
		trigger         = "xcode8",
		shortname       = "Xcode 8",
		description     = "Generate Apple Xcode 8 project files",
		os              = "macosx",

		valid_kinds     = { "ConsoleApp", "WindowedApp", "StaticLib", "SharedLib", "Bundle" },

		valid_languages = { "C", "C++" },

		valid_tools     = {
			cc     = { "gcc" },
		},

		valid_platforms = {
			Native = "Native",
			x32 = "Native 32-bit",
			x64 = "Native 64-bit",
			Universal = "Universal",
		},

		default_platform = "Native",

		onsolution = function(sln)
			premake.generate(sln, "%%.xcworkspace/contents.xcworkspacedata", xcode.workspace_generate)
			premake.generate(sln, "%%.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings", xcode.workspace_settings)
			premake.generate(sln, "%%.xcworkspace/xcshareddata/xcschemes/-ALL-.xcscheme", xcode.workspace_scheme)
		end,

		onproject = function(prj)
			premake.generate(prj, "%%.xcodeproj/project.pbxproj", xcode8.project)
			xcode.generate_schemes(prj, "%%.xcodeproj/xcshareddata/xcschemes")
		end,

		oncleanproject = function(prj)
			premake.clean.directory(prj, "%%.xcodeproj")
			premake.clean.directory(prj, "%%.xcworkspace")
		end,

		oncheckproject = xcode.checkproject,

		xcode = {
			iOSTargetPlatformVersion = nil,
			macOSTargetPlatformVersion = nil,
			tvOSTargetPlatformVersion = nil,
		},
	}
