// Copyright 2011-2012 Renato Tegon Forti
// Copyright 2015-2019 Antony Polukhin
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// For more information, see http://www.boost.org

#include "../example/b2_workarounds.hpp"
#include <boost/dll.hpp>
#include <boost/core/lightweight_test.hpp>
// Unit Tests

namespace boost { namespace dll { namespace fs {

#ifdef BOOST_DLL_USE_STD_FS
using std::filesystem::remove;
using std::filesystem::copy;
#else
using boost::filesystem::remove;
using boost::filesystem::copy;
#endif

}}}

inline boost::dll::fs::path drop_version(const boost::dll::fs::path& lhs) {
    boost::dll::fs::path ext = lhs.filename().extension();
    if (ext.native().size() > 1 && std::isdigit(ext.string()[1])) {
        ext = lhs;
        ext.replace_extension().replace_extension().replace_extension();
        return ext;
    }

    return lhs;
}

inline bool lib_path_equal(const boost::dll::fs::path& lhs, const boost::dll::fs::path& rhs) {
    const bool res = (drop_version(lhs).filename() == drop_version(rhs).filename());
    if (!res) {
        std::cerr << "lhs != rhs: " << lhs << " != " << rhs << '\n';
    }
    return res;
}

struct fs_copy_guard {
    const boost::dll::fs::path actual_path_;
    const bool same_;

    inline explicit fs_copy_guard(const boost::dll::fs::path& shared_library_path)
        : actual_path_( drop_version(shared_library_path) )
        , same_(actual_path_ == shared_library_path)
    {
        if (!same_) {
            boost::dll::fs::error_code ignore;
            boost::dll::fs::remove(actual_path_, ignore);
            boost::dll::fs::copy(shared_library_path, actual_path_, ignore);
        }
    }

    inline ~fs_copy_guard() {
        if (!same_) {
            boost::dll::fs::error_code ignore;
            boost::dll::fs::remove(actual_path_, ignore);
        }
    }
};

// Disgusting workarounds for b2 on Windows platform
inline boost::dll::fs::path do_find_correct_libs_path(int argc, char* argv[], const char* lib_name) {
    boost::dll::fs::path ret;

    for (int i = 1; i < argc; ++i) {
        ret = argv[i];
        if (ret.string().find(lib_name) != std::string::npos && b2_workarounds::is_shared_library(ret)) {
            return ret;
        }
    }

    return lib_name;
}

