/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2022 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/string_util.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/smc.h"
#include "xenia/kernel/user_module.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/xam_content_device.h"
#include "xenia/kernel/xam/xam_module.h"
#include "xenia/kernel/xam/xam_private.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
#include "xenia/kernel/xboxkrnl/xboxkrnl_threading.h"
#include "xenia/kernel/xenumerator.h"
#include "xenia/ui/imgui_dialog.h"
#include "xenia/ui/imgui_drawer.h"
#include "xenia/vfs/devices/stfs_xbox.h"
#include "xenia/xbox.h"

DEFINE_int32(
    license_mask, 0,
    "Set license mask for activated content.\n"
    " 0 = No licenses enabled.\n"
    " 1 = First license enabled. Generally the full version license in\n"
    "     Xbox Live Arcade titles.\n"
    " -1 or 0xFFFFFFFF = All possible licenses enabled. Generally a\n"
    "                    bad idea, could lead to undefined behavior.",
    "Content");

namespace xe {
namespace kernel {
namespace xam {

dword_result_t XamContentGetLicenseMask_entry(lpdword_t mask_ptr,
                                              lpvoid_t overlapped_ptr) {
  if (!mask_ptr) {
    return X_ERROR_INVALID_PARAMETER;
  }

  auto run = [mask_ptr](uint32_t& extended_error, uint32_t& length) {
    X_RESULT result = X_ERROR_FUNCTION_FAILED;

    // Remark: This cannot be reflected as on console. Xenia can boot games
    // directly and XBLA games can be repacked to ZAR. For these titles we must
    // provide some license. Normally it should fail for OpticalDisc type.
    if (kernel_state()->deployment_type_ != XDeploymentType::kOther) {
      // Each bit in the mask represents a granted license. Available licenses
      // seems to vary from game to game, but most appear to use bit 0 to
      // indicate if the game is purchased or not.
      *mask_ptr = static_cast<uint32_t>(cvars::license_mask);
      result = X_ERROR_SUCCESS;
    }

    extended_error = X_HRESULT_FROM_WIN32(result);
    length = 0;
    return result;
  };

  if (!overlapped_ptr) {
    uint32_t extended_error, length;
    return run(extended_error, length);
  } else {
    kernel_state()->CompleteOverlappedDeferredEx(run, overlapped_ptr);
    return X_ERROR_IO_PENDING;
  }
}
DECLARE_XAM_EXPORT2(XamContentGetLicenseMask, kContent, kStub, kHighFrequency);

dword_result_t xeXamContentResolve(
    dword_t user_index, lpvoid_t content_data_ptr, dword_t content_data_size,
    lpstring_t path_ptr, dword_t path_size, dword_t create_directory,
    lpstring_t root_name_ptr, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  auto run = [user_index, content_data_ptr, content_data_size, path_ptr,
              path_size, create_directory, root_name_ptr](
                 uint32_t& extended_error, uint32_t& length) -> X_RESULT {
    XCONTENT_AGGREGATE_DATA content_data;
    if (content_data_size == sizeof(XCONTENT_DATA)) {
      content_data = *content_data_ptr.as<XCONTENT_DATA*>();
    } else if (content_data_size == sizeof(XCONTENT_DATA_INTERNAL)) {
      // Due to the current implementation of content data we can't use
      // XCONTENT_DATA_INTERNAL
      content_data = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
    } else {
      assert_always();
      return X_ERROR_INVALID_PARAMETER;
    }
    uint64_t xuid = 0;
    if (user_index < XUserMaxUserCount) {
      const auto profile =
          kernel_state()->xam_state()->profile_manager()->GetProfile(
              static_cast<uint8_t>(user_index));
      if (profile && content_data.content_type == XContentType::kSavedGame) {
        xuid = profile->xuid();
      }
    }

    std::string root_device_path = "";

    if (root_name_ptr) {
      // Check if root_name is valid.
      // root_device_path = std::string(root_name_ptr);
      // Unsupported for now.
      return X_ERROR_INVALID_PARAMETER;
    } else {
      if (content_data.device_id == static_cast<uint32_t>(DummyDeviceId::HDD)) {
        root_device_path = "\\Device\\Harddisk0\\Partition1\\Content\\";
      } else if (content_data.device_id ==
                 static_cast<uint32_t>(DummyDeviceId::ODD)) {
        // Or GAME, but D: usually means DVD drive meanwhile GAME always
        // pinpoints to game, even if it is running from HDD
        root_device_path = "D:\\content\\";
      } else {
        return X_ERROR_INVALID_PARAMETER;
      }
    }

    const std::string relative_path = fmt::format(
        "{:016X}\\{:08X}\\{:08X}\\{}", xuid, kernel_state()->title_id(),
        static_cast<uint32_t>(content_data.content_type.get()),
        content_data.file_name());

    string_util::copy_truncating(path_ptr, root_device_path + relative_path,
                                 path_size);

    // Check if it exists and try to mount that package
    // Result of buffer_ptr is sent to RtlInitAnsiString.
    // buffer_size is usually 260 (max path).
    return X_ERROR_SUCCESS;
  };

  if (!overlapped_ptr) {
    uint32_t extended_error, length;
    return run(extended_error, length);
  } else {
    kernel_state()->CompleteOverlappedDeferredEx(run, overlapped_ptr);
    return X_ERROR_IO_PENDING;
  }
}

dword_result_t XamContentResolve_entry(
    dword_t user_index, lpvoid_t content_data_ptr, lpstring_t path_ptr,
    dword_t path_size, dword_t create_directory, lpstring_t root_name_ptr,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  return xeXamContentResolve(user_index, content_data_ptr,
                             sizeof(XCONTENT_DATA), path_ptr, path_size,
                             create_directory, root_name_ptr, overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentResolve, kContent, kSketchy);

dword_result_t XamContentResolveInternal_entry(
    lpvoid_t content_data_ptr, lpstring_t path_ptr, dword_t path_size,
    dword_t create_directory, lpstring_t root_name_ptr,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  return xeXamContentResolve(
      XUserIndexNone, content_data_ptr, sizeof(XCONTENT_DATA_INTERNAL),
      path_ptr, path_size, create_directory, root_name_ptr, overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentResolveInternal, kContent, kSketchy);

// https://github.com/MrColdbird/gameservice/blob/master/ContentManager.cpp
dword_result_t XamContentCreateEnumeratorInternal_entry(
    qword_t xuid, dword_t device_id, dword_t content_type, dword_t title_id,
    dword_t content_flags, dword_t items_per_enumerate,
    lpdword_t buffer_size_ptr, lpdword_t handle_out) {
  assert_not_null(handle_out);

  auto device_info = device_id == 0 ? nullptr : GetDummyDeviceInfo(device_id);
  if ((device_id && device_info == nullptr) || !handle_out) {
    if (buffer_size_ptr) {
      *buffer_size_ptr = 0;
    }

    // TODO(benvanik): memset 0 the data?
    return X_E_INVALIDARG;
  }

  if (buffer_size_ptr) {
    *buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
  }

  auto e = object_ref<ContentEnumerator>(
      new ContentEnumerator(kernel_state(), items_per_enumerate));

  auto result =
      e->Initialize(XUserIndexAny, 0xFE, 0x20005, 0x20007, 0, 0x98, nullptr);
  if (XFAILED(result)) {
    return result;
  }

  uint32_t title = title_id;
  if (!title) {
    title = kernel_state()->title_id();
  }

  std::vector<XCONTENT_AGGREGATE_DATA> enumerated_content =
      {};  // XCONTENT_DATA_INTERNAL

  if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
    std::vector<uint64_t> xuids_to_enumerate = {};
    if (xuid) {
      xuids_to_enumerate.push_back(xuid);
    }

    if (!xuid || !(content_flags & vfs::XContentFlag::kExcludeCommon)) {
      xuids_to_enumerate.push_back(0);  // Common content
    }

    for (const auto& xuid : xuids_to_enumerate) {
      auto user_enumerated_data =
          kernel_state()->content_manager()->ListContent(
              static_cast<uint32_t>(DummyDeviceId::HDD), xuid, title,
              static_cast<XContentType>(content_type.value()));

      enumerated_content.insert(enumerated_content.end(),
                                user_enumerated_data.cbegin(),
                                user_enumerated_data.cend());
    }

    // Remove duplicates
    enumerated_content.erase(
        std::unique(enumerated_content.begin(), enumerated_content.end()),
        enumerated_content.end());
  }

  if (!device_info || device_info->device_id == DummyDeviceId::ODD) {
    auto disc_enumerated_data =
        kernel_state()->content_manager()->ListContentODD(
            static_cast<uint32_t>(DummyDeviceId::ODD), 0, title,
            XContentType(uint32_t(content_type)));

    enumerated_content.insert(enumerated_content.end(),
                              disc_enumerated_data.cbegin(),
                              disc_enumerated_data.cend());
  }

  for (const auto& content_data : enumerated_content) {
    e->AppendItem(content_data);
    XELOGI("{}: Adding: {} (Filename: {}) to enumerator result", __func__,
           xe::to_utf8(content_data.display_name()), content_data.file_name());
  }

  XELOGD("XamContentCreateEnumeratorInternal: added {} items to enumerator",
         e->item_count());

  *handle_out = e->handle();
  return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamContentCreateEnumeratorInternal, kContent, kImplemented);

dword_result_t XamContentCreateEnumerator_entry(
    dword_t user_index, dword_t device_id, dword_t content_type,
    dword_t content_flags, dword_t items_per_enumerate,
    lpdword_t buffer_size_ptr, lpdword_t handle_out) {
  uint64_t xuid = 0;
  if (user_index < XUserMaxUserCount) {
    const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

    if (!user) {
      return X_ERROR_NO_SUCH_USER;
    }

    xuid = user->xuid();
  }

  return XamContentCreateEnumeratorInternal_entry(
      xuid, device_id, content_type, 0, content_flags, items_per_enumerate,
      buffer_size_ptr, handle_out);
}
DECLARE_XAM_EXPORT1(XamContentCreateEnumerator, kContent, kImplemented);

enum class kDispositionState : uint32_t { Unknown = 0, Create = 1, Open = 2 };

dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
                                  lpvoid_t content_data_ptr,
                                  dword_t content_data_size, dword_t flags,
                                  lpdword_t disposition_ptr,
                                  lpdword_t license_mask_ptr,
                                  dword_t cache_size, qword_t content_size,
                                  pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  uint64_t xuid = 0;
  if (user_index != XUserIndexNone && user_index != XUserIndexAny) {
    const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

    if (!user) {
      return X_ERROR_NO_SUCH_USER;
    }

    xuid = user->xuid();
  }

  if (!root_name || *root_name == '\0') {
    return X_ERROR_INVALID_NAME;
  }

  // Check if we have something under provided symlink.
  std::string symlink_path = root_name.value();
  if (!symlink_path.ends_with(':')) {
    symlink_path += ':';
  }

  if (kernel_state()->file_system()->IsSymbolicLinkRegistered(symlink_path)) {
    return X_ERROR_INVALID_PARAMETER;
  }

  XCONTENT_AGGREGATE_DATA content_data;
  if (content_data_size == sizeof(XCONTENT_DATA)) {
    content_data = *content_data_ptr.as<XCONTENT_DATA*>();
  } else if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) {
    content_data = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
  } else {
    assert_always();
    return X_ERROR_INVALID_PARAMETER;
  }

