//===-- PlatformRemoteDarwinDevice.cpp ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "PlatformRemoteDarwinDevice.h"

#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"

using namespace lldb;
using namespace lldb_private;

PlatformRemoteDarwinDevice::SDKDirectoryInfo::SDKDirectoryInfo(
    const lldb_private::FileSpec &sdk_dir)
    : directory(sdk_dir), build(), user_cached(false) {
  llvm::StringRef dirname_str = sdk_dir.GetFilename().GetStringRef();
  llvm::StringRef build_str;
  std::tie(version, build_str) = ParseVersionBuildDir(dirname_str);
  build.SetString(build_str);
}

/// Default Constructor
PlatformRemoteDarwinDevice::PlatformRemoteDarwinDevice()
    : PlatformDarwinDevice(false) {} // This is a remote platform

/// Destructor.
///
/// The destructor is virtual since this class is designed to be
/// inherited from by the plug-in instance.
PlatformRemoteDarwinDevice::~PlatformRemoteDarwinDevice() = default;

void PlatformRemoteDarwinDevice::GetStatus(Stream &strm) {
  Platform::GetStatus(strm);
  const char *sdk_directory = GetDeviceSupportDirectoryForOSVersion();
  if (sdk_directory)
    strm.Printf("  SDK Path: \"%s\"\n", sdk_directory);
  else
    strm.PutCString("  SDK Path: error: unable to locate SDK\n");

  const uint32_t num_sdk_infos = m_sdk_directory_infos.size();
  for (uint32_t i = 0; i < num_sdk_infos; ++i) {
    const SDKDirectoryInfo &sdk_dir_info = m_sdk_directory_infos[i];
    strm.Printf(" SDK Roots: [%2u] \"%s\"\n", i,
                sdk_dir_info.directory.GetPath().c_str());
  }
}

Status PlatformRemoteDarwinDevice::ResolveExecutable(
    const ModuleSpec &ms, lldb::ModuleSP &exe_module_sp,
    const FileSpecList *module_search_paths_ptr) {
  Status error;
  // Nothing special to do here, just use the actual file and architecture

  ModuleSpec resolved_module_spec(ms);

  // Resolve any executable within a bundle on MacOSX
  // TODO: verify that this handles shallow bundles, if not then implement one
  // ourselves
  Host::ResolveExecutableInBundle(resolved_module_spec.GetFileSpec());

  if (FileSystem::Instance().Exists(resolved_module_spec.GetFileSpec())) {
    if (resolved_module_spec.GetArchitecture().IsValid() ||
        resolved_module_spec.GetUUID().IsValid()) {
      error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
                                          nullptr, nullptr, nullptr);

      if (exe_module_sp && exe_module_sp->GetObjectFile())
        return error;
      exe_module_sp.reset();
    }
    // No valid architecture was specified or the exact ARM slice wasn't found
    // so ask the platform for the architectures that we should be using (in
    // the correct order) and see if we can find a match that way
    StreamString arch_names;
    llvm::ListSeparator LS;
    ArchSpec process_host_arch;
    for (const ArchSpec &arch : GetSupportedArchitectures(process_host_arch)) {
      resolved_module_spec.GetArchitecture() = arch;
      error = ModuleList::GetSharedModule(resolved_module_spec, exe_module_sp,
                                          nullptr, nullptr, nullptr);
      // Did we find an executable using one of the
      if (error.Success()) {
        if (exe_module_sp && exe_module_sp->GetObjectFile())
          break;
        else
          error.SetErrorToGenericError();
      }

      arch_names << LS << arch.GetArchitectureName();
    }

    if (error.Fail() || !exe_module_sp) {
      if (FileSystem::Instance().Readable(resolved_module_spec.GetFileSpec())) {
        error.SetErrorStringWithFormatv(
            "'{0}' doesn't contain any '{1}' platform architectures: {2}",
            resolved_module_spec.GetFileSpec(), GetPluginName(),
            arch_names.GetData());
      } else {
        error.SetErrorStringWithFormat(
            "'%s' is not readable",
            resolved_module_spec.GetFileSpec().GetPath().c_str());
      }
    }
  } else {
    error.SetErrorStringWithFormat(
        "'%s' does not exist",
        resolved_module_spec.GetFileSpec().GetPath().c_str());
  }

  return error;
}

