--
-- makefile.lua
-- Android NDK makefile generator for Premake.
-- Copyright (c) 2014 Will Vale and the Premake project
--

local ndk       = premake.modules.ndk
local project   = premake.project
local config    = premake.config
local make      = premake.make

-- Register the action with Premake.
newaction {
	trigger     = "ndk-makefile",
	shortname   = "Android NDK makefiles",
	description = "Generate makefiles for Android ndk-build",

	-- The capabilities of this action
	valid_kinds     = { "WindowedApp", "StaticLib", "SharedLib" },
	valid_languages = { "C", "C++" },
	valid_tools     = {
		cc     = { "gcc" },
	},

	onsolution = function(sln)
		-- There isn't anything meaningful to generate for solutions. ndk-build is really intended to 
		-- compile and link together all the components for a given app.
	end,

	onproject = function(prj)
		-- Not all projects can generate something sensible.
		if  not ndk.isValidProject(prj) then
			return
		end

		-- Need to generate one makefile per configuration
		for cfg in project.eachconfig(prj) do
			if cfg.platform ~= ndk.ANDROID then
				error('The only supported platform for NDK builds is "android"')
			end		

			-- Define closure to pass config
			function generateMakefileCallback(prj)
				ndk.generateMakefile(prj, cfg)
			end
			function generateAppMakefileCallback(prj)
				ndk.generateAppMakefile(prj, cfg)
			end

			-- Generate the ndk-build makefile
			premake.generate(prj, ndk.getMakefileName(prj, cfg, ndk.MAKEFILE), generateMakefileCallback)

			if cfg.kind == premake.WINDOWEDAPP then
				-- Generate the application makefile for application projects only
				premake.generate(prj, ndk.getMakefileName(prj, cfg, ndk.APPMAKEFILE), generateAppMakefileCallback)
			end
		end
	end,

	oncleansolution = function(sln)
		-- We don't generate anything for solutions, so there's nothing to clean.
	end,

	oncleanproject = function(prj)
		-- Need to clean one makefile per configuration
		for cfg in project.eachconfig(prj) do
			premake.clean.file(prj, ndk.getMakefileName(prj, cfg, ndk.MAKEFILE))

			if prj.kind == premake.WINDOWEDAPP then
				premake.clean.file(prj, ndk.getMakefileName(prj, cfg, ndk.APPMAKEFILE))
			end
		end
	end
}


-- Map premake cflags onto ndk-build cpp features
function ndk.getCppFeatures(cfg)
	local cppfeatures = {
		-- No flags to map yet
	}

	local flags = table.translate(cfg.flags, cppfeatures)

	-- turn on exceptions and RTTI by default, to match other toolsets
	if not cfg.flags.NoExceptions then
		table.insert(flags, "exceptions")
	end
	if not cfg.flags.NoRTTI then
		table.insert(flags, "rtti")
	end

	return flags
end

-- Makefiles go in the jni directory under the project path
function ndk.getMakefilePath(this, cfg)
	-- e.g. c:/root/myconfig/myproject/jni
	return path.join(ndk.getProjectPath(this, cfg), "")
end

-- Append basename
function ndk.getMakefileName(this, cfg, basename)
	return path.join(ndk.getMakefilePath(this, cfg), basename)
end

-- Generator for the application makefile, which contains app-wide settings.
function ndk.generateAppMakefile(prj, cfg)
	-- write a header showing the build options
	_p('# Android NDK application makefile autogenerated by Premake')
	_p('PROJECT_PATH := .')
	_p('')

	_p('# Application settings')
	if cfg.framework then
		_p('APP_PLATFORM := android-%d', ndk.getApiLevel(cfg))
	end
	if cfg.abis then
		_p('APP_ABI := %s', table.concat(cfg.abis, ' '))
	end
	if type(cfg.stl) == 'string' then
		_p('APP_STL := %s', cfg.stl)
	end
	if cfg.optimize == 'Off' then
		-- Optimise defaults to "release"
		_p('APP_OPTIM := debug')
	end
	_p('')			
end

-- Write a list of makefile dependencies
function ndk.writeDependencies(location, depends, cfg)
	for _,d in ipairs(depends) do
		if ndk.isValidProject(d) then	
			local p = project.getfilename(d, ndk.getMakefileName(d, cfg, ndk.MAKEFILE))
			p = path.getrelative(location, p)
			_p('include $(DEPENDENCY_PATH)/'..make.esc(p))
		end
	end
end

