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

#include "xenia/ui/graphics_upload_buffer_pool.h"

#include <algorithm>

#include "xenia/base/assert.h"
#include "xenia/base/math.h"

namespace xe {
namespace ui {

GraphicsUploadBufferPool::~GraphicsUploadBufferPool() { ClearCache(); }

void GraphicsUploadBufferPool::Reclaim(uint64_t completed_submission_index) {
  while (submitted_first_) {
    if (submitted_first_->last_submission_index_ > completed_submission_index) {
      break;
    }
    if (writable_last_) {
      writable_last_->next_ = submitted_first_;
    } else {
      writable_first_ = submitted_first_;
    }
    writable_last_ = submitted_first_;
    submitted_first_ = submitted_first_->next_;
    writable_last_->next_ = nullptr;
  }
  if (!submitted_first_) {
    submitted_last_ = nullptr;
  }
}

void GraphicsUploadBufferPool::ClearCache() {
  // Called from the destructor - must not call virtual functions here.
  current_page_flushed_ = 0;
  current_page_used_ = 0;
  while (submitted_first_) {
    Page* next_ = submitted_first_->next_;
    delete submitted_first_;
    submitted_first_ = next_;
  }
  submitted_last_ = nullptr;
  while (writable_first_) {
    Page* next_ = writable_first_->next_;
    delete writable_first_;
    writable_first_ = next_;
  }
  writable_last_ = nullptr;
}

GraphicsUploadBufferPool::Page::~Page() {}

void GraphicsUploadBufferPool::FlushWrites() {
  if (current_page_flushed_ >= current_page_used_) {
    return;
  }
  assert_not_null(writable_first_);
  FlushPageWrites(writable_first_, current_page_flushed_,
                  current_page_used_ - current_page_flushed_);
  current_page_flushed_ = current_page_used_;
}

GraphicsUploadBufferPool::Page* GraphicsUploadBufferPool::Request(
    uint64_t submission_index, size_t size, size_t alignment,
    size_t& offset_out) {
  alignment = std::max(alignment, size_t(1));
  assert_true(xe::is_pow2(alignment));
  size = xe::align(size, alignment);
  assert_true(size <= page_size_);
  if (size > page_size_) {
    return nullptr;
  }
  assert_true(!current_page_used_ ||
              submission_index >= writable_first_->last_submission_index_);
  assert_true(!submitted_last_ ||
              submission_index >= submitted_last_->last_submission_index_);
  size_t current_page_used_aligned = xe::align(current_page_used_, alignment);
  if (current_page_used_aligned + size > page_size_ || !writable_first_) {
    // Start a new page if can't fit all the bytes or don't have an open page.
    if (writable_first_) {
      // Close the page that was current.
      FlushWrites();
      if (submitted_last_) {
        submitted_last_->next_ = writable_first_;
      } else {
        submitted_first_ = writable_first_;
      }
      submitted_last_ = writable_first_;
      writable_first_ = writable_first_->next_;
      submitted_last_->next_ = nullptr;
      if (!writable_first_) {
        writable_last_ = nullptr;
      }
    }
    if (!writable_first_) {
      // Create a new page if none available.
      writable_first_ = CreatePageImplementation();
      if (!writable_first_) {
        // Failed to create.
        return nullptr;
      }
      writable_first_->last_submission_index_ = submission_index;
      writable_first_->next_ = nullptr;
      writable_last_ = writable_first_;
      // After CreatePageImplementation (more specifically, the first successful
      // call), page_size_ may grow - but this doesn't matter here.
    }
    current_page_used_ = 0;
    current_page_used_aligned = 0;
    current_page_flushed_ = 0;
  }
  writable_first_->last_submission_index_ = submission_index;
  offset_out = current_page_used_aligned;
  current_page_used_ = current_page_used_aligned + size;
  return writable_first_;
}

GraphicsUploadBufferPool::Page* GraphicsUploadBufferPool::RequestPartial(
    uint64_t submission_index, size_t size, size_t alignment,
    size_t& offset_out, size_t& size_out) {
  alignment = std::max(alignment, size_t(1));
  assert_true(xe::is_pow2(alignment));
  size = xe::align(size, alignment);
  size = std::min(size, page_size_);
  size_t current_page_used_aligned = xe::align(current_page_used_, alignment);
  if (current_page_used_aligned + alignment <= page_size_) {
    size = std::min(
        size, (page_size_ - current_page_used_aligned) & ~(alignment - 1));
  }
  Page* page = Request(submission_index, size, alignment, offset_out);
  if (!page) {
    return nullptr;
  }
  size_out = size;
  return page;
}

void GraphicsUploadBufferPool::FlushPageWrites(Page* page, size_t offset,
                                               size_t size) {}

}  // namespace ui
}  // namespace xe
