// Copyright 2024 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

#include "pw_multibuf/from_span.h"

namespace pw::multibuf {
namespace {

/// A simple ``ChunkRegionTracker`` that calls ``deleter`` for destruction.
class SpanTracker final : public ChunkRegionTracker {
 private:
  class PrivateConstructorType {};
  static constexpr PrivateConstructorType kPrivateConstructor{};

 public:
  static std::optional<OwnedChunk> New(Allocator& alloc,
                                       ByteSpan region,
                                       pw::Function<void(ByteSpan)>&& deleter) {
    SpanTracker* tracker = alloc.New<SpanTracker>(
        kPrivateConstructor, alloc, region, std::move(deleter));
    if (tracker == nullptr) {
      return std::nullopt;
    }
    std::optional<OwnedChunk> chunk = tracker->CreateFirstChunk();
    if (!chunk.has_value()) {
      // Delete without destroying the associated memory.
      alloc.Delete(tracker);
      return std::nullopt;
    }
    return chunk;
  }

  SpanTracker(PrivateConstructorType,
              pw::Allocator& alloc,
              ByteSpan region,
              pw::Function<void(ByteSpan)>&& deleter)
      : alloc_(alloc), region_(region), deleter_(std::move(deleter)) {}

  ~SpanTracker() final = default;

  // SpanTracker is not copyable nor movable.
  SpanTracker(const SpanTracker&) = delete;
  SpanTracker& operator=(const SpanTracker&) = delete;
  SpanTracker(SpanTracker&&) = delete;
  SpanTracker& operator=(SpanTracker&&) = delete;

 private:
  void Destroy() final {
    deleter_(region_);
    alloc_.Delete(this);
  }

  ByteSpan Region() const final { return region_; }

  void* AllocateChunkClass() final {
    return alloc_.Allocate(allocator::Layout::Of<Chunk>());
  }

  void DeallocateChunkClass(void* ptr) final { return alloc_.Deallocate(ptr); }

  pw::Allocator& alloc_;
  const ByteSpan region_;
  pw::Function<void(ByteSpan)> deleter_;
};

}  // namespace

std::optional<MultiBuf> FromSpan(pw::Allocator& metadata_allocator,
                                 ByteSpan region,
                                 pw::Function<void(ByteSpan)>&& deleter) {
  std::optional<OwnedChunk> chunk =
      SpanTracker::New(metadata_allocator, region, std::move(deleter));
  if (chunk == std::nullopt) {
    return std::nullopt;
  }
  return MultiBuf::FromChunk(std::move(*chunk));
}

}  // namespace pw::multibuf