  if (content_data.content_type == XContentType::kMarketplaceContent) {
    xuid = 0;
  }

  auto content_manager = kernel_state()->content_manager();

  if (overlapped_ptr && disposition_ptr) {
    *disposition_ptr = 0;
  }

  auto run = [content_manager, xuid, root_name = root_name.value(), flags,
              content_data, disposition_ptr, license_mask_ptr, overlapped_ptr](
                 uint32_t& extended_error, uint32_t& length) -> X_RESULT {
    X_RESULT result = X_ERROR_INVALID_PARAMETER;
    kDispositionState disposition = kDispositionState::Unknown;
    switch (flags & 0xF) {
      case vfs::XContentFlag::kCreateNew:
        // Fail if exists.
        if (content_manager->ContentExists(xuid, content_data)) {
          result = X_ERROR_ALREADY_EXISTS;
        } else {
          disposition = kDispositionState::Create;
        }
        break;
      case vfs::XContentFlag::kCreateAlways:
        // Overwrite existing, if any.
        if (content_manager->ContentExists(xuid, content_data)) {
          content_manager->DeleteContent(xuid, content_data);
        }
        disposition = kDispositionState::Create;
        break;
      case vfs::XContentFlag::kOpenExisting:
        // Open only if exists.
        if (!content_manager->ContentExists(xuid, content_data)) {
          result = X_ERROR_PATH_NOT_FOUND;
        } else {
          disposition = kDispositionState::Open;
        }
        break;
      case vfs::XContentFlag::kOpenAlways:
        // Create if needed.
        if (!content_manager->ContentExists(xuid, content_data)) {
          disposition = kDispositionState::Create;
        } else {
          disposition = kDispositionState::Open;
        }
        break;
      case vfs::XContentFlag::kTruncateExisting:
        // Fail if doesn't exist, if does exist delete and recreate.
        if (!content_manager->ContentExists(xuid, content_data)) {
          result = X_ERROR_PATH_NOT_FOUND;
        } else {
          content_manager->DeleteContent(xuid, content_data);
          disposition = kDispositionState::Create;
        }
        break;
      default:
        assert_unhandled_case(flags & 0xF);
        break;
    }

    uint32_t content_license = 0;
    if (disposition == kDispositionState::Create) {
      result = content_manager->CreateContent(root_name, xuid, content_data);
      if (XSUCCEEDED(result)) {
        content_manager->WriteContentHeaderFile(xuid, content_data);
      }
    } else if (disposition == kDispositionState::Open) {
      result = content_manager->OpenContent(root_name, xuid, content_data,
                                            content_license);
    }

    if (license_mask_ptr && XSUCCEEDED(result)) {
      *license_mask_ptr = content_license;
    }

    extended_error = X_HRESULT_FROM_WIN32(result);
    length = static_cast<uint32_t>(disposition);

    if (disposition_ptr) {
      *disposition_ptr = static_cast<uint32_t>(disposition);
    }

    if (result && overlapped_ptr) {
      result = X_ERROR_FUNCTION_FAILED;
    }
    return result;
  };

