/*
 * This file is part of the Score-P software (http://www.score-p.org)
 *
 * Copyright (c) 2009-2013,
 * RWTH Aachen University, Germany
 *
 * Copyright (c) 2009-2013,
 * Gesellschaft fuer numerische Simulation mbH Braunschweig, Germany
 *
 * Copyright (c) 2009-2014,
 * Technische Universitaet Dresden, Germany
 *
 * Copyright (c) 2009-2013,
 * University of Oregon, Eugene, USA
 *
 * Copyright (c) 2009-2013, 2015, 2017, 2023-2024,
 * Forschungszentrum Juelich GmbH, Germany
 *
 * Copyright (c) 2009-2013,
 * German Research School for Simulation Sciences GmbH, Juelich/Aachen, Germany
 *
 * Copyright (c) 2009-2013,
 * Technische Universitaet Muenchen, Germany
 *
 * This software may be modified and distributed under the terms of
 * a BSD-style license.  See the COPYING file in the package base
 * directory for details.
 *
 */

/**
 * @file
 *
 * Implements the representation and analysis of library dependencies
 */

#include <config.h>

#include <iostream>
#include <stdlib.h>
#include <cstring>

#include "otf2_config.hpp"

#include "otf2_config_data.hpp"

using namespace std;

/* ****************************************************************************
                                                         class otf2_config_data
******************************************************************************/

otf2_config_data::otf2_config_data( void )
{
}

otf2_config_data::~otf2_config_data()
{
}

string
otf2_config_data::GetLibraries( const deque<string>& inputLibs )
{
    assert_lib_objects_exist( inputLibs );
    deque<string> libs;

    /* Traversing backwards will add the libs in front of the lib's needs_libs
       and dependencies. */
    for ( auto i = inputLibs.rbegin(); i != inputLibs.rend(); i++ )
    {
        LibraryData obj = m_library_objects[ *i ];
        libs.push_front( "-l" + obj.m_lib_name.substr( 3 ) );
        libs.insert( libs.end(),
                     obj.m_needs_libs.begin(),
                     obj.m_needs_libs.end() );
    }
    libs = remove_double_entries( libs );

    return deque_to_string( libs, "", " ", "" );
}

string
otf2_config_data::GetLDFlags( const deque<string>& libs )
{
    return deque_to_string( remove_linker_search_paths(
                                get_libdirs( libs ) ),
                            "", " -L", "" );
}

string
otf2_config_data::GetLTLDFlags( const deque<string>& libs )
{
    deque<string> libdirs( remove_linker_search_paths( get_libdirs( libs ) ) );
    deque<string> rpaths( remove_loader_search_paths( libdirs ) );
    deque<string> ld_run_path = get_ld_run_path();
    rpaths.insert( rpaths.end(), ld_run_path.begin(), ld_run_path.end() );
    return deque_to_string( libdirs, "", " -L", "" ) +
           deque_to_string( rpaths, "", " -R", "" );
}

string
otf2_config_data::GetRpathFlags( const deque<string>& libs )
{
    deque<string> libdirs     = remove_loader_search_paths( get_libdirs( libs ) );
    deque<string> ld_run_path = get_ld_run_path();
    libdirs.insert( libdirs.end(), ld_run_path.begin(), ld_run_path.end() );
    return deque_to_string( libdirs,
                            m_rpath_head,
                            m_rpath_delimiter,
                            m_rpath_tail );
}

void
otf2_config_data::prepare_rpath_flags( void )
{
    // Replace $wl by LIBDIR_FLAG_WL and erase everything from
    // $libdir on in order to create m_rpath_head and
    // m_rpath_delimiter. This will work for most and for the relevant
    // (as we know in 2012-07) values of LIBDIR_FLAG_CC. Some possible
    // values are (see also ticket 530,
    // https://silc.zih.tu-dresden.de/trac-silc/ticket/530):
    // '+b $libdir'
    // '-L$libdir'
    // '-R$libdir'
    // '-rpath $libdir'
    // '$wl-blibpath:$libdir:'"$aix_libpath"
    // '$wl+b $wl$libdir'
    // '$wl-R,$libdir'
    // '$wl-R $libdir:/usr/lib:/lib'
    // '$wl-rpath,$libdir'
    // '$wl--rpath $wl$libdir'
    // '$wl-rpath $wl$libdir'
    // '$wl-R $wl$libdir'
    // For a complete list, check the currently used libtool.m4.

    string        rpath_flag = m_rpath_flag_cc;
    const string& wl         = m_wl_flag;
    const string& aix_path   = m_aix_libpath;
    size_t        index      = 0;
    while ( true )
    {
        index = rpath_flag.find( "$wl", index );
        if ( index == string::npos )
        {
            break;
        }
        rpath_flag.replace( index, strlen( "$wl" ), wl );
        ++index;
    }
    index = rpath_flag.find( "$libdir", 0 );
    if ( index != string::npos )
    {
        rpath_flag.erase( index );
    }

    bool is_aix = ( !aix_path.empty() );
    if ( is_aix )
    {
        m_rpath_head      = " " + rpath_flag;
        m_rpath_delimiter = ":";
        m_rpath_tail      = ":" + aix_path;
    }
    else
    {
        m_rpath_head      = "";
        m_rpath_delimiter = " " + rpath_flag;
        m_rpath_tail      = "";
    }
}