-- Write a list of relative paths following the tag, e.g. for source files, includes, ..
function ndk.writeRelativePaths(tag, location, paths, local_path)
		
	-- Remap paths relative to project and escape them
	for i,p in ipairs(paths) do
		paths[i] = make.esc(path.getrelative(location, p))
	end

	-- Optionally make paths relative to local_path
	local prefix = ''
	if local_path then
		prefix = '$(LOCAL_PATH)/'
	end			

	-- Call helper
	ndk.writeStrings(tag .. ' :=', prefix, paths)
end

-- Write a list of relative paths following the tag, e.g. for source files, includes, ..
function ndk.writeStrings(tag, prefix, strings)
	if #strings == 1 then
		-- Write simple string on one line
		_p("%s %s%s", tag, prefix, strings[1])
	elseif #strings > 1 then
		-- Write strings with continuation

		-- Continuation character and newline
		local suffix = '\\\n'

		-- Prepend tab
		prefix = '\t'..prefix

		-- Write strings from table
		_p("%s %s%s%s", tag, suffix, prefix, table.concat(strings, suffix..prefix))
	end
end

-- Create module name for an NDK module
function ndk.getModuleName(prj, cfg)
	if prj.kind == premake.WINDOWEDAPP then
		-- HACK: Want to specify targetname for projects, but only on Android. Does config scoping allow this?
		return cfg.targetname
	else
		return prj.name
	end
end

-- Extract list of dependencies the given kind
function ndk.getDependentModules(prj, cfg, kind)
	local names = {}
	for _,d in ipairs(project.getdependencies(prj)) do
		if d.kind == kind then
			table.insert(names, ndk.getModuleName(d, cfg))
		end
	end
	return names 
end

-- Generate the Android.mk makefile which builds a particular module.
function ndk.generateMakefile(prj, cfg)
	_p('# Android NDK project makefile autogenerated by Premake')
	_p('')

	-- This is $(LOCAL_PATH) in the makefile. We need to be relative to this in most cases.
	local local_path = ndk.getMakefilePath(prj, cfg)

	-- We include dependencies using the relative local path, but internal paths
	-- (to files, etc.) are expanded to get rid of the very long path recursion.
	_p('# Preamble')
	_p('DEPENDENCY_PATH := $(call my-dir)')
	_p('LOCAL_PATH := $(abspath $(DEPENDENCY_PATH))')
	_p('include $(CLEAR_VARS)')
	_p('')

	_p('# Project configuration')
	_p('LOCAL_MODULE := '..ndk.getModuleName(prj, cfg))
	ndk.writeStrings('LOCAL_CFLAGS' .. ' :=', '-D', cfg.defines)
	ndk.writeStrings('LOCAL_CFLAGS' .. ' +=', '', cfg.buildoptions)
	ndk.writeStrings('LOCAL_CPP_FEATURES' .. ' :=', '', ndk.getCppFeatures(cfg))

	-- Join linker options with linked libraries to get single table
	local link_options = cfg.linkoptions
	local links = config.getlinks(cfg, 'system', 'basename')
	for _,v in ipairs(links) do
		table.insert(link_options, '-l'..v)
	end
	ndk.writeStrings('LOCAL_LDLIBS' .. ' :=', '', link_options)
	ndk.writeStrings('LOCAL_SHARED_LIBRARIES' .. ' :=', '', ndk.getDependentModules(prj, cfg, premake.SHAREDLIB))
	ndk.writeStrings('LOCAL_STATIC_LIBRARIES' .. ' :=', '', ndk.getDependentModules(prj, cfg, premake.STATICLIB))
	ndk.writeStrings('LOCAL_STATIC_LIBRARIES' .. ' +=', '', cfg.staticlibs)
	_p('')

	_p('# Include paths')
	ndk.writeRelativePaths('LOCAL_C_INCLUDES', local_path, cfg.includedirs, true)
	_p('')

	_p('# Source files')
	-- Filter out header files
	local files = {}
	for _,f in ipairs(cfg.files) do
		if path.iscppfile(f) or path.iscfile(f) then
			table.insert(files, f)
		end
	end
	ndk.writeRelativePaths('LOCAL_SRC_FILES', local_path, files, false)
	_p('')
	
	_p('# Build instructions')
	if cfg.kind == premake.STATICLIB then
		_p('include $(BUILD_STATIC_LIBRARY)')
	else
		_p('include $(BUILD_SHARED_LIBRARY)')
	end
	_p('')

	_p('# Project dependencies')
	ndk.writeDependencies(local_path, project.getdependencies(prj), cfg)
	_p('')
	
	for _,f in ipairs(prj.import) do
		_p("$(call import-module, " .. f .. ")")
	end
	
	

end
