--
-- _cmake.lua
-- Define the CMake action(s).
-- Copyright (c) 2015 Miodrag Milanovic
-- Modifications and additions in 2017 by Maurizio Petrarota
--

local cmake = premake.cmake
local tree = premake.tree

local includestr = 'include_directories(../%s)'
local definestr = 'add_definitions(-D%s)'


local function is_excluded(prj, cfg, file)
    if table.icontains(prj.excludes, file) then
        return true
    end

    if table.icontains(cfg.excludes, file) then
        return true
    end

    return false
end

function cmake.excludedFiles(prj, cfg, src)
    for _, v in ipairs(src) do
        if (is_excluded(prj, cfg, v)) then
            _p(1, 'list(REMOVE_ITEM source_list ../%s)', v)
        end

    end
end

function cmake.list(value)
    if #value > 0 then
        return " " .. table.concat(value, " ")
    else
        return ""
    end
end

function cmake.listWrapped(value, prefix, postfix)
    if #value > 0 then
        return prefix .. table.concat(value, postfix .. prefix) .. postfix
    else
        return ""
    end
end

function cmake.files(prj)
    local ret = {}
    local tr = premake.project.buildsourcetree(prj)
    tree.traverse(tr, {
        onbranchenter = function(node, depth)
        end,
        onbranchexit = function(node, depth)
        end,
        onleaf = function(node, depth)
            assert(node, "unexpected empty node")
            if node.cfg then
                table.insert(ret, node.cfg.name)
                _p(1, '../%s', node.cfg.name)
            end
        end,
    }, true, 1)

    return ret
end

function cmake.header(prj)
    _p('# %s project autogenerated by GENie', premake.action.current().shortname)
    _p('cmake_minimum_required(VERSION 3.15)')
    if os.is("windows") then
        -- Add support for CMP0091, see https://cmake.org/cmake/help/latest/policy/CMP0091.html
        _p('cmake_policy(SET CMP0091 NEW)')
    end
    _p('')
    _p('project(%s)', premake.esc(prj.name))
    _p('')
    _p('include(GNUInstallDirs)')
end

function cmake.customtasks(prj)
    local dirs = {}
    local tasks = {}
    for _, custombuildtask in ipairs(prj.custombuildtask or {}) do
        for _, buildtask in ipairs(custombuildtask or {}) do
            table.insert(tasks, buildtask)
            local d = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s", path.getdirectory(path.getrelative(prj.location, buildtask[2])))
            if not table.contains(dirs, d) then
                table.insert(dirs, d)
                _p('file(MAKE_DIRECTORY \"%s\")', d)
            end
        end
    end
    _p('')

    for _, buildtask in ipairs(tasks) do
        local deps = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[1]))
        local outputs = string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[2]))
        local msg = ""

        for _, depdata in ipairs(buildtask[3] or {}) do
            deps = deps .. string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, depdata))
        end

        _p('add_custom_command(')
        _p(1, 'OUTPUT %s', outputs)
        _p(1, 'DEPENDS %s', deps)

        for _, cmdline in ipairs(buildtask[4] or {}) do
            if (cmdline:sub(1, 1) ~= "@") then
                local cmd = cmdline
                local num = 1
                for _, depdata in ipairs(buildtask[3] or {}) do
                    cmd = string.gsub(cmd, "%$%(" .. num .. "%)", string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, depdata)))
                    num = num + 1
                end

                cmd = string.gsub(cmd, "%$%(<%)", string.format("${CMAKE_CURRENT_SOURCE_DIR}/../%s ", path.getrelative(prj.location, buildtask[1])))
                cmd = string.gsub(cmd, "%$%(@%)", outputs)

                _p(1, 'COMMAND %s', cmd)
            else
                msg = cmdline
            end
        end
        _p(1, 'COMMENT \"%s\"', msg)
        _p(')')
        _p('')
    end
end

function cmake.depRules(prj)
    local maintable = {}
    for _, dependency in ipairs(prj.dependency) do
        for _, dep in ipairs(dependency) do
            if path.issourcefile(dep[1]) then
                local dep1 = premake.esc(path.getrelative(prj.location, dep[1]))
                local dep2 = premake.esc(path.getrelative(prj.location, dep[2]))
                if not maintable[dep1] then maintable[dep1] = {} end
                table.insert(maintable[dep1], dep2)
            end
        end
    end

    for key, _ in pairs(maintable) do
        local deplist = {}
        local depsname = string.format('%s_deps', path.getname(key))

        for _, d2 in pairs(maintable[key]) do
            table.insert(deplist, d2)
        end
        _p('set(')
        _p(1, depsname)
        for _, v in pairs(deplist) do
            _p(1, '${CMAKE_CURRENT_SOURCE_DIR}/../%s', v)
        end
        _p(')')
        _p('')
        _p('set_source_files_properties(')
        _p(1, '\"${CMAKE_CURRENT_SOURCE_DIR}/../%s\"', key)
        _p(1, 'PROPERTIES OBJECT_DEPENDS \"${%s}\"', depsname)
        _p(')')
        _p('')
    end
