--!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        archive.lua
--

-- imports
import("core.base.option")
import("lib.detect.find_file")
import("lib.detect.find_tool")
import("archive_xmz")
import("extension", {alias = "get_archive_extension"})

-- archive archivefile using xmake compress module
function _archive_using_xmz(archivefile, inputfiles, extension, opt)
    archive_xmz(archivefile, inputfiles, opt)
    return true
end

-- archive archivefile using zip
function _archive_using_zip(archivefile, inputfiles, extension, opt)

    -- find zip
    local zip = find_tool("zip")
    if not zip then
        return false
    end

    -- init argv
    local argv = {archivefile}
    if not option.get("verbose") then
        table.insert(argv, "-q")
    end
    if opt.excludes then
        table.insert(argv, "-x")
        for _, exclude in ipairs(opt.excludes) do
            table.insert(argv, exclude)
        end
    end
    local compress = opt.compress
    if compress then
        if compress == "faster" or compress == "fastest" then
            table.insert(argv, "-1")
        elseif compress == "better" or compress == "best" then
            table.insert(argv, "-9")
        end
    end
    if opt.recurse then
        table.insert(argv, "-r")
    end
    local inputlistfile = os.tmpfile()
    if type(inputfiles) == "table" then
        local file = io.open(inputlistfile, "w")
        for _, inputfile in ipairs(inputfiles) do
            file:print(inputfile)
        end
        file:close()
        table.insert(argv, "-@")
    else
        table.insert(argv, inputfiles)
    end

    -- archive it
    os.vrunv(zip.program, argv, {curdir = opt.curdir, stdin = inputlistfile})
    if inputlistfile then
        os.tryrm(inputlistfile)
    end
    return true
end

-- archive archivefile using 7z
function _archive_using_7z(archivefile, inputfiles, extension, opt)

    -- find 7z
    local z7 = find_tool("7z")
    if not z7 then
        return false
    end

    -- init argv
    local argv = {"a"}
    if extension == ".gz" then
        table.insert(argv, "-tgzip")
    end
    if extension == ".tar" then
        table.insert(argv, "-ttar")
    end
    if extension == ".xz" then
        table.insert(argv, "-txz")
    end
    table.insert(argv, archivefile)
    table.insert(argv, "-y")
    local excludesfile
    if opt.excludes then
        excludesfile = os.tmpfile()
        io.writefile(excludesfile, table.concat(table.wrap(opt.excludes), '\n'))
        table.insert(argv, "-xr@" .. excludesfile)
    end
    local compress = opt.compress
    if compress and extension ~= ".tar" then
        if compress == "fastest" then
            table.insert(argv, "-mx1")
        elseif compress == "faster" then
            table.insert(argv, "-mx3")
        elseif compress == "better" then
            table.insert(argv, "-mx7")
        elseif compress == "best" then
            table.insert(argv, "-mx9")
        end
    end
    if opt.recurse then
        table.insert(argv, "-r")
    end
    local inputlistfile = os.tmpfile()
    if type(inputfiles) == "table" then
        local file = io.open(inputlistfile, "w")
        for _, inputfile in ipairs(inputfiles) do
            file:print(inputfile)
        end
        file:close()
        table.insert(argv, "-i@" .. inputlistfile)
    else
        table.insert(argv, inputfiles)
    end

    -- archive it
    os.vrunv(z7.program, argv, {curdir = opt.curdir})

    -- remove the excludes files
    if excludesfile then
        os.tryrm(excludesfile)
    end
    if inputlistfile then
        os.tryrm(inputlistfile)
    end
    return true
end

-- archive archivefile using xz
function _archive_using_xz(archivefile, inputfiles, extension, opt)

    -- find xz
    local xz = find_tool("xz")
    if not xz then
        return false
    end

    -- init argv
    local argv = {"-z", "-k", "-c", archivefile}
    if not option.get("verbose") then
        table.insert(argv, "-q")
    end
    local compress = opt.compress
    if compress then
        if compress == "fastest" then
            table.insert(argv, "-1")
        elseif compress == "faster" then
            table.insert(argv, "-3")
        elseif compress == "better" then
            table.insert(argv, "-7")
        elseif compress == "best" then
            table.insert(argv, "-9")
        end
    end
    if type(inputfiles) == "table" then
        for _, inputfile in ipairs(inputfiles) do
            table.insert(argv, inputfile)
        end
    else
        table.insert(argv, inputfiles)
    end

    -- archive it
    os.vrunv(xz.program, argv, {stdout = archivefile, curdir = opt.curdir})
    return true
end

-- archive archivefile using gzip
function _archive_using_gzip(archivefile, inputfiles, extension, opt)

    -- find gzip
    local gzip = find_tool("gzip")
    if not gzip then
        return false
    end

    -- init argv
    local argv = {"-c", archivefile}
    local keep = _g.gzip_keep
    if keep == nil then
        -- https://github.com/xmake-io/xmake/issues/7361
        keep = try {function ()
            local outdata, errdata = os.iorunv(gzip.program, {"--help"})
            local result = (outdata or "") .. (errdata or "")
            if result and (result:find(" -k", 1, true) or result:find("--keep", 1, true)) then
                return true
            end
        end}
        _g.gzip_keep = keep or false
    end
    if keep then
        table.insert(argv, 1, "-k")
    end
    if not option.get("verbose") then
        table.insert(argv, "-q")
    end
    local compress = opt.compress
    if compress then
        if compress == "fastest" then
            table.insert(argv, "-1")
        elseif compress == "faster" then
            table.insert(argv, "-3")
        elseif compress == "better" then
            table.insert(argv, "-7")
        elseif compress == "best" then
            table.insert(argv, "-9")
        end
    end
    if opt.recurse then
        table.insert(argv, "-r")
    end
    if type(inputfiles) == "table" then
        for _, inputfile in ipairs(inputfiles) do
            table.insert(argv, inputfile)
        end
    else
        table.insert(argv, inputfiles)
    end

    -- archive it
    os.vrunv(gzip.program, argv, {stdout = archivefile, curdir = opt.curdir})
    return true
