/*
 * 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/stats/simple_stats_evolution.h"

#include <cassert>
#include <cstddef>
#include <string_view>

#include "paimon/common/data/binary_array.h"
#include "paimon/common/data/binary_array_writer.h"
#include "paimon/common/data/binary_row.h"
#include "paimon/common/data/binary_string.h"
#include "paimon/common/data/generic_row.h"
#include "paimon/common/data/internal_array.h"
#include "paimon/common/utils/object_utils.h"
#include "paimon/common/utils/projected_array.h"
#include "paimon/common/utils/projected_row.h"
#include "paimon/core/casting/casted_row.h"
#include "paimon/core/stats/simple_stats.h"
#include "paimon/core/utils/field_mapping.h"
#include "paimon/data/decimal.h"
#include "paimon/data/timestamp.h"
#include "paimon/status.h"

namespace paimon {
class Bytes;
class CastExecutor;
class InternalMap;
class InternalRow;
class MemoryPool;

SimpleStatsEvolution::SimpleStatsEvolution(const std::vector<DataField>& data_fields,
                                           const std::vector<DataField>& table_fields,
                                           bool need_mapping,
                                           const std::shared_ptr<MemoryPool>& pool)
    : need_mapping_(need_mapping), data_fields_(data_fields), table_fields_(table_fields) {
    empty_values_ = std::make_shared<GenericRow>(data_fields.size());
    empty_null_counts_ = std::make_shared<BinaryArray>();
    BinaryArrayWriter array_writer(empty_null_counts_.get(), data_fields.size(),
                                   /*element_size=*/sizeof(int64_t), pool.get());
    for (size_t i = 0; i < data_fields.size(); ++i) {
        array_writer.SetNullAt(i);
    }
    array_writer.Complete();

    for (size_t i = 0; i < data_fields.size(); i++) {
        const auto& data_field = data_fields[i];
        id_to_data_fields_.emplace(data_field.Id(), std::make_pair(i, data_field));
    }
    for (const auto& field : table_fields) {
        name_to_table_fields_.emplace(field.Name(), field);
    }
}

class NullCountsEvoArray : public InternalArray {
 public:
    NullCountsEvoArray(const std::shared_ptr<InternalArray>& array,
                       const std::vector<int32_t>& mapping, int64_t not_found_value)
        : array_(array), mapping_(mapping), not_found_value_(not_found_value) {
        assert(array_);
    }
    int32_t Size() const override {
        return mapping_.size();
    }
    bool IsNullAt(int32_t pos) const override {
        assert(static_cast<size_t>(pos) < mapping_.size());
        if (mapping_[pos] < 0) {
            return false;
        }
        return array_->IsNullAt(mapping_[pos]);
    }
    int64_t GetLong(int32_t pos) const override {
        if (mapping_[pos] < 0) {
            return not_found_value_;
        }
        return array_->GetLong(mapping_[pos]);
    }

 private:
    // ============================= Unsupported Methods ================================
    bool GetBoolean(int32_t pos) const override;
    char GetByte(int32_t pos) const override;
    int16_t GetShort(int32_t pos) const override;
    int32_t GetInt(int32_t pos) const override;
    int32_t GetDate(int32_t pos) const override;
    float GetFloat(int32_t pos) const override;
    double GetDouble(int32_t pos) const override;
    BinaryString GetString(int32_t pos) const override;
    std::string_view GetStringView(int32_t pos) const override;
    Decimal GetDecimal(int32_t pos, int32_t precision, int32_t scale) const override;
    Timestamp GetTimestamp(int32_t pos, int32_t precision) const override;
    std::shared_ptr<Bytes> GetBinary(int32_t pos) const override;
    std::shared_ptr<InternalArray> GetArray(int32_t pos) const override;
    std::shared_ptr<InternalMap> GetMap(int32_t pos) const override;
    std::shared_ptr<InternalRow> GetRow(int32_t pos, int32_t num_fields) const override;
    Result<std::vector<char>> ToBooleanArray() const override;
    Result<std::vector<char>> ToByteArray() const override;
    Result<std::vector<int16_t>> ToShortArray() const override;
    Result<std::vector<int32_t>> ToIntArray() const override;
    Result<std::vector<int64_t>> ToLongArray() const override;
    Result<std::vector<float>> ToFloatArray() const override;
    Result<std::vector<double>> ToDoubleArray() const override;