end

function cmake.commonRules(conf, str)
    local Dupes = {}
    local t2 = {}
    for _, cfg in ipairs(conf) do
        local cfgd = iif(str == includestr, cfg.includedirs, cfg.defines)
        for _, v in ipairs(cfgd) do
            if(t2[v] == #conf - 1) then
                _p(str, v)
                table.insert(Dupes, v)
            end
            if not t2[v] then
                t2[v] = 1
            else
                t2[v] = t2[v] + 1
            end
        end
    end
    return Dupes
end

function cmake.cfgRules(cfg, dupes, str)
    for _, v in ipairs(cfg) do
        if (not table.icontains(dupes, v)) then
            _p(1, str, v)
        end
    end
end

function cmake.removeCrosscompiler(platforms)
    for i = #platforms, 1, -1 do
        if premake.platforms[platforms[i]].iscrosscompiler then
            table.remove(platforms, i)
        end
    end
end

function cmake.project(prj)
    io.indent = "  "
    cmake.header(prj)
    _p('set(')
    _p('source_list')
    local source_files = cmake.files(prj)
    _p(')')
    _p('')

    local nativeplatform = iif(os.is64bit(), "x64", "x32")
    local cc = premake.gettool(prj)
    local platforms = premake.filterplatforms(prj.solution, cc.platforms, "Native")

    cmake.removeCrosscompiler(platforms)

    local configurations = {}

    for _, platform in ipairs(platforms) do
        for cfg in premake.eachconfig(prj, platform) do
            -- TODO: Extend support for 32-bit targets on 64-bit hosts
            if cfg.platform == nativeplatform then
                table.insert(configurations, cfg)
            end
        end
    end

    local commonIncludes = cmake.commonRules(configurations, includestr)
    local commonDefines = cmake.commonRules(configurations, definestr)

    _p('')

    for _, cfg in ipairs(configurations) do
        _p('if(CMAKE_BUILD_TYPE MATCHES \"%s\")', cfg.name)

        -- list excluded files
        cmake.excludedFiles(prj, cfg, source_files)

        -- add includes directories
        cmake.cfgRules(cfg.includedirs, commonIncludes, includestr)

        -- add build defines
        cmake.cfgRules(cfg.defines, commonDefines, definestr)

        -- set CXX flags
        _p(1, 'set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} %s\")', cmake.list(table.join(cc.getcppflags(cfg), cc.getcflags(cfg), cc.getcxxflags(cfg), cfg.buildoptions, cfg.buildoptions_cpp)))

        -- set C flags
        _p(1, 'set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} %s\")', cmake.list(table.join(cc.getcppflags(cfg), cc.getcflags(cfg), cfg.buildoptions, cfg.buildoptions_c)))

        _p('endif()')
        _p('')
    end

    -- force CPP if needed
    if (prj.options.ForceCPP) then
        _p('set_source_files_properties(${source_list} PROPERTIES LANGUAGE CXX)')
    end

    -- add custom tasks
    cmake.customtasks(prj)

    -- per-dependency build rules
    cmake.depRules(prj)

    for _, cfg in ipairs(configurations) do
        _p('if(CMAKE_BUILD_TYPE MATCHES \"%s\")', cfg.name)

        if (prj.kind == 'StaticLib') then
            _p(1, 'add_library(%s STATIC ${source_list})', premake.esc(cfg.buildtarget.basename))
            -- Install
            _p(1, 'install(TARGETS %s RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR})', premake.esc(cfg.buildtarget.basename))
        end

        if (prj.kind == 'SharedLib') then
            _p(1, 'add_library(%s SHARED ${source_list})', premake.esc(cfg.buildtarget.basename))
            -- Install
            _p(1, 'install(TARGETS %s RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR})', premake.esc(cfg.buildtarget.basename))
        end
        if (prj.kind == 'ConsoleApp' or prj.kind == 'WindowedApp') then
            _p(1, 'add_executable(%s ${source_list})', premake.esc(cfg.buildtarget.basename))

            local libdirs = cmake.listWrapped(premake.esc(premake.getlinks(cfg, "all", "directory")), " -L\"../", "\"")

            _p(1, 'target_link_libraries(%s%s%s%s%s%s)', premake.esc(cfg.buildtarget.basename), libdirs, cmake.list(cfg.linkoptions), cmake.list(cc.getldflags(cfg)), cmake.list(premake.esc(premake.getlinks(cfg, "siblings", "basename"))), cmake.list(cc.getlinkflags(cfg)))

            -- Install
            _p(1, 'install(TARGETS %s RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})', premake.esc(cfg.buildtarget.basename))
        end
        _p('endif()')
        _p('')
    end
end