bool PlatformRemoteDarwinDevice::GetFileInSDK(const char *platform_file_path,
                                     uint32_t sdk_idx,
                                     lldb_private::FileSpec &local_file) {
  Log *log = GetLog(LLDBLog::Host);
  if (sdk_idx < m_sdk_directory_infos.size()) {
    std::string sdkroot_path =
        m_sdk_directory_infos[sdk_idx].directory.GetPath();
    local_file.Clear();

    if (!sdkroot_path.empty() && platform_file_path && platform_file_path[0]) {
      // We may need to interpose "/Symbols/" or "/Symbols.Internal/" between
      // the
      // SDK root directory and the file path.

      const char *paths_to_try[] = {"Symbols", "", "Symbols.Internal", nullptr};
      for (size_t i = 0; paths_to_try[i] != nullptr; i++) {
        local_file.SetFile(sdkroot_path, FileSpec::Style::native);
        if (paths_to_try[i][0] != '\0')
          local_file.AppendPathComponent(paths_to_try[i]);
        local_file.AppendPathComponent(platform_file_path);
        FileSystem::Instance().Resolve(local_file);
        if (FileSystem::Instance().Exists(local_file)) {
          LLDB_LOGF(log, "Found a copy of %s in the SDK dir %s/%s",
                    platform_file_path, sdkroot_path.c_str(), paths_to_try[i]);
          return true;
        }
        local_file.Clear();
      }
    }
  }
  return false;
}

Status PlatformRemoteDarwinDevice::GetSymbolFile(const FileSpec &platform_file,
                                                 const UUID *uuid_ptr,
                                                 FileSpec &local_file) {
  Log *log = GetLog(LLDBLog::Host);
  Status error;
  char platform_file_path[PATH_MAX];
  if (platform_file.GetPath(platform_file_path, sizeof(platform_file_path))) {
    const char *os_version_dir = GetDeviceSupportDirectoryForOSVersion();
    if (os_version_dir) {
      std::string resolved_path =
          (llvm::Twine(os_version_dir) + "/" + platform_file_path).str();

      local_file.SetFile(resolved_path, FileSpec::Style::native);
      FileSystem::Instance().Resolve(local_file);
      if (FileSystem::Instance().Exists(local_file)) {
        if (log) {
          LLDB_LOGF(log, "Found a copy of %s in the DeviceSupport dir %s",
                    platform_file_path, os_version_dir);
        }
        return error;
      }

      resolved_path = (llvm::Twine(os_version_dir) + "/Symbols.Internal/" +
                       platform_file_path)
                          .str();

      local_file.SetFile(resolved_path, FileSpec::Style::native);
      FileSystem::Instance().Resolve(local_file);
      if (FileSystem::Instance().Exists(local_file)) {
        LLDB_LOGF(
            log,
            "Found a copy of %s in the DeviceSupport dir %s/Symbols.Internal",
            platform_file_path, os_version_dir);
        return error;
      }
      resolved_path =
          (llvm::Twine(os_version_dir) + "/Symbols/" + platform_file_path)
              .str();

      local_file.SetFile(resolved_path, FileSpec::Style::native);
      FileSystem::Instance().Resolve(local_file);
      if (FileSystem::Instance().Exists(local_file)) {
        LLDB_LOGF(log, "Found a copy of %s in the DeviceSupport dir %s/Symbols",
                  platform_file_path, os_version_dir);
        return error;
      }
    }
    local_file = platform_file;
    if (FileSystem::Instance().Exists(local_file))
      return error;

    error.SetErrorStringWithFormatv(
        "unable to locate a platform file for '{0}' in platform '{1}'",
        platform_file_path, GetPluginName());
  } else {
    error.SetErrorString("invalid platform file argument");
  }
  return error;
}

