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

-- define module
local binutils = binutils or {}

-- load modules
local os = require("base/os")

-- save original interfaces
binutils._bin2c = binutils._bin2c or binutils.bin2c
binutils._bin2coff = binutils._bin2coff or binutils.bin2coff
binutils._bin2macho = binutils._bin2macho or binutils.bin2macho
binutils._bin2elf = binutils._bin2elf or binutils.bin2elf
binutils._readsyms = binutils._readsyms or binutils.readsyms
binutils._deplibs = binutils._deplibs or binutils.deplibs
binutils._rpath_list = binutils._rpath_list or binutils.rpath_list
binutils._rpath_clean = binutils._rpath_clean or binutils.rpath_clean
binutils._extractlib = binutils._extractlib or binutils.extractlib
binutils._format = binutils._format or binutils.format

-- generate c/c++ header with binary data from the binary file
--
-- @param binaryfile    the input binary file path
-- @param outputfile    the output header file path
-- @param opt           the options, e.g. {nozeroend = true}
--
function binutils.bin2c(binaryfile, outputfile, opt)
    opt = opt or {}
    if binutils._bin2c then
        return binutils._bin2c(binaryfile, outputfile, opt.linewidth or 32, opt.nozeroend or false)
    else
        -- fallback to old implementation if C implementation not available
        return nil, "C implementation not available"
    end
end

-- generate object file from the binary file
-- @param binaryfile  the binary file path
-- @param outputfile  the output object file path
-- @param opt         the options
--                      - format: the object file format (coff, elf, macho), required
--                      - symbol_prefix: the symbol prefix (default: _binary_)
--                      - arch: the target architecture (default: x86_64)
--                      - plat: the target platform (default: macosx, only for macho)
--                      - basename: the base name for symbols
--                      - target_minver: the target minimum version (only for macho)
--                      - xcode_sdkver: the Xcode SDK version (only for macho)
--                      - zeroend: append null terminator (default: false)
--                      - refobj: a reference elf object whose class/endianness/machine/e_flags
--                                are mirrored into the output (default: derived from arch)
function binutils.bin2obj(binaryfile, outputfile, opt)
    opt = opt or {}
    local format = opt.format
    if not format then
        -- auto-detect format based on host platform
        local host = os.host()
        if host == "windows" or host == "mingw" or host == "msys" or host == "cygwin" then
            format = "coff"
        elseif host == "macosx" or host == "iphoneos" or host == "watchos" or host == "appletvos" then
            format = "macho"
        else
            format = "elf"
        end
    end
    format = format:lower()

    if format == "coff" then
        if not binutils._bin2coff then
            return nil, "binutils._bin2coff not available (C implementation not compiled)"
        end
        return binutils._bin2coff(binaryfile, outputfile, opt.symbol_prefix or "_binary_", opt.arch or "x86_64", opt.basename, opt.zeroend or false)
    elseif format == "macho" then
        if not binutils._bin2macho then
            return nil, "binutils._bin2macho not available (C implementation not compiled)"
        end
        return binutils._bin2macho(binaryfile, outputfile, opt.symbol_prefix or "_binary_", opt.plat or "macosx", opt.arch or "x86_64", opt.basename, opt.target_minver, opt.xcode_sdkver, opt.zeroend or false)
    elseif format == "elf" then
        if not binutils._bin2elf then
            return nil, "binutils._bin2elf not available (C implementation not compiled)"
        end
        return binutils._bin2elf(binaryfile, outputfile, opt.symbol_prefix or "_binary_", opt.arch or "x86_64", opt.basename, opt.zeroend or false, opt.refobj)
    else
        return nil, string.format("unsupported format '%s' (supported: coff, elf, macho)", format)
    end
end

-- get binary file format (auto-detect: coff, elf, macho, ar, pe, unknown)
--
-- @param binaryfile    the binary file path
-- @return              the format string
--
function binutils.format(binaryfile)
    if binutils._format then
        return binutils._format(binaryfile)
    else
        return nil, "C implementation not available"
    end
end


-- read symbols from object file (auto-detect: COFF, ELF, or Mach-O)
--
-- @param binaryfile    the binary file path
-- @return              the symbols table, or nil and error info
--
function binutils.readsyms(binaryfile)
    if binutils._readsyms then
        return binutils._readsyms(binaryfile)
    else
        return nil, "C implementation not available"
    end
end

-- get dependent libraries from binary file (auto-detect: COFF, ELF, or Mach-O)
--
-- @param binaryfile    the binary file path
-- @return              the dependent libraries table, or nil and error info
--
function binutils.deplibs(binaryfile)
    if binutils._deplibs then
        return binutils._deplibs(binaryfile)
    else
        return nil, "C implementation not available"
    end
end

-- get rpath list from binary file (auto-detect: ELF or Mach-O)
--
-- @param binaryfile    the binary file path
-- @return              the rpath list table, or nil and error info
--
function binutils.rpath_list(binaryfile)
    if binutils._rpath_list then
        return binutils._rpath_list(binaryfile)
    else
        return nil, "internal rpath_list not supported!"
    end
end

-- insert rpath to binary file (auto-detect format: ELF or Mach-O)

-- clean rpaths from binary file (auto-detect: ELF or Mach-O)
--
-- @param binaryfile    the binary file path
-- @return              true on success, or false and error info
--
function binutils.rpath_clean(binaryfile)
    if binutils._rpath_clean then
        return binutils._rpath_clean(binaryfile)
    else
        return false, "internal rpath_clean not supported!"
    end
end

-- extract static library to directory
-- Supports AR format (.a) and MSVC lib format (.lib)
-- @param libraryfile the static library file path (.a or .lib)
-- @param outputdir    the output directory to extract object files
-- @param opt          the options (optional)
--                       - plain: extract all object files to the same directory (default: true)
-- @return             true on success, false and error message on failure
function binutils.extractlib(libraryfile, outputdir, opt)
    if binutils._extractlib then
        local ok, errors = binutils._extractlib(libraryfile, outputdir, opt and opt.plain)
        if ok then
            return true
        else
            return false, errors or "unknown error"
        end
    else
        return false, "C implementation not available"
    end
end

-- return module
return binutils
