// Copyright (C) 2020 Samsung Electronics Co., Ltd.
// See the LICENSE file in the project root for more information.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/// \file interop_unix.h  This file contains unix-specific functions for Interop class defined in interop.h

#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <dirent.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string>
#include <set>

#include "managed/interop.h"
#include "utils/filesystem.h"

namespace netcoredbg
{

// This function searches *.dll files in specified directory and adds full paths to files
// to colon-separated list `tpaList'.
template <>
void InteropTraits<UnixPlatformTag>::AddFilesFromDirectoryToTpaList(const std::string &directory, std::string& tpaList)
{
    const char * const tpaExtensions[] = {
                ".ni.dll",      // Probe for .ni.dll first so that it's preferred if ni and il coexist in the same dir
                ".dll",
                ".ni.exe",
                ".exe",
                };

    DIR* dir = opendir(directory.c_str());
    if (dir == nullptr)
        return;

    std::set<std::string> addedAssemblies;

    // Walk the directory for each extension separately so that we first get files with .ni.dll extension,
    // then files with .dll extension, etc.
    for (size_t extIndex = 0; extIndex < sizeof(tpaExtensions) / sizeof(tpaExtensions[0]); extIndex++)
    {
        const char* ext = tpaExtensions[extIndex];
        int extLength = strlen(ext);

        struct dirent* entry;

        // For all entries in the directory
        while ((entry = readdir(dir)) != nullptr)
        {
            // We are interested in files only
            switch (entry->d_type)
            {
            case DT_REG:
                break;

            // Handle symlinks and file systems that do not support d_type
            case DT_LNK:
            case DT_UNKNOWN:
            {
                std::string fullFilename;

                fullFilename.append(directory);
                fullFilename += FileSystem::PathSeparator;
                fullFilename.append(entry->d_name);

                struct stat sb;
                if (stat(fullFilename.c_str(), &sb) == -1)
                    continue;

                if (!S_ISREG(sb.st_mode))
                    continue;
            }
            break;

            default:
                continue;
            }

            std::string filename(entry->d_name);

            // Check if the extension matches the one we are looking for
            int extPos = filename.length() - extLength;
            if ((extPos <= 0) || (filename.compare(extPos, extLength, ext) != 0))
            {
                continue;
            }

            std::string filenameWithoutExt(filename.substr(0, extPos));

            // Make sure if we have an assembly with multiple extensions present,
            // we insert only one version of it.
            if (addedAssemblies.find(filenameWithoutExt) == addedAssemblies.end())
            {
                addedAssemblies.insert(filenameWithoutExt);

                tpaList.append(directory);
                tpaList += FileSystem::PathSeparator;
                tpaList.append(filename);
                tpaList.append(":");
            }
        }

        // Rewind the directory stream to be able to iterate over it for the next extension
        rewinddir(dir);
    }

    closedir(dir);
}

// This function unsets `CORECLR_ENABLE_PROFILING' environment variable.
template <>
void InteropTraits<UnixPlatformTag>::UnsetCoreCLREnv()
{
    unsetenv("CORECLR_ENABLE_PROFILING");
}

// Returns the length of a BSTR.
template <>
UINT InteropTraits<UnixPlatformTag>::SysStringLen(BSTR bstrString)
{
    if (bstrString == NULL)
        return 0;
    return (unsigned int)((((DWORD FAR*)bstrString)[-1]) / sizeof(OLECHAR));
}

}  // ::netcoredbg
#endif  // __unix__
