--!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
-- @file        vs201x_solution.lua
--

-- imports
import("core.project.project")
import("vsfile")
import("vsutils")
import("private.utils.target", {alias = "target_utils"})

-- make header
function _make_header(slnfile, vsinfo)
    slnfile:print("Microsoft Visual Studio Solution File, Format Version %s.00", vsinfo.solution_version)
    slnfile:print("# Visual Studio %s", vsinfo.vstudio_version)
end

-- make projects
function _make_projects(slnfile, vsinfo)

    -- make all targets
    local groups = {}
    local targets = {}
    local vctool = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"
    local project_targets = target_utils.get_project_targets()
    for targetname, target in table.orderpairs(project_targets) do
        -- we need to set startup project for default or binary target
        -- @see https://github.com/xmake-io/xmake/issues/1249
        if target:get("default") == true then
            table.insert(targets, 1, target)
        elseif target:is_binary() then
            local first_target = targets[1]
            if not first_target or first_target:get("default") ~= true then
                table.insert(targets, 1, target)
            else
                table.insert(targets, target)
            end
        else
            table.insert(targets, target)
        end
    end
    for _, target in ipairs(targets) do
        local targetname = target:name()
        slnfile:enter("Project(\"{%s}\") = \"%s\", \"%s\\%s.vcxproj\", \"{%s}\"", vctool, targetname, targetname, targetname, hash.uuid4(targetname))
        for _, dep in ipairs(target:get("deps")) do
            slnfile:enter("ProjectSection(ProjectDependencies) = postProject")
            slnfile:print("{%s} = {%s}", hash.uuid4(dep), hash.uuid4(dep))
            slnfile:leave("EndProjectSection")
        end
        slnfile:leave("EndProject")
        local group_path = target:get("group")
        if group_path and #(group_path:trim()) > 0 then
            local group_current_path
            local group_names = path.split(group_path)
            for idx, group_name in ipairs(group_names) do
                group_current_path = group_current_path and path.join(group_current_path, group_name) or group_name
                groups[group_current_path] = hash.uuid4("group." .. group_current_path)
            end
        end
    end

    -- make all groups
    local project_group_uuid = "2150E333-8FDC-42A3-9474-1A3956D46DE8"
    for group_path, group_uuid in table.orderpairs(groups) do
        local group_name = path.filename(group_path)
        slnfile:enter("Project(\"{%s}\") = \"%s\", \"%s\", \"{%s}\"",
            project_group_uuid, group_name, group_name, group_uuid)
        slnfile:leave("EndProject")
    end
end

-- make global
function _make_global(slnfile, vsinfo)

    -- enter global
    slnfile:enter("Global")

    -- add solution configuration platforms
    slnfile:enter("GlobalSection(SolutionConfigurationPlatforms) = preSolution")
    for _, mode in ipairs(vsinfo.modes) do
        for _, arch in ipairs(vsinfo.archs) do
            slnfile:print("%s|%s = %s|%s", mode, arch, mode, arch)
        end
    end
    slnfile:leave("EndGlobalSection")

    -- add project configuration platforms
    slnfile:enter("GlobalSection(ProjectConfigurationPlatforms) = postSolution")
    local project_targets = target_utils.get_project_targets()
    for targetname, target in table.orderpairs(project_targets) do
        for _, mode in ipairs(vsinfo.modes) do
            for _, arch in ipairs(vsinfo.archs) do
                local vs_arch = vsutils.vsarch(arch)
                slnfile:print("{%s}.%s|%s.ActiveCfg = %s|%s", hash.uuid4(targetname), mode, arch, mode, vs_arch)
                slnfile:print("{%s}.%s|%s.Build.0 = %s|%s", hash.uuid4(targetname), mode, arch, mode, vs_arch)
            end
        end
    end
    slnfile:leave("EndGlobalSection")

    -- add solution properties
    slnfile:enter("GlobalSection(SolutionProperties) = preSolution")
    slnfile:print("HideSolutionNode = FALSE")
    slnfile:leave("EndGlobalSection")

    -- add project groups
    slnfile:enter("GlobalSection(NestedProjects) = preSolution")
    local subgroups = {}
    for targetname, target in table.orderpairs(project_targets) do
        local group_path = target:get("group")
        if group_path then
            -- target -> group
            group_path = path.normalize(group_path)
            slnfile:print("{%s} = {%s}", hash.uuid4(targetname), hash.uuid4("group." .. group_path))
            -- group -> group -> ...
            local group_current_path
            local group_names = path.split(group_path)
            for idx, group_name in ipairs(group_names) do
                group_current_path = group_current_path and path.join(group_current_path, group_name) or group_name
                local group_name_sub = group_names[idx + 1]
                local key = group_name .. (group_name_sub or "")
                if group_name_sub and not subgroups[key] then
                    slnfile:print("{%s} = {%s}", hash.uuid4("group." .. path.join(group_current_path, group_name_sub)),
                        hash.uuid4("group." .. group_current_path))
                    subgroups[key] = true
                end
            end
        end
    end
    slnfile:leave("EndGlobalSection")

    -- leave global
    slnfile:leave("EndGlobal")
end

-- make solution
function make(vsinfo)

    -- init solution name
    vsinfo.solution_name = project.name() or ("vs" .. vsinfo.vstudio_version)

    -- open solution file
    local slnpath = path.join(vsinfo.solution_dir, vsinfo.solution_name .. ".sln")
    local slnfile = vsfile.open(slnpath, "w")

    -- init indent character
    vsfile.indentchar('\t')

    -- make header
    _make_header(slnfile, vsinfo)

    -- make projects
    _make_projects(slnfile, vsinfo)

    -- make global
    _make_global(slnfile, vsinfo)

    -- exit solution file
    slnfile:close()
end

