//===-- PlatformMacOSX.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 "PlatformMacOSX.h"
#include "PlatformRemoteMacOSX.h"
#include "PlatformRemoteiOS.h"
#if defined(__APPLE__)
#include "PlatformAppleSimulator.h"
#include "PlatformDarwinKernel.h"
#include "PlatformRemoteAppleBridge.h"
#include "PlatformRemoteAppleTV.h"
#include "PlatformRemoteAppleWatch.h"
#endif
#include "lldb/Breakpoint/BreakpointLocation.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleList.h"
#include "lldb/Core/ModuleSpec.h"
#include "lldb/Core/PluginManager.h"
#include "lldb/Host/Config.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/DataBufferHeap.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StreamString.h"

#include <sstream>

using namespace lldb;
using namespace lldb_private;

LLDB_PLUGIN_DEFINE(PlatformMacOSX)

static uint32_t g_initialize_count = 0;

void PlatformMacOSX::Initialize() {
  PlatformDarwin::Initialize();
  PlatformRemoteiOS::Initialize();
  PlatformRemoteMacOSX::Initialize();
#if defined(__APPLE__)
  PlatformAppleSimulator::Initialize();
  PlatformDarwinKernel::Initialize();
  PlatformRemoteAppleTV::Initialize();
  PlatformRemoteAppleWatch::Initialize();
  PlatformRemoteAppleBridge::Initialize();
#endif

  if (g_initialize_count++ == 0) {
#if defined(__APPLE__)
    PlatformSP default_platform_sp(new PlatformMacOSX());
    default_platform_sp->SetSystemArchitecture(HostInfo::GetArchitecture());
    Platform::SetHostPlatform(default_platform_sp);
#endif
    PluginManager::RegisterPlugin(PlatformMacOSX::GetPluginNameStatic(),
                                  PlatformMacOSX::GetDescriptionStatic(),
                                  PlatformMacOSX::CreateInstance);
  }
}

void PlatformMacOSX::Terminate() {
  if (g_initialize_count > 0) {
    if (--g_initialize_count == 0) {
      PluginManager::UnregisterPlugin(PlatformMacOSX::CreateInstance);
    }
  }

#if defined(__APPLE__)
  PlatformRemoteAppleBridge::Terminate();
  PlatformRemoteAppleWatch::Terminate();
  PlatformRemoteAppleTV::Terminate();
  PlatformDarwinKernel::Terminate();
  PlatformAppleSimulator::Terminate();
#endif
  PlatformRemoteMacOSX::Initialize();
  PlatformRemoteiOS::Terminate();
  PlatformDarwin::Terminate();
}

llvm::StringRef PlatformMacOSX::GetDescriptionStatic() {
  return "Local Mac OS X user platform plug-in.";
}

PlatformSP PlatformMacOSX::CreateInstance(bool force, const ArchSpec *arch) {
  // The only time we create an instance is when we are creating a remote
  // macosx platform which is handled by PlatformRemoteMacOSX.
  return PlatformSP();
}

/// Default Constructor
PlatformMacOSX::PlatformMacOSX() : PlatformDarwinDevice(true) {}

ConstString PlatformMacOSX::GetSDKDirectory(lldb_private::Target &target) {
  ModuleSP exe_module_sp(target.GetExecutableModule());
  if (!exe_module_sp)
    return {};

  ObjectFile *objfile = exe_module_sp->GetObjectFile();
  if (!objfile)
    return {};

  llvm::VersionTuple version = objfile->GetSDKVersion();
  if (version.empty())
    return {};

  // First try to find an SDK that matches the given SDK version.
  if (FileSpec fspec = HostInfo::GetXcodeContentsDirectory()) {
    StreamString sdk_path;
    sdk_path.Printf("%s/Developer/Platforms/MacOSX.platform/Developer/"
                    "SDKs/MacOSX%u.%u.sdk",
                    fspec.GetPath().c_str(), version.getMajor(),
                    *version.getMinor());
    if (FileSystem::Instance().Exists(fspec))
      return ConstString(sdk_path.GetString());
  }

  // Use the default SDK as a fallback.
  auto sdk_path_or_err = HostInfo::GetXcodeSDKPath(XcodeSDK::GetAnyMacOS());
  if (!sdk_path_or_err) {
    Debugger::ReportError("Error while searching for Xcode SDK: " +
                          toString(sdk_path_or_err.takeError()));
    return {};
  }

  FileSpec fspec(*sdk_path_or_err);
  if (fspec) {
    if (FileSystem::Instance().Exists(fspec))
      return ConstString(fspec.GetPath());
  }

  return {};
}