 private:
    std::shared_ptr<InternalArray> array_;
    std::vector<int32_t> mapping_;
    int64_t not_found_value_;
};

Result<SimpleStatsEvolution::EvolutionStats> SimpleStatsEvolution::Evolution(
    const SimpleStats& stats, int64_t row_count,
    const std::optional<std::vector<std::string>>& dense_fields) {
    SimpleStatsEvolution::EvolutionStats evolution_stats(
        std::make_shared<BinaryRow>(stats.MinValues()),
        std::make_shared<BinaryRow>(stats.MaxValues()),
        std::make_shared<BinaryArray>(stats.NullCounts()));
    auto& min_values = evolution_stats.min_values;
    auto& max_values = evolution_stats.max_values;
    auto& null_counts = evolution_stats.null_counts;
    if (dense_fields != std::nullopt && dense_fields.value().empty()) {
        // optimize for empty dense fields
        min_values = empty_values_;
        max_values = empty_values_;
        null_counts = empty_null_counts_;
    } else if (dense_fields != std::nullopt) {
        // create dense index mapping
        std::vector<int32_t> data_idx_to_dense_idx;
        std::optional<std::vector<int32_t>> cached_dense_idx =
            dense_fields_mapping_.Find(dense_fields.value());
        if (!cached_dense_idx) {
            std::map<std::string, int32_t> field_name_to_dense_idx =
                ObjectUtils::CreateIdentifierToIndexMap(dense_fields.value());
            data_idx_to_dense_idx.resize(data_fields_.size(), -1);
            for (size_t i = 0; i < data_fields_.size(); ++i) {
                auto iter = field_name_to_dense_idx.find(data_fields_[i].Name());
                if (iter != field_name_to_dense_idx.end()) {
                    data_idx_to_dense_idx[i] = iter->second;
                }
            }
            dense_fields_mapping_.Insert(dense_fields.value(), data_idx_to_dense_idx);
        } else {
            data_idx_to_dense_idx = std::move(cached_dense_idx).value();
        }

        min_values = std::make_shared<ProjectedRow>(min_values, data_idx_to_dense_idx);
        max_values = std::make_shared<ProjectedRow>(max_values, data_idx_to_dense_idx);
        null_counts = std::make_shared<ProjectedArray>(null_counts, data_idx_to_dense_idx);
    }

    if (!need_mapping_) {
        return evolution_stats;
    }
    return MappingStats(row_count, evolution_stats);
}

Result<SimpleStatsEvolution::EvolutionStats> SimpleStatsEvolution::MappingStats(
    int64_t row_count, const SimpleStatsEvolution::EvolutionStats& src_stats) const {
    SimpleStatsEvolution::EvolutionStats evolution_stats = src_stats;
    auto& min_values = evolution_stats.min_values;
    auto& max_values = evolution_stats.max_values;
    auto& null_counts = evolution_stats.null_counts;

    std::vector<int32_t> table_idx_to_data_idx(table_fields_.size(), -1);
    std::vector<DataField> projected_data_fields;
    projected_data_fields.reserve(table_fields_.size());
    std::map<int32_t, int32_t> field_id_to_data_idx = ObjectUtils::CreateIdentifierToIndexMap(
        data_fields_, [](const DataField& field) -> int32_t { return field.Id(); });
    for (size_t i = 0; i < table_fields_.size(); ++i) {
        auto iter = field_id_to_data_idx.find(table_fields_[i].Id());
        if (iter != field_id_to_data_idx.end()) {
            table_idx_to_data_idx[i] = iter->second;
            projected_data_fields.push_back(data_fields_[iter->second]);
        } else {
            projected_data_fields.push_back(table_fields_[i]);
        }
    }
    min_values = std::make_shared<ProjectedRow>(min_values, table_idx_to_data_idx);
    max_values = std::make_shared<ProjectedRow>(max_values, table_idx_to_data_idx);
    null_counts =
        std::make_shared<NullCountsEvoArray>(null_counts, table_idx_to_data_idx, row_count);

    // cast mapping
    PAIMON_ASSIGN_OR_RAISE(
        std::vector<std::shared_ptr<CastExecutor>> cast_executors,
        FieldMappingBuilder::CreateDataCastExecutors(table_fields_, projected_data_fields));

    bool need_cast = false;
    for (const auto& executor : cast_executors) {
        if (executor) {
            need_cast = true;
            break;
        }
    }
    if (need_cast) {
        PAIMON_ASSIGN_OR_RAISE(min_values, CastedRow::Create(cast_executors, projected_data_fields,
                                                             table_fields_, min_values));
        PAIMON_ASSIGN_OR_RAISE(max_values, CastedRow::Create(cast_executors, projected_data_fields,
                                                             table_fields_, max_values));
    }
    return evolution_stats;
}

