--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- Copyright (C) 2015-present, Xmake Open Source Community.
--
-- @author      ruki, Arthapz
-- @file        mapper.lua
--

import("support")
import("core.base.hashset")

-- get or create a target module mapper
function get_mapper_for(target, opt)
    local localcache = support.localcache()
    local mapper = localcache:get2(target:fullname(), "module_mapper")
    local invalidate = opt and opt.invalidate
    if not mapper or invalidate then
        mapper = {}
        localcache:set2(target:fullname(), "module_mapper", mapper)
    end
    return mapper
end

function reuse_modules(target)
    local localcache = support.localcache()
    local reused_modules = localcache:get2(target:fullname(), "reused_modules")
    for key, from_name in pairs(reused_modules) do
        local dep = target:dep(from_name)
        assert(dep)
        support.set_reused(target, dep, key)
    end
end

-- feed the module mapper
function feed(target, modules, sourcefiles)

    local mapper = get_mapper_for(target, {invalidate = true})
    local deps_names = hashset.new()
    local deps_names_map = {}
    local headerunit_aliases = {}
    for _, module in pairs(modules) do
        if module.headerunit and module.alias then
            table.insert(headerunit_aliases, module)
        elseif not module.sourcealias then
            local name = module.headerunit and (module.name .. module.key) or module.name
            if name then
                if name ~= "std" and name ~= "std.compat" and deps_names:has(name) then
                    raise("duplicate module name detected for \"" .. name .. "\"\n  <" .. target:fullname() .. "> -> " .. module.sourcefile .. "\n  <" .. deps_names_map[name] .. "> -> " .. mapper[name].sourcefile)
                end
                deps_names:insert(module.name)
                deps_names_map[name] = target:fullname()
                if module.headerunit then
                    if not mapper[name] then
                        mapper[name] = module
                    end
                else
                    mapper[name] = module
                end
            end
            if not module.headerunit and not mapper[module.sourcefile] then
                mapper[module.sourcefile] = table.clone(module)
                mapper[module.sourcefile].sourcealias = true
            end
        end
    end
    local reuse = target:policy("build.c++.modules.reuse") or
                  target:policy("build.c++.modules.tryreuse")
    -- replace target reused module by dep module
    if reuse then
        for _, sourcefile in ipairs(sourcefiles) do
            local from_dep = support.is_from_dep(target, sourcefile)
            if from_dep then
                local reused, from = support.is_reused(target, sourcefile)
                if reused then
                    local module = get(from, sourcefile)
                    if module.name then
		                    mapper[module.name] =  module
		                    for dep_name, dep_module in pairs(module.deps) do
		                        if dep_module.headerunit then
		                            local key = dep_name .. dep_module.key
		                            mapper[key] = get(from, key)
		                            local sourcefile = mapper[key].sourcefile
		                            mapper[sourcefile .. dep_module.key] = table.clone(mapper[key])
		                            mapper[sourcefile .. dep_module.key].alias = false
		                        end
		                    end
	                  end
                end
            end
        end
    end
    -- insert aliases
    for _, alias in pairs(headerunit_aliases) do
        local name = alias.name .. alias.key
        if not mapper[name] then
            local _module = table.clone(mapper[alias.sourcefile .. alias.key])
            _module.name = alias.name
            _module.alias = true
            mapper[name] = _module
        end
    end
end

-- get a module from target mapper by name
function get(target, name)
    local mapper = get_mapper_for(target)
    assert(mapper)
    return mapper[name]
end

