/*
 * 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/format/parquet/parquet_writer_builder.h"

#include <optional>
#include <utility>

#include "arrow/util/compression.h"
#include "arrow/util/type_fwd.h"
#include "fmt/format.h"
#include "paimon/common/utils/arrow/arrow_utils.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/options_utils.h"
#include "paimon/core/core_options.h"
#include "paimon/format/parquet/parquet_format_defs.h"
#include "paimon/format/parquet/parquet_format_writer.h"
#include "paimon/status.h"
#include "parquet/properties.h"
#include "parquet/type_fwd.h"

namespace paimon {
class OutputStream;
}  // namespace paimon

namespace paimon::parquet {

Result<std::unique_ptr<FormatWriter>> ParquetWriterBuilder::Build(
    const std::shared_ptr<OutputStream>& out, const std::string& compression) {
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<::parquet::WriterProperties> writer_properties,
                           PrepareWriterProperties(compression));
    PAIMON_ASSIGN_OR_RAISE(uint64_t max_memory_use, OptionsUtils::GetValueFromMap<uint64_t>(
                                                        options_, PARQUET_WRITER_MAX_MEMORY_USE,
                                                        DEFAULT_PARQUET_WRITER_MAX_MEMORY_USE));

    return ParquetFormatWriter::Create(out, schema_, writer_properties, max_memory_use, pool_);
}

Result<std::shared_ptr<::parquet::WriterProperties>> ParquetWriterBuilder::PrepareWriterProperties(
    const std::string& compression) {
    PAIMON_ASSIGN_OR_RAISE(CoreOptions core_options, CoreOptions::FromMap(options_));
    PAIMON_ASSIGN_OR_RAISE(arrow::Compression::type compression_type,
                           ArrowUtils::GetCompressionType(compression));
    ::parquet::WriterProperties::Builder builder;
    builder.memory_pool(pool_.get());
    builder.write_batch_size(batch_size_);
    builder.compression(compression_type);
    PAIMON_ASSIGN_OR_RAISE(
        int64_t row_group_length,
        OptionsUtils::GetValueFromMap<int64_t>(options_, PARQUET_WRITE_MAX_ROW_GROUP_LENGTH,
                                               DEFAULT_PARQUET_WRITE_MAX_ROW_GROUP_LENGTH));
    builder.max_row_group_length(row_group_length);
    builder.enable_store_decimal_as_integer();
    if (arrow::util::Codec::SupportsCompressionLevel(compression_type)) {
        std::string key = CompressLevelOptionsKey(compression_type);
        PAIMON_ASSIGN_OR_RAISE(
            int64_t file_compression_level,
            OptionsUtils::GetValueFromMap<int64_t>(options_, key,
                                                   (key == PARQUET_COMPRESSION_CODEC_ZSTD_LEVEL)
                                                       ? core_options.GetFileCompressionZstdLevel()
                                                       : 1));
        builder.compression_level(file_compression_level);
    }

    PAIMON_ASSIGN_OR_RAISE(int64_t row_group_size, OptionsUtils::GetValueFromMap<int64_t>(
                                                       options_, PARQUET_BLOCK_SIZE,
                                                       ::parquet::DEFAULT_MAX_ROW_GROUP_SIZE));
    builder.max_row_group_size(row_group_size);

    PAIMON_ASSIGN_OR_RAISE(int64_t page_size,
                           OptionsUtils::GetValueFromMap<int64_t>(options_, PARQUET_PAGE_SIZE,
                                                                  ::parquet::kDefaultDataPageSize));
    builder.data_pagesize(page_size);
    PAIMON_ASSIGN_OR_RAISE(bool enable_dictionary, OptionsUtils::GetValueFromMap<bool>(
                                                       options_, PARQUET_ENABLE_DICTIONARY,
                                                       ::parquet::DEFAULT_IS_DICTIONARY_ENABLED));
    enable_dictionary ? builder.enable_dictionary() : builder.disable_dictionary();
    PAIMON_ASSIGN_OR_RAISE(
        int64_t dictionary_page_size,
        OptionsUtils::GetValueFromMap<int64_t>(options_, PARQUET_DICTIONARY_PAGE_SIZE,
                                               ::parquet::DEFAULT_DICTIONARY_PAGE_SIZE_LIMIT));
    builder.dictionary_pagesize_limit(dictionary_page_size);
    PAIMON_ASSIGN_OR_RAISE(std::string writer_version,
                           OptionsUtils::GetValueFromMap<std::string>(
                               options_, PARQUET_WRITER_VERSION, std::string("PARQUET_2_0")));
    PAIMON_ASSIGN_OR_RAISE(::parquet::ParquetVersion::type version,
                           ConvertWriterVersion(writer_version));
    builder.version(version);

    // Enable writing page index (ColumnIndex + OffsetIndex) for page-level filtering
    PAIMON_ASSIGN_OR_RAISE(bool enable_page_index, OptionsUtils::GetValueFromMap<bool>(
                                                       options_, PARQUET_WRITE_ENABLE_PAGE_INDEX,
                                                       DEFAULT_PARQUET_WRITE_ENABLE_PAGE_INDEX));
    if (enable_page_index) {
        builder.enable_write_page_index();
    }

    return builder.build();
}

Result<::parquet::ParquetVersion::type> ParquetWriterBuilder::ConvertWriterVersion(
    const std::string& writer_version) {
    if (writer_version == "PARQUET_1_0" || writer_version == "v1") {
        return ::parquet::ParquetVersion::type::PARQUET_1_0;
    } else if (writer_version == "PARQUET_2_0" || writer_version == "v2") {
        return ::parquet::ParquetVersion::type::PARQUET_2_6;
    }
    return Status::Invalid(fmt::format("Unknown writer version {}", writer_version));
}

std::string ParquetWriterBuilder::CompressLevelOptionsKey(::arrow::Compression::type compression) {
    switch (compression) {
        case ::arrow::Compression::GZIP:
            return PARQUET_COMPRESSION_CODEC_ZLIB_LEVEL;
        case ::arrow::Compression::BROTLI:
            return PARQUET_COMPRESSION_CODEC_BROTLI_LEVEL;
        case ::arrow::Compression::ZSTD:
            return PARQUET_COMPRESSION_CODEC_ZSTD_LEVEL;
        default:
            return "";
    }
}

}  // namespace paimon::parquet