  if (!overlapped_ptr) {
    uint32_t extended_error, length;
    return run(extended_error, length);
  } else {
    kernel_state()->CompleteOverlappedDeferredEx(run, overlapped_ptr);
    return X_ERROR_IO_PENDING;
  }
}

dword_result_t XamContentCreateEx_entry(
    dword_t user_index, lpstring_t root_name, lpvoid_t content_data_ptr,
    dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr,
    dword_t cache_size, qword_t content_size,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  auto content_data = *content_data_ptr.as<XCONTENT_DATA*>();
  if (content_data.file_name_raw[0] == '\0') {
    return X_ERROR_INVALID_NAME;
  }
  return xeXamContentCreate(user_index, root_name, content_data_ptr,
                            sizeof(XCONTENT_DATA), flags, disposition_ptr,
                            license_mask_ptr, cache_size, content_size,
                            overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentCreateEx, kContent, kImplemented);

dword_result_t XamContentCreate_entry(
    dword_t user_index, lpstring_t root_name, lpvoid_t content_data_ptr,
    dword_t flags, lpdword_t disposition_ptr, lpdword_t license_mask_ptr,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  return XamContentCreateEx_entry(user_index, root_name, content_data_ptr,
                                  flags, disposition_ptr, license_mask_ptr, 0,
                                  0, overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentCreate, kContent, kImplemented);

dword_result_t XamContentCreateInternal_entry(
    lpstring_t root_name, lpvoid_t content_data_ptr, dword_t flags,
    lpdword_t disposition_ptr, lpdword_t license_mask_ptr, dword_t cache_size,
    qword_t content_size, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  auto content_data_internal = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
  if (content_data_internal.file_name_raw[0] == '\0') {
    return X_ERROR_INVALID_NAME;
  }
  return xeXamContentCreate(XUserIndexNone, root_name, content_data_ptr,
                            sizeof(XCONTENT_AGGREGATE_DATA), flags,
                            disposition_ptr, license_mask_ptr, cache_size,
                            content_size, overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentCreateInternal, kContent, kImplemented);

dword_result_t XamContentOpenFile_entry(
    dword_t user_index, lpstring_t root_name, lpstring_t path, dword_t flags,
    lpdword_t disposition_ptr, lpdword_t license_mask_ptr,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  // TODO(gibbed): arguments assumed based on XamContentCreate.
  return X_ERROR_FILE_NOT_FOUND;
}
DECLARE_XAM_EXPORT1(XamContentOpenFile, kContent, kStub);

dword_result_t XamContentFlush_entry(lpstring_t root_name,
                                     pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  X_RESULT result = X_ERROR_SUCCESS;
  if (overlapped_ptr) {
    kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
    return X_ERROR_IO_PENDING;
  } else {
    return result;
  }
}
DECLARE_XAM_EXPORT1(XamContentFlush, kContent, kStub);

dword_result_t XamContentClose_entry(lpstring_t root_name,
                                     pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  // Closes a previously opened root from XamContentCreate*.
  auto result =
      kernel_state()->content_manager()->CloseContent(root_name.value());

  if (overlapped_ptr) {
    kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
    return X_ERROR_IO_PENDING;
  } else {
    return result;
  }
}
DECLARE_XAM_EXPORT1(XamContentClose, kContent, kImplemented);

dword_result_t XamContentGetCreator_entry(
    dword_t user_index, lpvoid_t content_data_ptr, lpdword_t is_creator_ptr,
    lpqword_t creator_xuid_ptr, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  if (!is_creator_ptr) {
    return X_ERROR_INVALID_PARAMETER;
  }

  const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

  if (!user) {
    return X_ERROR_NO_SUCH_USER;
  }

  XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();

  auto run = [content_data, xuid = user->xuid(), user_index, is_creator_ptr,
              creator_xuid_ptr, overlapped_ptr](uint32_t& extended_error,
                                                uint32_t& length) -> X_RESULT {
    X_RESULT result = X_ERROR_SUCCESS;

    bool content_exists =
        kernel_state()->content_manager()->ContentExists(xuid, content_data);

    if (content_exists) {
      if (content_data.content_type == XContentType::kSavedGame) {
        // User always creates saves.
        *is_creator_ptr = 1;
        if (creator_xuid_ptr) {
          if (kernel_state()->xam_state()->IsUserSignedIn(user_index)) {
            const auto& user_profile =
                kernel_state()->xam_state()->GetUserProfile(user_index);
            *creator_xuid_ptr = user_profile->xuid();
          } else {
            result = X_ERROR_NO_SUCH_USER;
          }
        }
      } else {
        *is_creator_ptr = 0;
        if (creator_xuid_ptr) {
          *creator_xuid_ptr = 0;
        }
      }
    } else {
      result = X_ERROR_PATH_NOT_FOUND;
    }

    extended_error = X_HRESULT_FROM_WIN32(result);
    length = 0;

    if (result && overlapped_ptr) {
      result = X_ERROR_FUNCTION_FAILED;
    }

    return result;
  };

  if (!overlapped_ptr) {
    uint32_t extended_error, length;
    return run(extended_error, length);
  } else {
    kernel_state()->CompleteOverlappedDeferredEx(run, overlapped_ptr);
    return X_ERROR_IO_PENDING;
  }
}
DECLARE_XAM_EXPORT1(XamContentGetCreator, kContent, kImplemented);

dword_result_t XamContentGetThumbnail_entry(
    dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr,
    lpdword_t buffer_size_ptr, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

  if (!user) {
    return X_ERROR_NO_SUCH_USER;
  }

  assert_not_null(buffer_size_ptr);
  uint32_t buffer_size = *buffer_size_ptr;
  XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();

  // Get thumbnail (if it exists).
  std::vector<uint8_t> buffer;
  auto result = kernel_state()->content_manager()->GetContentThumbnail(
      user->xuid(), content_data, &buffer);

  *buffer_size_ptr = uint32_t(buffer.size());

  if (XSUCCEEDED(result)) {
    // Write data, if we were given a pointer.
    // This may have just been a size query.
    if (buffer_ptr) {
      if (buffer_size < buffer.size()) {
        // Dest buffer too small.
        result = X_ERROR_INSUFFICIENT_BUFFER;
      } else {
        // Copy data.
        std::memcpy((uint8_t*)buffer_ptr, buffer.data(), buffer.size());
      }
    }
  }

  if (overlapped_ptr) {
    kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
    return X_ERROR_IO_PENDING;
  } else {
    return result;
  }
}
DECLARE_XAM_EXPORT1(XamContentGetThumbnail, kContent, kImplemented);

dword_result_t XamContentSetThumbnail_entry(
    dword_t user_index, lpvoid_t content_data_ptr, lpvoid_t buffer_ptr,
    dword_t buffer_size, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

  if (!user) {
    return X_ERROR_NO_SUCH_USER;
  }

  XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();

  // Buffer is PNG data.
  auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
                                     (uint8_t*)buffer_ptr + buffer_size);
  auto result = kernel_state()->content_manager()->SetContentThumbnail(
      user->xuid(), content_data, std::move(buffer));

  if (overlapped_ptr) {
    kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
    return X_ERROR_IO_PENDING;
  } else {
    return result;
  }
}
DECLARE_XAM_EXPORT1(XamContentSetThumbnail, kContent, kImplemented);

dword_result_t xeXamContentDelete(dword_t user_index, lpvoid_t content_data_ptr,
                                  dword_t content_data_size,
                                  pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  uint64_t xuid = 0;
  XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
  if (content_data_size == sizeof(XCONTENT_AGGREGATE_DATA)) {
    content_data = *content_data_ptr.as<XCONTENT_AGGREGATE_DATA*>();
  }

  if (user_index != XUserIndexNone) {
    const auto& user = kernel_state()->xam_state()->GetUserProfile(user_index);

    if (!user) {
      return X_ERROR_NO_SUCH_USER;
    }

    xuid = user->xuid();
  }

  auto result =
      kernel_state()->content_manager()->DeleteContent(xuid, content_data);

  if (overlapped_ptr) {
    kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
    return X_ERROR_IO_PENDING;
  } else {
    return result;
  }
}

dword_result_t XamContentDelete_entry(
    dword_t user_index, lpvoid_t content_data_ptr,
    pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  return xeXamContentDelete(user_index, content_data_ptr, sizeof(XCONTENT_DATA),
                            overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentDelete, kContent, kImplemented);

dword_result_t XamContentDeleteInternal_entry(
    lpvoid_t content_data_ptr, pointer_t<XAM_OVERLAPPED> overlapped_ptr) {
  // INFO: Analysis of xam.xex shows that "internal" functions are wrappers with
  // 0xFE as user_index.
  // In XAM content size is set to 0x200.
  return xeXamContentDelete(XUserIndexNone, content_data_ptr,
                            sizeof(XCONTENT_AGGREGATE_DATA), overlapped_ptr);
}
DECLARE_XAM_EXPORT1(XamContentDeleteInternal, kContent, kImplemented);

typedef struct {
  xe::be<uint32_t> stringTitlePtr;
  xe::be<uint32_t> stringTextPtr;
  xe::be<uint32_t> stringBtnMsgPtr;
} X_SWAPDISC_ERROR_MESSAGE;
static_assert_size(X_SWAPDISC_ERROR_MESSAGE, 12);

dword_result_t XamSwapDisc_entry(
    dword_t disc_number, pointer_t<X_KEVENT> completion_handle,
    pointer_t<X_SWAPDISC_ERROR_MESSAGE> error_message) {
  xex2_opt_execution_info* info = nullptr;
  kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO,
                                                      &info);

  if (info->disc_number > info->disc_count) {
    return X_ERROR_INVALID_PARAMETER;
  }

  auto completion_event = [completion_handle]() -> void {
    auto kevent = xboxkrnl::xeKeSetEvent(completion_handle, 1, 0);

    // Release the completion handle
    auto object =
        XObject::GetNativeObject<XObject>(kernel_state(), completion_handle);
    if (object) {
      object->Retain();
    }
  };

  if (info->disc_number == disc_number) {
    completion_event();
    return X_ERROR_SUCCESS;
  }

  auto filesystem = kernel_state()->file_system();
  auto mount_path = "\\Device\\LauncherData";

  if (filesystem->ResolvePath(mount_path) != NULL) {
    filesystem->UnregisterDevice(mount_path);
  }

  std::u16string text_message = xe::load_and_swap<std::u16string>(
      kernel_state()->memory()->TranslateVirtual(error_message->stringTextPtr));

  const std::filesystem::path new_disc_path =
      kernel_state()->emulator()->GetNewDiscPath(xe::to_utf8(text_message));
  XELOGI("GetNewDiscPath returned path {}.", new_disc_path.string().c_str());

  // TODO(Gliniak): Implement checking if inserted file is requested one
  kernel_state()->emulator()->MountPath(new_disc_path, mount_path);
  completion_event();

  return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamSwapDisc, kContent, kSketchy);

dword_result_t XamLoaderGetDvdTrayState_entry() {
  return static_cast<uint8_t>(kernel_state()->smc()->GetTrayState());
}
DECLARE_XAM_EXPORT1(XamLoaderGetDvdTrayState, kNone, kImplemented);

void XamLoaderGetMediaInfoEx_entry(lpdword_t media_type, lpdword_t unk2,
                                   lpdword_t unk3) {
  if (media_type) {
    *media_type = X_DVD_DISC_STATE::XBOX_360_GAME_DISC;
  }
  if (unk2) {
    *unk2 = 0;
  }
  if (unk3) {
    *unk3 = 0;
  }
}
DECLARE_XAM_EXPORT1(XamLoaderGetMediaInfoEx, kNone, kStub);

void XamLoaderGetMediaInfo_entry(lpdword_t media_type, lpdword_t unk2) {
  XamLoaderGetMediaInfoEx_entry(media_type, unk2, 0);
}
DECLARE_XAM_EXPORT1(XamLoaderGetMediaInfo, kNone, kStub);

dword_result_t xeXamContentLaunchImage(dword_t user_index,
                                       lpstring_t image_location,
                                       lpvoid_t content_data_ptr,
                                       dword_t content_data_size,
                                       lpstring_t xex_path, dword_t flag) {
  /* Notes:
      - In code this subfunction is used by all XamContentLaunchImage
     functions
      - Due to the current implementation of content data we can't use
     XCONTENT_DATA_INTERNAL
      - flags used by XamLoaderLaunchTitleEx
      - user_index used by xeXamContentOpenFile
      - if image_location null use xeXamContentOpenFile else use
     exXamContentCreate
      - root_name is "XSYSLAUNCH" while XamLoaderLaunchTitleEx uses
     "XSYSLAUNCH:\\""
      - title_id is usually written into first 8 characters of filename
  */
  vfs::Entry* entry;
  if (!image_location) {
    XCONTENT_AGGREGATE_DATA content_data =
        *content_data_ptr.as<XCONTENT_DATA*>();
    const uint32_t title_id = xe::string_util::from_string<uint32_t>(
        content_data.file_name().substr(0, 8), true);

    // This should be done via content_manager, however as it isn't capable of
    // such action we need to improvise.
    const std::string package_path =
        fmt::format("GAME:/Content/0000000000000000/{:08X}/{:08X}/{}", title_id,
                    static_cast<uint32_t>(content_data.content_type.get()),
                    content_data.file_name());

    entry = kernel_state()->file_system()->ResolvePath(package_path);
  } else {
    entry = kernel_state()->file_system()->ResolvePath(image_location.value());
  }

  if (!entry) {
    return X_STATUS_NO_SUCH_FILE;
  }

  const std::filesystem::path host_path =
      kernel_state()->emulator()->content_root() / entry->name();

  if (!std::filesystem::exists(host_path)) {
    uint64_t progress = 0;
    vfs::VirtualFileSystem::ExtractContentFile(
        entry, kernel_state()->emulator()->content_root(), progress, true);
  }

  auto xam = kernel_state()->GetKernelModule<XamModule>("xam.xex");

  auto& loader_data = xam->loader_data();
  loader_data.host_path = xe::path_to_utf8(host_path);
  loader_data.launch_path = xex_path.value();

  xam->SaveLoaderData();

  auto display_window = kernel_state()->emulator()->display_window();
  auto imgui_drawer = kernel_state()->emulator()->imgui_drawer();

  if (display_window && imgui_drawer) {
    display_window->app_context().CallInUIThreadSynchronous([imgui_drawer]() {
      xe::ui::ImGuiDialog::ShowMessageBox(
          imgui_drawer, "Launching new title!",
          "Launching new title. \nPlease close Xenia and launch it again. Game "
          "should load automatically.");
    });
  }

  kernel_state()->TerminateTitle();
  return X_ERROR_SUCCESS;
}

dword_result_t XamContentLaunchImageFromFileInternal_entry(
    lpstring_t image_location, lpstring_t xex_name) {
  return xeXamContentLaunchImage(XUserIndexNone, image_location, nullptr, NULL,
                                 xex_name, NULL);
}
DECLARE_XAM_EXPORT1(XamContentLaunchImageFromFileInternal, kContent, kStub);

dword_result_t XamContentLaunchImage_entry(dword_t user_index,
                                           lpvoid_t content_data_ptr,
                                           lpstring_t xex_path) {
  return xeXamContentLaunchImage(user_index, nullptr, content_data_ptr,
                                 sizeof(XCONTENT_DATA), xex_path, NULL);
}
DECLARE_XAM_EXPORT1(XamContentLaunchImage, kContent, kStub);

dword_result_t XamContentLaunchImageInternal_entry(lpvoid_t content_data_ptr,
                                                   lpstring_t xex_path) {
  return xeXamContentLaunchImage(XUserIndexNone, nullptr, content_data_ptr,
                                 sizeof(XCONTENT_DATA_INTERNAL), xex_path,
                                 NULL);
}
DECLARE_XAM_EXPORT1(XamContentLaunchImageInternal, kContent, kStub);

dword_result_t XamContentLaunchImageInternalEx_entry(lpvoid_t content_data_ptr,
                                                     lpstring_t xex_path,
                                                     dword_t flags) {
  return xeXamContentLaunchImage(XUserIndexNone, nullptr, content_data_ptr,
                                 sizeof(XCONTENT_DATA_INTERNAL), xex_path,
                                 flags);
}
DECLARE_XAM_EXPORT1(XamContentLaunchImageInternalEx, kContent, kStub);

void XamContentRegisterChangeCallback_entry(dword_t callback) {
  kernel_state()->xam_state()->SetContentRegisterCallback(callback);
}
DECLARE_XAM_EXPORT1(XamContentRegisterChangeCallback, kContent, kImplemented);

dword_result_t XamContentGetDeviceVolumePath_entry(dword_t device_id,
                                                   lpvoid_t path_ptr,
                                                   dword_t path_size,
                                                   dword_t append_backslash) {
  std::string volume_path = "hdd0\\";
  if (device_id != static_cast<uint32_t>(DummyDeviceId::HDD)) {
    return X_ERROR_FUNCTION_FAILED;
  }

  char* path =
      kernel_memory()->TranslateVirtual<char*>(path_ptr.guest_address());

  string_util::copy_truncating(path, volume_path, path_size);

  return X_ERROR_SUCCESS;
}
DECLARE_XAM_EXPORT1(XamContentGetDeviceVolumePath, kContent, kStub);
}  // namespace xam
}  // namespace kernel
}  // namespace xe

DECLARE_XAM_EMPTY_REGISTER_EXPORTS(Content);
