/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *     http://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.
 */

#pragma once

#include <cstdint>
#include <memory>
#include <unordered_map>

#include "paimon/common/io/cache/cache_manager.h"
#include "paimon/fs/file_system.h"
#include "paimon/memory/memory_segment.h"
#include "paimon/reader/batch_reader.h"
#include "paimon/result.h"
namespace paimon {

class PAIMON_EXPORT BlockCache {
 public:
    BlockCache(const std::string& file_path, const std::shared_ptr<InputStream>& in,
               const std::shared_ptr<CacheManager>& cache_manager,
               const std::shared_ptr<MemoryPool>& pool)
        : pool_(pool), file_path_(file_path), in_(in), cache_manager_(cache_manager) {}

    ~BlockCache() {
        Close();
    }

    Result<MemorySegment> GetBlock(
        int64_t position, int32_t length, bool is_index,
        std::function<Result<MemorySegment>(const MemorySegment&)> decompress_func) {
        auto key = CacheKey::ForPosition(file_path_, position, length, is_index);
        auto it = blocks_.find(key);
        if (it == blocks_.end() || it->second.GetAccessCount() == CacheManager::REFRESH_COUNT) {
            PAIMON_ASSIGN_OR_RAISE(
                MemorySegment segment,
                cache_manager_->GetPage(
                    key,
                    [&](const std::shared_ptr<paimon::CacheKey>&) -> Result<MemorySegment> {
                        PAIMON_ASSIGN_OR_RAISE(MemorySegment compress_data,
                                               ReadFrom(position, length));
                        if (!decompress_func) {
                            return compress_data;
                        }
                        return decompress_func(compress_data);
                    },
                    [this](const std::shared_ptr<CacheKey>& evicted_key) {
                        blocks_.erase(evicted_key);
                    }));
            auto container = CacheManager::SegmentContainer(segment);
            const auto& result_segment = container.Access();
            blocks_.insert_or_assign(key, container);
            return result_segment;
        }
        return it->second.Access();
    }

    /// Returns the number of entries in the local blocks_ cache.
    size_t BlocksSize() const {
        return blocks_.size();
    }

    /// Returns true if the local blocks_ cache contains an entry for the given position/length.
    bool ContainsBlock(int64_t position, int32_t length, bool is_index) const {
        auto key = CacheKey::ForPosition(file_path_, position, length, is_index);
        return blocks_.find(key) != blocks_.end();
    }

    void Close() {
        // Snapshot blocks_ to avoid iterator invalidation from `InvalidPage` callback.
        auto copied_blocks = blocks_;
        for (const auto& [key, _] : copied_blocks) {
            cache_manager_->InvalidPage(key);
        }
        // Some entries may remain in blocks_ if they were already evicted from the
        // LRU cache (InvalidPage is a no-op for missing keys), so clear explicitly.
        blocks_.clear();
    }

 private:
    Result<MemorySegment> ReadFrom(int64_t offset, int32_t length) {
        PAIMON_RETURN_NOT_OK(in_->Seek(offset, SeekOrigin::FS_SEEK_SET));
        auto segment = MemorySegment::AllocateHeapMemory(length, pool_.get());
        PAIMON_RETURN_NOT_OK(in_->Read(segment.MutableData(), length));
        return segment;
    }

 private:
    std::shared_ptr<MemoryPool> pool_;
    std::string file_path_;
    std::shared_ptr<InputStream> in_;

    std::shared_ptr<CacheManager> cache_manager_;
    std::unordered_map<std::shared_ptr<CacheKey>, CacheManager::SegmentContainer, CacheKeyHash,
                       CacheKeyEqual>
        blocks_;
};
}  // namespace paimon