Status PlatformRemoteDarwinDevice::GetSharedModule(
    const ModuleSpec &module_spec, Process *process, ModuleSP &module_sp,
    const FileSpecList *module_search_paths_ptr,
    llvm::SmallVectorImpl<ModuleSP> *old_modules, bool *did_create_ptr) {
  // For iOS, the SDK files are all cached locally on the host system. So first
  // we ask for the file in the cached SDK, then we attempt to get a shared
  // module for the right architecture with the right UUID.
  const FileSpec &platform_file = module_spec.GetFileSpec();
  Log *log = GetLog(LLDBLog::Host);

  Status error;
  char platform_file_path[PATH_MAX];

  if (platform_file.GetPath(platform_file_path, sizeof(platform_file_path))) {
    ModuleSpec platform_module_spec(module_spec);

    UpdateSDKDirectoryInfosIfNeeded();

    const uint32_t num_sdk_infos = m_sdk_directory_infos.size();

    // If we are connected we migth be able to correctly deduce the SDK
    // directory using the OS build.
    const uint32_t connected_sdk_idx = GetConnectedSDKIndex();
    if (connected_sdk_idx < num_sdk_infos) {
      LLDB_LOGV(log, "Searching for {0} in sdk path {1}", platform_file,
                m_sdk_directory_infos[connected_sdk_idx].directory);
      if (GetFileInSDK(platform_file_path, connected_sdk_idx,
                       platform_module_spec.GetFileSpec())) {
        module_sp.reset();
        error = ResolveExecutable(platform_module_spec, module_sp, nullptr);
        if (module_sp) {
          m_last_module_sdk_idx = connected_sdk_idx;
          error.Clear();
          return error;
        }
      }
    }

    // Try the last SDK index if it is set as most files from an SDK will tend
    // to be valid in that same SDK.
    if (m_last_module_sdk_idx < num_sdk_infos) {
      LLDB_LOGV(log, "Searching for {0} in sdk path {1}", platform_file,
                m_sdk_directory_infos[m_last_module_sdk_idx].directory);
      if (GetFileInSDK(platform_file_path, m_last_module_sdk_idx,
                       platform_module_spec.GetFileSpec())) {
        module_sp.reset();
        error = ResolveExecutable(platform_module_spec, module_sp, nullptr);
        if (module_sp) {
          error.Clear();
          return error;
        }
      }
    }

    // First try for an exact match of major, minor and update: If a particalar
    // SDK version was specified via --version or --build, look for a match on
    // disk.
    const SDKDirectoryInfo *current_sdk_info =
        GetSDKDirectoryForCurrentOSVersion();
    const uint32_t current_sdk_idx =
        GetSDKIndexBySDKDirectoryInfo(current_sdk_info);
    if (current_sdk_idx < num_sdk_infos &&
        current_sdk_idx != m_last_module_sdk_idx) {
      LLDB_LOGV(log, "Searching for {0} in sdk path {1}", platform_file,
                m_sdk_directory_infos[current_sdk_idx].directory);
      if (GetFileInSDK(platform_file_path, current_sdk_idx,
                       platform_module_spec.GetFileSpec())) {
        module_sp.reset();
        error = ResolveExecutable(platform_module_spec, module_sp, nullptr);
        if (module_sp) {
          m_last_module_sdk_idx = current_sdk_idx;
          error.Clear();
          return error;
        }
      }
    }

    // Second try all SDKs that were found.
    for (uint32_t sdk_idx = 0; sdk_idx < num_sdk_infos; ++sdk_idx) {
      if (m_last_module_sdk_idx == sdk_idx) {
        // Skip the last module SDK index if we already searched it above
        continue;
      }
      LLDB_LOGV(log, "Searching for {0} in sdk path {1}", platform_file,
                m_sdk_directory_infos[sdk_idx].directory);
      if (GetFileInSDK(platform_file_path, sdk_idx,
                       platform_module_spec.GetFileSpec())) {
        // printf ("sdk[%u]: '%s'\n", sdk_idx, local_file.GetPath().c_str());

        error = ResolveExecutable(platform_module_spec, module_sp, nullptr);
        if (module_sp) {
          // Remember the index of the last SDK that we found a file in in case
          // the wrong SDK was selected.
          m_last_module_sdk_idx = sdk_idx;
          error.Clear();
          return error;
        }
      }
    }
  }
  // Not the module we are looking for... Nothing to see here...
  module_sp.reset();

  // This may not be an SDK-related module.  Try whether we can bring in the
  // thing to our local cache.
  error = GetSharedModuleWithLocalCache(module_spec, module_sp,
                                        module_search_paths_ptr, old_modules,
                                        did_create_ptr);
  if (error.Success())
    return error;

  // See if the file is present in any of the module_search_paths_ptr
  // directories.
  if (!module_sp)
    error = PlatformDarwin::FindBundleBinaryInExecSearchPaths(
        module_spec, process, module_sp, module_search_paths_ptr, old_modules,
        did_create_ptr);

  if (error.Success())
    return error;

  const bool always_create = false;
  error = ModuleList::GetSharedModule(module_spec, module_sp,
                                      module_search_paths_ptr, old_modules,
                                      did_create_ptr, always_create);

  if (module_sp)
    module_sp->SetPlatformFileSpec(platform_file);

  return error;
}

uint32_t PlatformRemoteDarwinDevice::GetConnectedSDKIndex() {
  if (IsConnected()) {
    if (m_connected_module_sdk_idx == UINT32_MAX) {
      if (llvm::Optional<std::string> build = GetRemoteOSBuildString()) {
        const uint32_t num_sdk_infos = m_sdk_directory_infos.size();
        for (uint32_t i = 0; i < num_sdk_infos; ++i) {
          const SDKDirectoryInfo &sdk_dir_info = m_sdk_directory_infos[i];
          if (strstr(sdk_dir_info.directory.GetFilename().AsCString(""),
                     build->c_str())) {
            m_connected_module_sdk_idx = i;
          }
        }
      }
    }
  } else {
    m_connected_module_sdk_idx = UINT32_MAX;
  }
  return m_connected_module_sdk_idx;
}

uint32_t PlatformRemoteDarwinDevice::GetSDKIndexBySDKDirectoryInfo(
    const SDKDirectoryInfo *sdk_info) {
  if (sdk_info == nullptr) {
    return UINT32_MAX;
  }

  return sdk_info - &m_sdk_directory_infos[0];
}