end

-- archive archivefile using tar
function _archive_using_tar(archivefile, inputfiles, extension, opt)

    -- find tar
    local tar = find_tool("tar")
    if not tar then
        return false
    end

    -- with compress? e.g. .tar.xz
    local compress = false
    local archivefile_tar
    if extension ~= ".tar" then
        compress = true
        archivefile_tar = path.join(path.directory(archivefile), path.basename(archivefile))
    end

    -- init argv
    local argv = {}
    local program = tar.program
    if is_host("windows") then
        local force_local = _g.force_local
        if force_local == nil then
            force_local = try {function ()
                local outdata, errdata = os.iorunv(program, {"--help"})
                local result = (outdata or "") .. (errdata or "")
                if result and result:find("--force-local", 1, true) then
                    return true
                end
            end}
            _g.force_local = force_local or false
        end
        if force_local then
            table.insert(argv, "--force-local")
        end
    end
    if compress and not is_host("windows") then
        table.insert(argv, "-a")
    end
    if option.get("verbose") then
        table.insert(argv, "-cvf")
    else
        table.insert(argv, "-cf")
    end
    table.insert(argv, archivefile_tar and archivefile_tar or archivefile)
    if opt.excludes then
        for _, exclude in ipairs(opt.excludes) do
            table.insert(argv, "--exclude=")
            table.insert(argv, exclude)
        end
    end
    if not opt.recurse then
        table.insert(argv, "-n")
    end
    local inputlistfile = os.tmpfile()
    if type(inputfiles) == "table" then
        local file = io.open(inputlistfile, "w")
        for _, inputfile in ipairs(inputfiles) do
            file:print(inputfile)
        end
        file:close()
        table.insert(argv, "-T")
        table.insert(argv, inputlistfile)
    else
        table.insert(argv, inputfiles)
    end

    -- archive it
    os.vrunv(program, argv, {curdir = opt.curdir})
    if inputlistfile then
        os.tryrm(inputlistfile)
    end
    if archivefile_tar and os.isfile(archivefile_tar) then
        try {
            function ()
                _archive_tarfile(archivefile, archivefile_tar, opt)
                os.tryrm(archivefile_tar)
                return true
            end,
            catch {
                function (errs)
                    os.tryrm(archivefile_tar)
                    raise(errs)
                end
            }
        }
    end
    return true
end

-- archive archive file using archivers
function _archive(archivefile, inputfiles, extension, archivers, opt)
    local errors
    for _, archive in ipairs(archivers) do
        local ok = try {
            function ()
                return archive(archivefile, inputfiles, extension, opt)
            end,
            catch {
                function (errs)
                    if errs then
                        errors = tostring(errs)
                    end
                end
            }
        }
        if ok then
            return true
        end
    end
    raise("cannot archive %s, %s!", path.filename(archivefile), errors or "no archiver(like zip, ...) found")
end

-- only archive tar file
function _archive_tarfile(archivefile, tarfile, opt)
    local archivers = {
        [".xz"]         = {_archive_using_xz, _archive_using_7z}
    ,   [".gz"]         = {_archive_using_gzip, _archive_using_7z}
    }
    local extension = opt.extension or path.extension(archivefile)
    return _archive(archivefile, tarfile, extension, archivers[extension], opt)
end

-- archive file
--
-- @param archivefile   the archive file. e.g. *.tar.gz, *.zip, *.7z, *.tar.bz2, ..
-- @param inputfiles    the input file or directory or list
-- @param options       the options, e.g.. {curdir = "/tmp", recurse = true, compress = "fastest|faster|default|better|best", excludes = {"*/dir/*", "dir/*"}}
--
function main(archivefile, inputfiles, opt)
    opt = opt or {}
    inputfiles = inputfiles or os.curdir()
    if opt.recurse == nil then
        opt.recurse = true
    end

    -- init archivers
    local archivers = {
        [".zip"]        = {_archive_using_zip, _archive_using_7z}
    ,   [".7z"]         = {_archive_using_7z}
    ,   [".xz"]         = {_archive_using_xz, _archive_using_7z}
    ,   [".gz"]         = {_archive_using_gzip, _archive_using_7z}
    ,   [".tar"]        = {_archive_using_tar, _archive_using_7z}
    ,   [".tar.gz"]     = {_archive_using_tar}
    ,   [".tar.xz"]     = {_archive_using_tar}
    ,   [".xmz"]        = {_archive_using_xmz}
    }

    -- get extension
    local extension = opt.extension or get_archive_extension(archivefile)

    -- ensure output directory
    local archivedir = path.directory(archivefile)
    if not os.isdir(archivedir) then
        os.mkdir(archivedir)
    end

    -- archive it
    return _archive(archivefile, inputfiles, extension, archivers[extension], opt)
end
