/*!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        rpath.c
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME "rpath_macho"
#define TB_TRACE_MODULE_DEBUG (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "prefix.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * implementation
 */
tb_bool_t xm_binutils_macho_rpath_list(tb_stream_ref_t istream, tb_hize_t base_offset, lua_State *lua) {
    tb_assert_and_check_return_val(istream && lua, tb_false);

    tb_bool_t ok = tb_false;
    do {
        // init Mach-O context
        xm_macho_context_t context;
        if (!xm_binutils_macho_context_init(istream, base_offset, &context)) break;

        // skip header to reach load commands
        tb_size_t header_size = context.is64 ? sizeof(xm_macho_header_64_t) : sizeof(xm_macho_header_32_t);
        if (!tb_stream_seek(istream, base_offset + header_size)) break;

        tb_size_t result_count = 0;

        // iterate load commands
        tb_uint32_t i = 0;
        for (i = 0; i < context.ncmds; i++) {
            xm_macho_load_command_t lc;
            tb_hize_t current_cmd_offset = tb_stream_offset(istream);

            if (!tb_stream_bread(istream, (tb_byte_t*)&lc, sizeof(lc))) break;
            xm_binutils_macho_swap_load_command(&lc, context.swap);

            // check for LC_RPATH
            if (lc.cmd == XM_MACHO_LC_RPATH) {

                xm_macho_rpath_command_t rc;
                if (tb_stream_seek(istream, current_cmd_offset)) {
                     if (tb_stream_bread(istream, (tb_byte_t*)&rc, sizeof(rc))) {
                         xm_binutils_macho_swap_rpath_command(&rc, context.swap);

                         tb_uint32_t name_offset = rc.path_offset;
                         if (name_offset < lc.cmdsize) {
                             // name is at current_cmd_offset + name_offset
                             tb_char_t rpath[TB_PATH_MAXN];
                             if (xm_binutils_read_string(istream, current_cmd_offset + name_offset, rpath, sizeof(rpath))) {
                                 if (tb_strlen(rpath) > 0) {
                                     lua_pushinteger(lua, result_count + 1);
                                     lua_pushstring(lua, rpath);
                                     lua_settable(lua, -3);
                                     result_count++;
                                 }
                             }
                         }
                     }
                }
            }

            // move to next command
            if (!tb_stream_seek(istream, current_cmd_offset + lc.cmdsize)) break;
        }
        if (i < context.ncmds) break;

        ok = tb_true;
    } while (0);

    return ok;
}

tb_bool_t xm_binutils_macho_rpath_clean(tb_stream_ref_t istream, tb_hize_t base_offset) {
    tb_assert_and_check_return_val(istream, tb_false);

    tb_bool_t ok = tb_false;
    tb_byte_t* buffer = tb_null;
    do {
        // init Mach-O context
        xm_macho_context_t context;
        if (!xm_binutils_macho_context_init(istream, base_offset, &context)) break;

        tb_size_t header_size = context.is64 ? sizeof(xm_macho_header_64_t) : sizeof(xm_macho_header_32_t);
        if (!tb_stream_seek(istream, base_offset + header_size)) break;

        tb_hize_t read_offset = base_offset + header_size;
        tb_hize_t write_offset = read_offset;
        tb_uint32_t new_ncmds = 0;
        tb_uint32_t new_sizeofcmds = 0;
        tb_bool_t found = tb_false;

        buffer = (tb_byte_t*)tb_malloc(64 * 1024); // 64KB should be enough for any load command
        if (!buffer) break;

        tb_uint32_t i = 0;
        for (i = 0; i < context.ncmds; i++) {
            xm_macho_load_command_t lc;
            if (!tb_stream_seek(istream, read_offset)) break;
            if (!tb_stream_bread(istream, (tb_byte_t*)&lc, sizeof(lc))) break;
            xm_binutils_macho_swap_load_command(&lc, context.swap);

            tb_bool_t remove = tb_false;
            if (lc.cmd == XM_MACHO_LC_RPATH) {
                remove = tb_true;
                found = tb_true;
            }

            if (!remove) {
                // copy command to write_offset
                if (read_offset != write_offset) {
                    if (lc.cmdsize > 64 * 1024) break;

                    if (!tb_stream_seek(istream, read_offset)) break;
                    if (!tb_stream_bread(istream, buffer, lc.cmdsize)) break;
                    if (!tb_stream_seek(istream, write_offset)) break;
                    if (!tb_stream_bwrit(istream, buffer, lc.cmdsize)) break;
                }
                write_offset += lc.cmdsize;
                new_ncmds++;
                new_sizeofcmds += lc.cmdsize;
            }

            read_offset += lc.cmdsize;
        }
        if (i < context.ncmds) break;

        if (found) {
            // zero out the remaining space
            if (read_offset > write_offset) {
                tb_hize_t diff = read_offset - write_offset;
                if (!tb_stream_seek(istream, write_offset)) break;

                tb_byte_t zero = 0;
                tb_bool_t write_ok = tb_true;
                for (tb_hize_t k = 0; k < diff; k++) {
                    if (!tb_stream_bwrit(istream, &zero, 1)) {
                        write_ok = tb_false;
                        break;
                    }
                }
                if (!write_ok) break;
            }

            // update header
            if (context.is64) {
                context.header.header64.ncmds = new_ncmds;
                context.header.header64.sizeofcmds = new_sizeofcmds;
                xm_binutils_macho_swap_header_64(&context.header.header64, context.swap);
                if (!tb_stream_seek(istream, base_offset)) break;
                if (!tb_stream_bwrit(istream, (tb_byte_t const*)&context.header.header64, sizeof(xm_macho_header_64_t))) break;
            } else {
                context.header.header32.ncmds = new_ncmds;
                context.header.header32.sizeofcmds = new_sizeofcmds;
                xm_binutils_macho_swap_header_32(&context.header.header32, context.swap);
                if (!tb_stream_seek(istream, base_offset)) break;
                if (!tb_stream_bwrit(istream, (tb_byte_t const*)&context.header.header32, sizeof(xm_macho_header_32_t))) break;
            }
        }

        ok = tb_true;
    } while (0);

    if (buffer) tb_free(buffer);
    return ok;
}