std::vector<ArchSpec>
PlatformMacOSX::GetSupportedArchitectures(const ArchSpec &process_host_arch) {
  std::vector<ArchSpec> result;
#if defined(__arm__) || defined(__arm64__) || defined(__aarch64__)
  // When cmdline lldb is run on iOS, watchOS, etc, it is still
  // using "PlatformMacOSX".
  llvm::Triple::OSType host_os = GetHostOSType();
  ARMGetSupportedArchitectures(result, host_os);

  if (host_os == llvm::Triple::MacOSX) {
    // We can't use x86GetSupportedArchitectures() because it uses
    // the system architecture for some of its return values and also
    // has a 32bits variant.
    result.push_back(ArchSpec("x86_64-apple-macosx"));
    result.push_back(ArchSpec("x86_64-apple-ios-macabi"));
    result.push_back(ArchSpec("arm64-apple-ios-macabi"));
    result.push_back(ArchSpec("arm64e-apple-ios-macabi"));

    // On Apple Silicon, the host platform is compatible with iOS triples to
    // support unmodified "iPhone and iPad Apps on Apple Silicon Macs". Because
    // the binaries are identical, we must rely on the host architecture to
    // tell them apart and mark the host platform as compatible or not.
    if (!process_host_arch ||
        process_host_arch.GetTriple().getOS() == llvm::Triple::MacOSX) {
      result.push_back(ArchSpec("arm64-apple-ios"));
      result.push_back(ArchSpec("arm64e-apple-ios"));
    }
  }
#else
  x86GetSupportedArchitectures(result);
  result.push_back(ArchSpec("x86_64-apple-ios-macabi"));
#endif
  return result;
}

lldb_private::Status PlatformMacOSX::GetSharedModule(
    const lldb_private::ModuleSpec &module_spec, Process *process,
    lldb::ModuleSP &module_sp,
    const lldb_private::FileSpecList *module_search_paths_ptr,
    llvm::SmallVectorImpl<lldb::ModuleSP> *old_modules, bool *did_create_ptr) {
  Status error = GetSharedModuleWithLocalCache(module_spec, module_sp,
                                               module_search_paths_ptr,
                                               old_modules, did_create_ptr);

  if (module_sp) {
    if (module_spec.GetArchitecture().GetCore() ==
        ArchSpec::eCore_x86_64_x86_64h) {
      ObjectFile *objfile = module_sp->GetObjectFile();
      if (objfile == nullptr) {
        // We didn't find an x86_64h slice, fall back to a x86_64 slice
        ModuleSpec module_spec_x86_64(module_spec);
        module_spec_x86_64.GetArchitecture() = ArchSpec("x86_64-apple-macosx");
        lldb::ModuleSP x86_64_module_sp;
        llvm::SmallVector<lldb::ModuleSP, 1> old_x86_64_modules;
        bool did_create = false;
        Status x86_64_error = GetSharedModuleWithLocalCache(
            module_spec_x86_64, x86_64_module_sp, module_search_paths_ptr,
            &old_x86_64_modules, &did_create);
        if (x86_64_module_sp && x86_64_module_sp->GetObjectFile()) {
          module_sp = x86_64_module_sp;
          if (old_modules)
            old_modules->append(old_x86_64_modules.begin(),
                                old_x86_64_modules.end());
          if (did_create_ptr)
            *did_create_ptr = did_create;
          return x86_64_error;
        }
      }
    }
  }

  if (!module_sp) {
    error = FindBundleBinaryInExecSearchPaths(module_spec, process, module_sp,
                                              module_search_paths_ptr,
                                              old_modules, did_create_ptr);
  }
  return error;
}

llvm::StringRef PlatformMacOSX::GetDeviceSupportDirectoryName() {
  return "macOS DeviceSupport";
}

llvm::StringRef PlatformMacOSX::GetPlatformName() { return "MacOSX.platform"; }
