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

#include "xenia/kernel/xenumerator.h"

namespace xe {
namespace kernel {

XEnumerator::XEnumerator(KernelState* kernel_state, size_t items_per_enumerate,
                         size_t item_size)
    : XObject(kernel_state, kObjectType),
      items_per_enumerate_(items_per_enumerate),
      item_size_(item_size) {}

XEnumerator::~XEnumerator() = default;

X_STATUS XEnumerator::Initialize(uint32_t user_index, uint32_t app_id,
                                 uint32_t open_message, uint32_t close_message,
                                 uint32_t flags, uint32_t extra_size,
                                 void** extra_buffer) {
  auto native_object = CreateNative(sizeof(X_KENUMERATOR) + extra_size);
  if (!native_object) {
    return X_STATUS_NO_MEMORY;
  }
  auto guest_object = reinterpret_cast<X_KENUMERATOR*>(native_object);
  guest_object->app_id = app_id;
  guest_object->open_message = open_message;
  guest_object->close_message = close_message;
  guest_object->user_index = user_index;
  guest_object->items_per_enumerate =
      static_cast<uint32_t>(items_per_enumerate_);
  guest_object->flags = flags;
  if (extra_buffer) {
    *extra_buffer =
        !extra_buffer ? nullptr : &native_object[sizeof(X_KENUMERATOR)];
  }

  extra_size_ = extra_size;
  return X_STATUS_SUCCESS;
}

X_STATUS XEnumerator::Initialize(uint32_t user_index, uint32_t app_id,
                                 uint32_t open_message, uint32_t close_message,
                                 uint32_t flags) {
  return Initialize(user_index, app_id, open_message, close_message, flags, 0,
                    nullptr);
}

uint8_t* XStaticUntypedEnumerator::AppendItem() {
  size_t offset = buffer_.size();
  buffer_.resize(offset + item_size());
  item_count_++;
  return const_cast<uint8_t*>(&buffer_.data()[offset]);
}

uint32_t XStaticUntypedEnumerator::WriteItems(uint8_t* buffer_data,
                                              uint32_t buffer_size,
                                              uint32_t* written_count) {
  const size_t actual_count =
      std::min(item_count_ - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  assert_false(buffer_size < item_size());

  const size_t available_count = buffer_size / item_size();
  const size_t size = actual_count * item_size();
  const size_t offset = current_item_ * item_size();

  // Some enumerators expect buffer_size to be respected for now assert.
  assert_false(actual_count > available_count);

  std::memcpy(buffer_data, buffer_.data() + offset, size);

  current_item_ += actual_count;

  if (written_count) {
    *written_count = static_cast<uint32_t>(actual_count);
  }

  return X_ERROR_SUCCESS;
}

uint32_t XAchievementEnumerator::WriteItems(uint8_t* buffer_data,
                                            uint32_t buffer_size,
                                            uint32_t* written_count) {
  const size_t actual_count =
      std::min(items_.size() - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  const size_t available_count = buffer_size / item_size();

  // Unknown if buffer_size should be respected.
  assert_false(actual_count > available_count);

  auto details = reinterpret_cast<xam::X_ACHIEVEMENT_DETAILS*>(buffer_data);
  size_t string_offset =
      items_per_enumerate() * sizeof(xam::X_ACHIEVEMENT_DETAILS);
  auto string_buffer = StringBuffer{
      &buffer_data[string_offset],
      actual_count * xam::X_ACHIEVEMENT_DETAILS::kStringBufferSize};
  for (size_t i = 0; i < actual_count; ++i, ++current_item_) {
    const auto& item = items_[current_item_];
    details[i].id = item.id;
    details[i].label_ptr =
        !!(flags_ & 1) ? AppendString(string_buffer, item.label) : 0;
    details[i].description_ptr =
        !!(flags_ & 2) ? AppendString(string_buffer, item.description) : 0;
    details[i].unachieved_ptr =
        !!(flags_ & 4) ? AppendString(string_buffer, item.unachieved) : 0;
    details[i].image_id = item.image_id;
    details[i].gamerscore = item.gamerscore;
    details[i].unlock_time.high_part = item.unlock_time.high_part;
    details[i].unlock_time.low_part = item.unlock_time.low_part;
    details[i].flags = item.flags;
  }

  if (written_count) {
    *written_count = static_cast<uint32_t>(actual_count);
  }

  return X_ERROR_SUCCESS;
}

uint32_t XTitleEnumerator::WriteItems(uint8_t* buffer_data,
                                      uint32_t buffer_size,
                                      uint32_t* written_count) {
  const size_t actual_count =
      std::min(items_.size() - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  const size_t available_count = buffer_size / item_size();

  // Unknown if buffer_size should be respected.
  assert_false(actual_count > available_count);

  auto details = reinterpret_cast<XTITLE_PLAYED*>(buffer_data);

  for (size_t i = 0; i < actual_count; ++i, ++current_item_) {
    const auto& item = items_[current_item_];
    details[i].base.title_id = item.id;
    details[i].base.achievements_count = item.achievements_count;
    details[i].base.achievements_unlocked = item.unlocked_achievements_count;
    details[i].base.gamerscore_total = item.gamerscore_amount;
    details[i].base.gamerscore_earned = item.title_earned_gamerscore;
    details[i].base.online_achievement_count =
        item.online_unlocked_achievements;
    details[i].base.all_avatar_awards.earned = item.all_avatar_awards.earned;
    details[i].base.all_avatar_awards.possible =
        item.all_avatar_awards.possible;
    details[i].base.male_avatar_awards.earned = item.male_avatar_awards.earned;
    details[i].base.male_avatar_awards.possible =
        item.male_avatar_awards.possible;
    details[i].base.female_avatar_awards.earned =
        item.female_avatar_awards.earned;
    details[i].base.female_avatar_awards.possible =
        item.female_avatar_awards.possible;
    details[i].base.flags = item.flags;

    // On console if title is played offline this field is set to 0.
    details[i].base.last_played = X_FILETIME((uint64_t)0);
    string_util::copy_and_swap_truncating((char16_t*)&details[i].title_name,
                                          item.title_name, 128);
  }

  if (written_count) {
    *written_count = static_cast<uint32_t>(actual_count);
  }

  return X_ERROR_SUCCESS;
}

uint32_t XUserStatsEnumerator::WriteItems(uint8_t* buffer_data,
                                          uint32_t buffer_size,
                                          uint32_t* written_count) {
  size_t count = std::min(items_.size() - current_item_, items_per_enumerate());
  if (!count) {
    return X_ERROR_NO_MORE_FILES;
  }

  size_t size = count * item_size();

  return X_ERROR_SUCCESS;
}

uint32_t XMPCreateUserPlaylistEnumerator::WriteItems(uint8_t* buffer_data,
                                                     uint32_t buffer_size,
                                                     uint32_t* written_count) {
  // Fixed 545408C0 freezing at main menu.
  std::memset(buffer_data, 0, buffer_size);

  const size_t actual_count =
      std::min(items_.size() - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  const size_t available_count = buffer_size / item_size();
  const size_t valid_count = std::min(actual_count, available_count);

  xam::XMP_USER_PLAYLIST_INFO* results =
      reinterpret_cast<xam::XMP_USER_PLAYLIST_INFO*>(buffer_data);

  std::copy_n(items_.begin() + current_item_, valid_count, results);

  current_item_ += valid_count;

  if (written_count) {
    *written_count = static_cast<uint32_t>(valid_count);
  }

  return X_ERROR_SUCCESS;
}

uint32_t ProfileEnumerator::WriteItems(uint8_t* buffer_data,
                                       uint32_t buffer_size,
                                       uint32_t* written_count) {
  const size_t actual_count =
      std::min(items_.size() - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  // FFFE07D1 provides buffer_size of 0.
  xam::X_PROFILEENUMRESULT* profiles =
      reinterpret_cast<xam::X_PROFILEENUMRESULT*>(buffer_data);

  std::copy_n(items_.begin() + current_item_, actual_count, profiles);

  current_item_ += actual_count;

  if (written_count) {
    *written_count = static_cast<uint32_t>(actual_count);
  }

  return X_ERROR_SUCCESS;
}

uint32_t ContentEnumerator::WriteItems(uint8_t* buffer_data,
                                       uint32_t buffer_size,
                                       uint32_t* written_count) {
  const size_t actual_count =
      std::min(items_.size() - current_item_, items_per_enumerate());
  if (!actual_count) {
    return X_ERROR_NO_MORE_FILES;
  }

  // 4E4D07F0 does not respect buffer_size, possibly bug?
  xam::XCONTENT_DATA* contents =
      reinterpret_cast<xam::XCONTENT_DATA*>(buffer_data);

  std::copy_n(items_.begin() + current_item_, actual_count, contents);

  current_item_ += actual_count;

  if (written_count) {
    *written_count = static_cast<uint32_t>(actual_count);
  }

  return X_ERROR_SUCCESS;
}

}  // namespace kernel
}  // namespace xe