// ============================= Unsupported Methods ================================
bool NullCountsEvoArray::GetBoolean(int32_t pos) const {
    assert(false);
    return array_->GetBoolean(mapping_[pos]);
}

char NullCountsEvoArray::GetByte(int32_t pos) const {
    assert(false);
    return array_->GetByte(mapping_[pos]);
}

int16_t NullCountsEvoArray::GetShort(int32_t pos) const {
    assert(false);
    return array_->GetShort(mapping_[pos]);
}

int32_t NullCountsEvoArray::GetInt(int32_t pos) const {
    assert(false);
    return array_->GetInt(mapping_[pos]);
}

int32_t NullCountsEvoArray::GetDate(int32_t pos) const {
    return NullCountsEvoArray::GetInt(pos);
}

float NullCountsEvoArray::GetFloat(int32_t pos) const {
    assert(false);
    return array_->GetFloat(mapping_[pos]);
}

double NullCountsEvoArray::GetDouble(int32_t pos) const {
    assert(false);
    return array_->GetDouble(mapping_[pos]);
}

BinaryString NullCountsEvoArray::GetString(int32_t pos) const {
    assert(false);
    return array_->GetString(mapping_[pos]);
}

std::string_view NullCountsEvoArray::GetStringView(int32_t pos) const {
    assert(false);
    return array_->GetStringView(mapping_[pos]);
}

Decimal NullCountsEvoArray::GetDecimal(int32_t pos, int32_t precision, int32_t scale) const {
    assert(false);
    return array_->GetDecimal(mapping_[pos], precision, scale);
}

Timestamp NullCountsEvoArray::GetTimestamp(int32_t pos, int32_t precision) const {
    assert(false);
    return array_->GetTimestamp(mapping_[pos], precision);
}

std::shared_ptr<Bytes> NullCountsEvoArray::GetBinary(int32_t pos) const {
    assert(false);
    return array_->GetBinary(mapping_[pos]);
}

std::shared_ptr<InternalArray> NullCountsEvoArray::GetArray(int32_t pos) const {
    assert(false);
    return array_->GetArray(mapping_[pos]);
}

std::shared_ptr<InternalMap> NullCountsEvoArray::GetMap(int32_t pos) const {
    assert(false);
    return array_->GetMap(mapping_[pos]);
}

std::shared_ptr<InternalRow> NullCountsEvoArray::GetRow(int32_t pos, int32_t num_fields) const {
    assert(false);
    return array_->GetRow(mapping_[pos], num_fields);
}

Result<std::vector<char>> NullCountsEvoArray::ToBooleanArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to boolean array");
}
Result<std::vector<char>> NullCountsEvoArray::ToByteArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to byte array");
}
Result<std::vector<int16_t>> NullCountsEvoArray::ToShortArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to short array");
}
Result<std::vector<int32_t>> NullCountsEvoArray::ToIntArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to int array");
}
Result<std::vector<int64_t>> NullCountsEvoArray::ToLongArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to long array");
}
Result<std::vector<float>> NullCountsEvoArray::ToFloatArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to float array");
}
Result<std::vector<double>> NullCountsEvoArray::ToDoubleArray() const {
    return Status::Invalid("NullCountsEvoArray do not support convert to double array");
}

}  // namespace paimon