deque<string>
otf2_config_data::remove_linker_search_paths( const deque<string>& input )
{
    return remove_paths( input,
                         convert_to_string_list( m_sys_lib_search_path, " " ) );
}

deque<string>
otf2_config_data::remove_loader_search_paths( const deque<string>& input )
{
    return remove_paths( input,
                         convert_to_string_list( m_sys_lib_dlsearch_path, " " ) );
}

deque<string>
otf2_config_data::remove_paths( const deque<string>& input,
                                const deque<string>& remove )
{
    deque<string> result_paths;

    for ( deque<string>::const_iterator input_path = input.begin();
          input_path != input.end(); input_path++ )
    {
        bool is_remove_path = false;
        for ( deque<string>::const_iterator remove_path = remove.begin();
              remove_path != remove.end(); remove_path++ )
        {
            if ( *input_path == *remove_path )
            {
                is_remove_path = true;
            }
        }
        if ( !is_remove_path )
        {
            result_paths.push_back( *input_path );
        }
    }
    return result_paths;
}

deque<string>
otf2_config_data::get_ld_run_path()
{
    deque<string> ld_run_path;

    const char* ld_run_path_env = getenv( "LD_RUN_PATH" ); // abs_dir[:abs_dir]
    if ( ld_run_path_env == NULL || *ld_run_path_env == '\0' )
    {
        return ld_run_path;
    }

    deque<string> ld_run_path_tmp = convert_to_string_list( ld_run_path_env, ":" );
    remove_double_entries( ld_run_path_tmp );
    ld_run_path_tmp = remove_loader_search_paths( ld_run_path_tmp );
    /* Omit empty entries, entries that are not absolute paths, and
     * those that contain whitespace. */
    for ( const auto& i : ld_run_path_tmp )
    {
        if ( i.empty()
             || i[ 0 ] != '/'
             || i.find_first_of( "\t\n " ) != string::npos )
        {
            warn( "omitting implausible LD_RUN_PATH element '" << i << "'." );
            continue;
        }
        ld_run_path.push_back( i );
    }
    return ld_run_path;
}

void
otf2_config_data::assert_lib_objects_exist( const deque<string>& libs ) const
{
    for ( const auto& lib : libs )
    {
        if ( 0 == m_library_objects.count( lib ) )
        {
            die( "cannot query \"" << lib << "\"" );
        }
    }
}

deque<string>
otf2_config_data::get_libdirs( const deque<string>& libs )
{
    assert_lib_objects_exist( libs );
    deque<string> dirs;

    for ( const auto& i : libs )
    {
        LibraryData obj = m_library_objects[ i ];
        if ( !obj.m_install_dir.empty() )
        {
            dirs.push_back( obj.m_install_dir );
        }
        dirs.insert( dirs.end(),
                     obj.m_needs_libdirs.begin(),
                     obj.m_needs_libdirs.end() );
    }
    return remove_double_entries( dirs );
}

string
otf2_config_data::prepare_string( const string& str )
{
    string       result( str );
    const string pattern1 = " ";
    const string replace1 = ",";
    const string pattern2 = m_wl_flag;
    const string replace2 = "";

    remove_multiple_whitespaces( result );
    /* Replace all white-spaces by comma */
    replace_all( result, pattern1, replace1 );
    /* Replace flag for passing arguments to linker through compiler
     * (flags not needed because we use '-Xlinker' to specify linker
     * flags when using CUDA compiler */
    if ( pattern2.length() != 0 )
    {
        replace_all( result, pattern2, replace2 );
    }

    return result;
}