int main(int argc, char* argv[])
{
    using namespace boost::dll;

    BOOST_TEST(argc >= 3);
    boost::dll::fs::path shared_library_path = do_find_correct_libs_path(argc, argv, "test_library");
    std::cout << "Library: " << shared_library_path;

    {
        shared_library sl(shared_library_path);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));

        shared_library sl2;
        BOOST_TEST(!sl2.is_loaded());
        BOOST_TEST(!sl2);

        swap(sl, sl2);
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);

        sl.assign(sl2);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);
        BOOST_TEST(sl2.location() == sl.location());

        sl.assign(sl2);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);
        BOOST_TEST(sl2.location() == sl.location());

        sl2.assign(sl);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);
        BOOST_TEST(sl2.location() == sl.location());

        // Assigning an empty shared library
        sl2.assign(shared_library());
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!sl2.is_loaded());
        BOOST_TEST(!sl2);
        boost::dll::fs::error_code ec;
        BOOST_TEST(sl2.location(ec) != sl.location());
        BOOST_TEST(ec);
   }

   {
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        BOOST_TEST(lib_path_equal(sl.location(ec), shared_library_path));
        BOOST_TEST(!ec);

        // Checking self assignment #1
        sl.assign(sl);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        BOOST_TEST(lib_path_equal(sl.location(ec), shared_library_path));

        // Checking self assignment #2
        sl.assign(sl, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        BOOST_TEST(lib_path_equal(sl.location(ec), shared_library_path));
   }

   {
        shared_library sl;
        BOOST_TEST(!sl.is_loaded());

        sl.assign(sl);
        BOOST_TEST(!sl);

        shared_library sl2(sl);
        BOOST_TEST(!sl);
        BOOST_TEST(!sl2);

        sl2.assign(sl);
        BOOST_TEST(!sl);
        BOOST_TEST(!sl2);
   }

   {
        shared_library sl;
        sl.load(shared_library_path);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl;
        boost::dll::fs::error_code ec;
        sl.load(shared_library_path, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl(shared_library_path, load_mode::load_with_altered_search_path );
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path, load_mode::load_with_altered_search_path, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        BOOST_TEST(lib_path_equal(sl.location(ec), shared_library_path));
        BOOST_TEST(!ec);
   }

   {
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path, load_mode::search_system_folders, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        BOOST_TEST(lib_path_equal(sl.location(ec), shared_library_path));
        BOOST_TEST(!ec);
   }

   {
        try {
#if BOOST_OS_WINDOWS
            boost::dll::shared_library("winmm.dll");
#elif BOOST_OS_LINUX
            boost::dll::shared_library("libdl.so");
#endif
            BOOST_TEST(false);
        } catch (...) {}
   }

   {
        try {
#if BOOST_OS_WINDOWS
            boost::dll::shared_library("winmm", load_mode::search_system_folders | load_mode::append_decorations);
#elif BOOST_OS_LINUX
            boost::dll::shared_library("dl", boost::dll::load_mode::search_system_folders | load_mode::append_decorations);
#endif
        } catch (...) {
            BOOST_TEST(false);
        }
   }

   {
        shared_library sl;
        sl.load(shared_library_path, load_mode::load_with_altered_search_path);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl;
        boost::dll::fs::error_code ec;
        sl.load(shared_library_path, load_mode::load_with_altered_search_path, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl(shared_library_path, load_mode::rtld_lazy | load_mode::rtld_global);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl(shared_library_path, load_mode::rtld_local);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl(shared_library_path, load_mode::rtld_now);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        fs_copy_guard guard(shared_library_path);

        boost::dll::fs::path platform_independent_path = guard.actual_path_;
        platform_independent_path.replace_extension();
        if (platform_independent_path.filename().wstring().find(L"lib") == 0) {
            platform_independent_path
                = platform_independent_path.parent_path() / platform_independent_path.filename().wstring().substr(3);
        }
        std::cerr << "platform_independent_path: " << platform_independent_path << '\n';

        shared_library sl(platform_independent_path, load_mode::append_decorations);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));

        sl.unload();
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);
   }

   {
        shared_library sl(shared_library_path, load_mode::rtld_now | load_mode::rtld_global | load_mode::load_with_altered_search_path);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
   }

   {
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path, load_mode::rtld_lazy | load_mode::rtld_global, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }

   {
        shared_library sl;
        sl.load(shared_library_path, load_mode::rtld_lazy | load_mode::rtld_global);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
   }


   {    // Non-default flags with assignment
        shared_library sl(shared_library_path,
            load_mode::rtld_now | load_mode::rtld_global | load_mode::load_with_altered_search_path

// `load_mode::rtld_deepbind` is incompatible with sanitizers:
//    You are trying to dlopen a libtest_library.so shared library with RTLD_DEEPBIND flag which is incompatibe with sanitizer runtime 
//    (see https://github.com/google/sanitizers/issues/611 for details).
#ifndef BOOST_TRAVISCI_BUILD
            | load_mode::rtld_deepbind
#endif

        );
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);

        boost::dll::fs::error_code ec;
        shared_library sl2(sl, ec);
        BOOST_TEST(!ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);
        BOOST_TEST(sl2.location() == sl.location());

        shared_library sl3(sl);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl3.is_loaded());
        BOOST_TEST(sl3);

        shared_library sl4;
        sl4.assign(sl, ec);
        BOOST_TEST(!ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl4.is_loaded());
        BOOST_TEST(sl4);
   }

   {    // Non-default flags with assignment and error_code
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path, load_mode::rtld_lazy | load_mode::rtld_global, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));

        shared_library sl2(sl, ec);
        BOOST_TEST(!ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST(sl2);
        BOOST_TEST(sl2.location() == sl.location());

        shared_library sl3(sl);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(sl3.is_loaded());
        BOOST_TEST(sl3);
        BOOST_TEST(sl3.location() == sl.location());
   }

   {  // self_load
        shared_library sl(program_location());
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        std::cout << "\nProgram location: " << program_location();
        std::cout << "\nLibrary location: " << sl.location();
        BOOST_TEST( boost::dll::fs::equivalent(sl.location(), program_location()) );

        boost::dll::fs::error_code ec;
        shared_library sl2(program_location());
        BOOST_TEST(sl2.is_loaded());
        BOOST_TEST( boost::dll::fs::equivalent(sl2.location(), program_location()) );
        BOOST_TEST(sl2);
        BOOST_TEST(!ec);

        BOOST_TEST(sl == sl2);
        BOOST_TEST(!(sl < sl2 || sl2 <sl));
        BOOST_TEST(!(sl != sl2));

        sl.load(shared_library_path);
        BOOST_TEST(sl != sl2);
        BOOST_TEST(sl < sl2 || sl2 <sl);
        BOOST_TEST(!(sl == sl2));

        sl.unload();
        BOOST_TEST(!sl);
        BOOST_TEST(sl != sl2);
        BOOST_TEST(sl < sl2 || sl2 <sl);
        BOOST_TEST(!(sl == sl2));
    
        sl2.unload();
        BOOST_TEST(!sl2);
        BOOST_TEST(sl == sl2);
        BOOST_TEST(!(sl < sl2 || sl2 <sl));
        BOOST_TEST(!(sl != sl2));

        // assigning self
        sl.load(program_location());
        sl2 = sl;
        BOOST_TEST(sl == sl2);
        BOOST_TEST(sl.location() == sl2.location());
   }

   {
        shared_library sl;
        boost::dll::fs::error_code ec;
        sl.load(shared_library_path, load_mode::rtld_lazy | load_mode::rtld_global, ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));

        sl.load(program_location());
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);

        sl.load(program_location());
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
   }
   
   {  // self_load
        shared_library sl;
        boost::dll::fs::error_code ec;
        sl.load(program_location());
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(!ec);
   }

   {  // unload
        shared_library sl(shared_library_path);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);
        BOOST_TEST(lib_path_equal(sl.location(), shared_library_path));
        sl.unload();
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);
   }


   {  // error_code load calls test
        boost::dll::fs::error_code ec;
        shared_library sl(shared_library_path / "dir_that_does_not_exist", ec);
        BOOST_TEST(ec);
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);

        boost::dll::fs::path bad_path(shared_library_path);
        bad_path += ".1.1.1.1.1.1";
        sl.load(bad_path, ec);
        BOOST_TEST(ec);
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);

        sl.load(shared_library_path, ec);
        BOOST_TEST(!ec);
        BOOST_TEST(sl.is_loaded());
        BOOST_TEST(sl);

        shared_library sl2(bad_path, ec);
        BOOST_TEST(ec);
        BOOST_TEST(!sl2.is_loaded());
        BOOST_TEST(!sl2);

        shared_library sl3(shared_library_path, ec);
        BOOST_TEST(!ec);
        BOOST_TEST(sl3.is_loaded());
        BOOST_TEST(sl3);

        sl.load("", ec);
        BOOST_TEST(ec);
        BOOST_TEST(!sl.is_loaded());
        BOOST_TEST(!sl);
   }


    shared_library_path = do_find_correct_libs_path(argc, argv, "library1");
    fs_copy_guard guard(shared_library_path);
    shared_library starts_with_lib(
        boost::dll::fs::path(guard.actual_path_).replace_extension(),
        load_mode::append_decorations
    );

    starts_with_lib.load(
        boost::dll::fs::path(guard.actual_path_).replace_extension(),
        load_mode::append_decorations
    );

   return boost::report_errors();
}
