/*
 * 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/common/utils/binary_row_partition_computer.h"

#include <algorithm>
#include <cstddef>

#include "arrow/type.h"
#include "fmt/format.h"
#include "fmt/ranges.h"
#include "paimon/common/data/binary_row.h"
#include "paimon/common/data/binary_row_writer.h"
#include "paimon/common/utils/string_utils.h"
#include "paimon/macros.h"
#include "paimon/result.h"
#include "paimon/status.h"

namespace paimon {
class MemoryPool;

BinaryRowPartitionComputer::BinaryRowPartitionComputer(
    const std::vector<std::string>& partition_keys, const std::shared_ptr<arrow::Schema>& schema,
    const std::string& default_part_value,
    const std::vector<PartitionConverter>& partition_converters,
    const std::shared_ptr<MemoryPool>& memory_pool)
    : memory_pool_(memory_pool),
      partition_keys_(partition_keys),
      schema_(schema),
      default_part_value_(default_part_value),
      partition_converters_(partition_converters) {}

Result<std::unique_ptr<BinaryRowPartitionComputer>> BinaryRowPartitionComputer::Create(
    const std::vector<std::string>& partition_keys, const std::shared_ptr<arrow::Schema>& schema,
    const std::string& default_part_value, bool legacy_partition_name_enabled,
    const std::shared_ptr<MemoryPool>& memory_pool) {
    if (PAIMON_UNLIKELY(schema == nullptr)) {
        return Status::Invalid(
            "create binary row partition computer failed, schema is null pointer.");
    }
    if (PAIMON_UNLIKELY(memory_pool == nullptr)) {
        return Status::Invalid(
            "create binary row partition computer failed, memory pool is null pointer.");
    }
    std::vector<PartitionConverter> partition_converters;
    for (const auto& partition_key : partition_keys) {
        PAIMON_ASSIGN_OR_RAISE(arrow::Type::type type_id,
                               GetTypeFromArrowSchema(schema, partition_key));
        PAIMON_ASSIGN_OR_RAISE(
            DataConverterUtils::StrToBinaryRowConverter converter,
            DataConverterUtils::CreateDataToBinaryRowConverter(type_id, memory_pool.get()));
        PAIMON_ASSIGN_OR_RAISE(DataConverterUtils::BinaryRowFieldToStrConverter reconverter,
                               DataConverterUtils::CreateBinaryRowFieldToStringConverter(
                                   type_id, legacy_partition_name_enabled));
        partition_converters.emplace_back(partition_key, std::move(converter),
                                          std::move(reconverter));
    }
    return std::unique_ptr<BinaryRowPartitionComputer>(new BinaryRowPartitionComputer(
        partition_keys, schema, default_part_value, partition_converters, memory_pool));
}

Result<BinaryRow> BinaryRowPartitionComputer::ToBinaryRow(
    const std::map<std::string, std::string>& partition) const {
    BinaryRow binary_row(partition_converters_.size());
    BinaryRowWriter writer(&binary_row, /*initial_size=*/0, memory_pool_.get());
    for (size_t field_idx = 0; field_idx < partition_converters_.size(); field_idx++) {
        const auto& partition_extractor = partition_converters_[field_idx];
        const auto& partition_key = partition_extractor.partition_key;
        const auto& to_binary_row = partition_extractor.converter;
        auto input_iter = partition.find(partition_key);
        if (input_iter == partition.end()) {
            return Status::Invalid(
                fmt::format("can not find partition key '{}' in input partition '{}'",
                            partition_key, partition));
        }
        const auto& value_str = input_iter->second;
        if (value_str == default_part_value_) {
            // TODO(yonghao.fyh): when support decimal/ timestamp in partition, use
            // WriteTimestamp(null) for non compact precision
            writer.SetNullAt(field_idx);
        } else {
            PAIMON_RETURN_NOT_OK(to_binary_row(value_str, field_idx, &writer));
        }
    }
    writer.Complete();
    return binary_row;
}

Result<std::vector<std::pair<std::string, std::string>>>
BinaryRowPartitionComputer::GeneratePartitionVector(const BinaryRow& partition) const {
    if (static_cast<size_t>(partition.GetFieldCount()) != partition_converters_.size()) {
        return Status::Invalid(fmt::format(
            "partition binary row field count {} not match with partition converter size {}",
            partition.GetFieldCount(), partition_converters_.size()));
    }
    std::vector<std::pair<std::string, std::string>> result;
    for (size_t field_idx = 0; field_idx < partition_converters_.size(); field_idx++) {
        const auto& partition_extractor = partition_converters_[field_idx];
        const auto& partition_key = partition_extractor.partition_key;
        const auto& to_str = partition_extractor.reconverter;
        if (partition.IsNullAt(field_idx)) {
            result.emplace_back(partition_key, default_part_value_);
        } else {
            PAIMON_ASSIGN_OR_RAISE(std::string partition_field_str, to_str(partition, field_idx));
            if (StringUtils::IsNullOrWhitespaceOnly(partition_field_str)) {
                partition_field_str = default_part_value_;
            }
            result.emplace_back(partition_key, partition_field_str);
        }
    }
    return result;
}

Result<arrow::Type::type> BinaryRowPartitionComputer::GetTypeFromArrowSchema(
    const std::shared_ptr<arrow::Schema>& schema, const std::string& field_name) {
    auto field = schema->GetFieldByName(field_name);
    if (field == nullptr) {
        return Status::Invalid(
            fmt::format("field {} not in schema {}", field_name, schema->ToString()));
    }
    return field->type()->id();
}

Result<std::string> BinaryRowPartitionComputer::PartToSimpleString(
    const std::shared_ptr<arrow::Schema>& partition_type, const BinaryRow& partition,
    const std::string& delimiter, int32_t max_length, bool legacy_partition_name_enabled) {
    std::vector<DataConverterUtils::BinaryRowFieldToStrConverter> partition_converters;
    partition_converters.reserve(partition_type->num_fields());
    for (const auto& field : partition_type->fields()) {
        PAIMON_ASSIGN_OR_RAISE(DataConverterUtils::BinaryRowFieldToStrConverter converter,
                               DataConverterUtils::CreateBinaryRowFieldToStringConverter(
                                   field->type()->id(), legacy_partition_name_enabled));
        partition_converters.emplace_back(converter);
    }
    std::vector<std::string> partition_vec;
    partition_vec.reserve(partition_converters.size());
    for (size_t field_idx = 0; field_idx < partition_converters.size(); field_idx++) {
        const auto& to_str = partition_converters[field_idx];
        if (partition.IsNullAt(field_idx)) {
            partition_vec.push_back("null");
        } else {
            PAIMON_ASSIGN_OR_RAISE(std::string partition_field_str, to_str(partition, field_idx));
            partition_vec.push_back(partition_field_str);
        }
    }
    return fmt::format("{}", fmt::join(partition_vec, delimiter)).substr(0, max_length);
}
}  // namespace paimon
