/*
 * 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.
 */

#include "paimon/core/mergetree/spill_writer.h"

#include "paimon/common/utils/arrow/arrow_output_stream_adapter.h"
#include "paimon/common/utils/arrow/arrow_utils.h"
#include "paimon/common/utils/arrow/mem_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/scope_guard.h"
#include "paimon/core/mergetree/spill_channel_manager.h"

namespace paimon {

SpillWriter::SpillWriter(const std::shared_ptr<FileSystem>& fs,
                         const std::shared_ptr<arrow::Schema>& schema,
                         const std::shared_ptr<FileIOChannel::Enumerator>& channel_enumerator,
                         const std::shared_ptr<SpillChannelManager>& spill_channel_manager,
                         const std::string& compression, int32_t compression_level,
                         bool use_threads, const std::shared_ptr<MemoryPool>& pool)
    : fs_(fs),
      schema_(schema),
      channel_enumerator_(channel_enumerator),
      spill_channel_manager_(spill_channel_manager),
      compression_(compression),
      compression_level_(compression_level),
      use_threads_(use_threads),
      arrow_pool_(GetArrowPool(pool)) {}

Result<std::unique_ptr<SpillWriter>> SpillWriter::Create(
    const std::shared_ptr<FileSystem>& fs, const std::shared_ptr<arrow::Schema>& schema,
    const std::shared_ptr<FileIOChannel::Enumerator>& channel_enumerator,
    const std::shared_ptr<SpillChannelManager>& spill_channel_manager,
    const std::string& compression, int32_t compression_level, bool use_threads,
    const std::shared_ptr<MemoryPool>& pool) {
    std::unique_ptr<SpillWriter> writer(new SpillWriter(fs, schema, channel_enumerator,
                                                        spill_channel_manager, compression,
                                                        compression_level, use_threads, pool));
    PAIMON_RETURN_NOT_OK(writer->Open());
    return writer;
}

Status SpillWriter::Open() {
    channel_id_ = channel_enumerator_->Next();
    auto ipc_write_options = arrow::ipc::IpcWriteOptions::Defaults();
    ipc_write_options.memory_pool = arrow_pool_.get();
    ipc_write_options.use_threads = use_threads_;
    auto cleanup_guard = ScopeGuard([&]() {
        arrow_writer_.reset();
        arrow_output_stream_adapter_.reset();
        if (out_stream_) {
            [[maybe_unused]] auto status = out_stream_->Close();
            out_stream_.reset();
        }
        if (!channel_id_.GetPath().empty()) {
            [[maybe_unused]] auto status = fs_->Delete(channel_id_.GetPath());
        }
    });
    PAIMON_ASSIGN_OR_RAISE(arrow::Compression::type arrow_compression,
                           ArrowUtils::GetCompressionType(compression_));
    if (!arrow::util::Codec::SupportsCompressionLevel(arrow_compression)) {
        compression_level_ = arrow::util::Codec::UseDefaultCompressionLevel();
    }
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
        ipc_write_options.codec, arrow::util::Codec::Create(arrow_compression, compression_level_));
    PAIMON_ASSIGN_OR_RAISE(out_stream_, fs_->Create(channel_id_.GetPath(), /*overwrite=*/false));
    arrow_output_stream_adapter_ = std::make_shared<ArrowOutputStreamAdapter>(out_stream_);
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
        arrow_writer_,
        arrow::ipc::MakeFileWriter(arrow_output_stream_adapter_, schema_, ipc_write_options));
    spill_channel_manager_->AddChannel(channel_id_);
    cleanup_guard.Release();
    return Status::OK();
}

Status SpillWriter::WriteBatch(const std::shared_ptr<arrow::RecordBatch>& batch) {
    PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow_writer_->WriteRecordBatch(*batch));
    return Status::OK();
}

Status SpillWriter::Close() {
    if (closed_) {
        return Status::OK();
    }
    if (arrow_writer_) {
        PAIMON_RETURN_NOT_OK_FROM_ARROW(arrow_writer_->Close());
    }
    if (out_stream_) {
        PAIMON_RETURN_NOT_OK(out_stream_->Close());
    }
    closed_ = true;
    return Status::OK();
}

Result<int64_t> SpillWriter::GetFileSize() const {
    if (channel_id_.GetPath().empty()) {
        return Status::Invalid("spill writer has no channel id");
    }
    if (!closed_ && arrow_output_stream_adapter_ != nullptr) {
        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(int64_t file_size, arrow_output_stream_adapter_->Tell());
        return file_size;
    }
    PAIMON_ASSIGN_OR_RAISE(std::unique_ptr<FileStatus> file_status,
                           fs_->GetFileStatus(channel_id_.GetPath()));
    return file_status->GetLen();
}

const FileIOChannel::ID& SpillWriter::GetChannelId() const {
    return channel_id_;
}

}  // namespace paimon
