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

/* This file is based on source code from the Spark Project (http://spark.apache.org/), licensed
 * by the Apache Software Foundation (ASF) under the Apache License, Version 2.0. See the NOTICE
 * file distributed with this work for additional information regarding copyright ownership. */

#include "paimon/common/data/variant/generic_variant.h"

#include <cmath>
#include <cstring>
#include <utility>

#include "arrow/util/base64.h"
#include "fmt/format.h"
#include "paimon/common/data/variant/variant_builder.h"
#include "paimon/common/data/variant/variant_defs.h"
#include "paimon/common/data/variant/variant_json_utils.h"

namespace paimon {

namespace {

Status ToJsonImpl(std::string_view value, std::string_view metadata, int32_t pos,
                  const std::string& zone_id, std::string* out) {
    PAIMON_ASSIGN_OR_RAISE(VariantValueType type, VariantBinaryUtil::GetType(value, pos));
    switch (type) {
        case VariantValueType::kObject: {
            PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info,
                                   VariantBinaryUtil::GetObjectInfo(value, pos));
            out->push_back('{');
            for (int32_t i = 0; i < info.num_elements; ++i) {
                PAIMON_ASSIGN_OR_RAISE(int32_t id,
                                       VariantBinaryUtil::ReadUnsigned(
                                           value, info.id_start + info.id_size * i, info.id_size));
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t offset,
                    VariantBinaryUtil::ReadUnsigned(value, info.offset_start + info.offset_size * i,
                                                    info.offset_size));
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t element_pos,
                    VariantBinaryUtil::CheckedElementPos(info.data_start, offset, value.size()));
                if (i != 0) {
                    out->push_back(',');
                }
                PAIMON_ASSIGN_OR_RAISE(std::string_view key,
                                       VariantBinaryUtil::GetMetadataKey(metadata, id));
                VariantJsonUtils::AppendEscapedJson(key, out);
                out->push_back(':');
                PAIMON_RETURN_NOT_OK(ToJsonImpl(value, metadata, element_pos, zone_id, out));
            }
            out->push_back('}');
            return Status::OK();
        }
        case VariantValueType::kArray: {
            PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info,
                                   VariantBinaryUtil::GetArrayInfo(value, pos));
            out->push_back('[');
            for (int32_t i = 0; i < info.num_elements; ++i) {
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t offset,
                    VariantBinaryUtil::ReadUnsigned(value, info.offset_start + info.offset_size * i,
                                                    info.offset_size));
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t element_pos,
                    VariantBinaryUtil::CheckedElementPos(info.data_start, offset, value.size()));
                if (i != 0) {
                    out->push_back(',');
                }
                PAIMON_RETURN_NOT_OK(ToJsonImpl(value, metadata, element_pos, zone_id, out));
            }
            out->push_back(']');
            return Status::OK();
        }
        case VariantValueType::kNull:
            out->append("null");
            return Status::OK();
        case VariantValueType::kBoolean: {
            PAIMON_ASSIGN_OR_RAISE(bool b, VariantBinaryUtil::GetBoolean(value, pos));
            out->append(b ? "true" : "false");
            return Status::OK();
        }
        case VariantValueType::kLong: {
            PAIMON_ASSIGN_OR_RAISE(int64_t l, VariantBinaryUtil::GetLong(value, pos));
            out->append(std::to_string(l));
            return Status::OK();
        }
        case VariantValueType::kString: {
            PAIMON_ASSIGN_OR_RAISE(std::string_view s, VariantBinaryUtil::GetString(value, pos));
            VariantJsonUtils::AppendEscapedJson(s, out);
            return Status::OK();
        }
        case VariantValueType::kDouble: {
            PAIMON_ASSIGN_OR_RAISE(double d, VariantBinaryUtil::GetDouble(value, pos));
            std::string repr = VariantJsonUtils::JavaDoubleToString(d);
            if (std::isfinite(d)) {
                out->append(repr);
            } else {
                out->push_back('"');
                out->append(repr);
                out->push_back('"');
            }
            return Status::OK();
        }
        case VariantValueType::kDecimal: {
            PAIMON_ASSIGN_OR_RAISE(VariantDecimal d, VariantBinaryUtil::GetDecimal(value, pos));
            out->append(d.ToPlainString());
            return Status::OK();
        }
        case VariantValueType::kDate: {
            PAIMON_ASSIGN_OR_RAISE(int64_t days, VariantBinaryUtil::GetLong(value, pos));
            out->push_back('"');
            out->append(VariantJsonUtils::DateToString(static_cast<int32_t>(days)));
            out->push_back('"');
            return Status::OK();
        }
        case VariantValueType::kTimestamp: {
            PAIMON_ASSIGN_OR_RAISE(int64_t micros, VariantBinaryUtil::GetLong(value, pos));
            PAIMON_ASSIGN_OR_RAISE(int32_t offset_seconds,
                                   VariantJsonUtils::GetZoneOffsetSeconds(zone_id, micros));
            out->push_back('"');
            out->append(VariantJsonUtils::TimestampToString(micros, offset_seconds, true));
            out->push_back('"');
            return Status::OK();
        }
        case VariantValueType::kTimestampNtz: {
            PAIMON_ASSIGN_OR_RAISE(int64_t micros, VariantBinaryUtil::GetLong(value, pos));
            out->push_back('"');
            out->append(VariantJsonUtils::TimestampToString(micros, 0, false));
            out->push_back('"');
            return Status::OK();
        }
        case VariantValueType::kFloat: {
            PAIMON_ASSIGN_OR_RAISE(float f, VariantBinaryUtil::GetFloat(value, pos));
            std::string repr = VariantJsonUtils::JavaFloatToString(f);
            if (std::isfinite(f)) {
                out->append(repr);
            } else {
                out->push_back('"');
                out->append(repr);
                out->push_back('"');
            }
            return Status::OK();
        }
        case VariantValueType::kBinary: {
            PAIMON_ASSIGN_OR_RAISE(std::string_view binary,
                                   VariantBinaryUtil::GetBinary(value, pos));
            out->push_back('"');
            out->append(arrow::util::base64_encode(binary));
            out->push_back('"');
            return Status::OK();
        }
        case VariantValueType::kUuid: {
            PAIMON_ASSIGN_OR_RAISE(std::string_view uuid, VariantBinaryUtil::GetUuid(value, pos));
            out->push_back('"');
            out->append(VariantBinaryUtil::UuidToString(uuid));
            out->push_back('"');
            return Status::OK();
        }
    }
    return VariantBinaryUtil::MalformedVariant("unknown variant value type in JSON rendering");
}

}  // namespace

GenericVariant::GenericVariant(std::shared_ptr<Bytes> value, std::shared_ptr<Bytes> metadata,
                               int32_t pos)
    : value_(std::move(value)), metadata_(std::move(metadata)), pos_(pos) {}

Result<std::shared_ptr<GenericVariant>> GenericVariant::Create(std::shared_ptr<Bytes> value,
                                                               std::shared_ptr<Bytes> metadata) {
    if (!value || !metadata) {
        return Status::Invalid("variant value and metadata must not be null");
    }
    // There is currently only one allowed version.
    if (metadata->size() < 1 || (static_cast<uint8_t>((*metadata)[0]) &
                                 VariantDefs::kVersionMask) != VariantDefs::kVersion) {
        return VariantBinaryUtil::MalformedVariant("unsupported variant metadata version");
    }
    // Don't attempt to use a Variant larger than 128 MiB. We'll never produce one, and it risks
    // memory instability.
    if (metadata->size() > static_cast<size_t>(VariantDefs::kSizeLimit) ||
        value->size() > static_cast<size_t>(VariantDefs::kSizeLimit)) {
        return VariantBinaryUtil::VariantConstructorSizeLimit();
    }
    return std::shared_ptr<GenericVariant>(
        new GenericVariant(std::move(value), std::move(metadata), 0));
}

Result<std::shared_ptr<GenericVariant>> GenericVariant::Create(
    std::string_view value, std::string_view metadata, const std::shared_ptr<MemoryPool>& pool) {
    // Reject over-limit inputs before allocating and copying them.
    if (value.size() > static_cast<size_t>(VariantDefs::kSizeLimit) ||
        metadata.size() > static_cast<size_t>(VariantDefs::kSizeLimit)) {
        return VariantBinaryUtil::VariantConstructorSizeLimit();
    }
    std::shared_ptr<Bytes> value_bytes = Bytes::AllocateBytes(value.size(), pool.get());
    if (!value.empty()) {
        std::memcpy(value_bytes->data(), value.data(), value.size());
    }
    std::shared_ptr<Bytes> metadata_bytes = Bytes::AllocateBytes(metadata.size(), pool.get());
    if (!metadata.empty()) {
        // An empty (malformed) metadata view may carry a null data pointer; the size check
        // keeps memcpy away from it.
        std::memcpy(metadata_bytes->data(), metadata.data(), metadata.size());
    }
    return Create(std::move(value_bytes), std::move(metadata_bytes));
}

Result<std::shared_ptr<GenericVariant>> GenericVariant::FromJson(
    std::string_view json, const std::shared_ptr<MemoryPool>& pool) {
    return VariantBuilder::ParseJson(json, /*allow_duplicate_keys=*/false, pool);
}

Result<std::string_view> GenericVariant::Value() const {
    std::string_view raw = RawValue();
    if (pos_ == 0) {
        return raw;
    }
    PAIMON_ASSIGN_OR_RAISE(int32_t size, VariantBinaryUtil::ValueSize(raw, pos_));
    PAIMON_RETURN_NOT_OK(
        VariantBinaryUtil::CheckIndex(pos_ + size - 1, static_cast<int32_t>(raw.size())));
    return raw.substr(pos_, size);
}

std::string_view GenericVariant::RawValue() const {
    return {value_->data(), value_->size()};
}

std::string_view GenericVariant::Metadata() const {
    return {metadata_->data(), metadata_->size()};
}

int64_t GenericVariant::SizeInBytes() const {
    return static_cast<int64_t>(value_->size()) + static_cast<int64_t>(metadata_->size());
}

Result<std::string> GenericVariant::ToJson(const std::string& zone_id) const {
    std::string result;
    PAIMON_RETURN_NOT_OK(ToJsonImpl(RawValue(), Metadata(), pos_, zone_id, &result));
    return result;
}

Result<VariantValueType> GenericVariant::GetType() const {
    return VariantBinaryUtil::GetType(RawValue(), pos_);
}

Result<int32_t> GenericVariant::GetTypeInfo() const {
    return VariantBinaryUtil::GetTypeInfo(RawValue(), pos_);
}

Result<bool> GenericVariant::GetBoolean() const {
    return VariantBinaryUtil::GetBoolean(RawValue(), pos_);
}

Result<int64_t> GenericVariant::GetLong() const {
    return VariantBinaryUtil::GetLong(RawValue(), pos_);
}

Result<double> GenericVariant::GetDouble() const {
    return VariantBinaryUtil::GetDouble(RawValue(), pos_);
}

Result<VariantDecimal> GenericVariant::GetDecimal() const {
    return VariantBinaryUtil::GetDecimal(RawValue(), pos_);
}

Result<float> GenericVariant::GetFloat() const {
    return VariantBinaryUtil::GetFloat(RawValue(), pos_);
}

Result<std::string_view> GenericVariant::GetBinary() const {
    return VariantBinaryUtil::GetBinary(RawValue(), pos_);
}

Result<std::string_view> GenericVariant::GetString() const {
    return VariantBinaryUtil::GetString(RawValue(), pos_);
}

Result<std::string_view> GenericVariant::GetUuid() const {
    return VariantBinaryUtil::GetUuid(RawValue(), pos_);
}

Result<int32_t> GenericVariant::ObjectSize() const {
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info,
                           VariantBinaryUtil::GetObjectInfo(RawValue(), pos_));
    return info.num_elements;
}

std::shared_ptr<GenericVariant> GenericVariant::SubVariant(int32_t pos) const {
    return std::shared_ptr<GenericVariant>(new GenericVariant(value_, metadata_, pos));
}

Result<std::shared_ptr<GenericVariant>> GenericVariant::GetFieldByKey(std::string_view key) const {
    std::string_view raw = RawValue();
    std::string_view metadata = Metadata();
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info,
                           VariantBinaryUtil::GetObjectInfo(raw, pos_));
    // Use linear search for a short list. Switch to binary search when the length reaches
    // `kBinarySearchThreshold`.
    if (info.num_elements < VariantDefs::kBinarySearchThreshold) {
        for (int32_t i = 0; i < info.num_elements; ++i) {
            PAIMON_ASSIGN_OR_RAISE(
                int32_t id, VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * i,
                                                            info.id_size));
            PAIMON_ASSIGN_OR_RAISE(std::string_view field_key,
                                   VariantBinaryUtil::GetMetadataKey(metadata, id));
            if (field_key == key) {
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t offset,
                    VariantBinaryUtil::ReadUnsigned(raw, info.offset_start + info.offset_size * i,
                                                    info.offset_size));
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t element_pos,
                    VariantBinaryUtil::CheckedElementPos(info.data_start, offset, raw.size()));
                return SubVariant(element_pos);
            }
        }
    } else {
        int32_t low = 0;
        int32_t high = info.num_elements - 1;
        while (low <= high) {
            // Use an unsigned shift to compute the middle of `low` and `high`, which properly
            // handles the case where `low + high` overflows int32.
            auto mid = static_cast<int32_t>(
                (static_cast<uint32_t>(low) + static_cast<uint32_t>(high)) >> 1);
            PAIMON_ASSIGN_OR_RAISE(
                int32_t id, VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * mid,
                                                            info.id_size));
            PAIMON_ASSIGN_OR_RAISE(std::string_view field_key,
                                   VariantBinaryUtil::GetMetadataKey(metadata, id));
            int32_t cmp = field_key.compare(key);
            if (cmp < 0) {
                low = mid + 1;
            } else if (cmp > 0) {
                high = mid - 1;
            } else {
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t offset,
                    VariantBinaryUtil::ReadUnsigned(raw, info.offset_start + info.offset_size * mid,
                                                    info.offset_size));
                PAIMON_ASSIGN_OR_RAISE(
                    int32_t element_pos,
                    VariantBinaryUtil::CheckedElementPos(info.data_start, offset, raw.size()));
                return SubVariant(element_pos);
            }
        }
    }
    return std::shared_ptr<GenericVariant>(nullptr);
}

Result<std::optional<GenericVariant::ObjectField>> GenericVariant::GetFieldAtIndex(
    int32_t index) const {
    std::string_view raw = RawValue();
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info,
                           VariantBinaryUtil::GetObjectInfo(raw, pos_));
    if (index < 0 || index >= info.num_elements) {
        return std::optional<ObjectField>(std::nullopt);
    }
    PAIMON_ASSIGN_OR_RAISE(
        int32_t id,
        VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * index, info.id_size));
    PAIMON_ASSIGN_OR_RAISE(
        int32_t offset, VariantBinaryUtil::ReadUnsigned(
                            raw, info.offset_start + info.offset_size * index, info.offset_size));
    PAIMON_ASSIGN_OR_RAISE(std::string_view key, VariantBinaryUtil::GetMetadataKey(Metadata(), id));
    ObjectField field;
    field.key = std::string(key);
    PAIMON_ASSIGN_OR_RAISE(int32_t field_pos, VariantBinaryUtil::CheckedElementPos(
                                                  info.data_start, offset, raw.size()));
    field.value = SubVariant(field_pos);
    return std::optional<ObjectField>(std::move(field));
}

Result<int32_t> GenericVariant::GetDictionaryIdAtIndex(int32_t index) const {
    std::string_view raw = RawValue();
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ObjectInfo info,
                           VariantBinaryUtil::GetObjectInfo(raw, pos_));
    if (index < 0 || index >= info.num_elements) {
        return VariantBinaryUtil::MalformedVariant(fmt::format(
            "object field index {} is out of bounds for {} fields", index, info.num_elements));
    }
    return VariantBinaryUtil::ReadUnsigned(raw, info.id_start + info.id_size * index, info.id_size);
}

Result<int32_t> GenericVariant::ArraySize() const {
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info,
                           VariantBinaryUtil::GetArrayInfo(RawValue(), pos_));
    return info.num_elements;
}

Result<std::shared_ptr<GenericVariant>> GenericVariant::GetElementAtIndex(int32_t index) const {
    std::string_view raw = RawValue();
    PAIMON_ASSIGN_OR_RAISE(VariantBinaryUtil::ArrayInfo info,
                           VariantBinaryUtil::GetArrayInfo(raw, pos_));
    if (index < 0 || index >= info.num_elements) {
        return std::shared_ptr<GenericVariant>(nullptr);
    }
    PAIMON_ASSIGN_OR_RAISE(
        int32_t offset, VariantBinaryUtil::ReadUnsigned(
                            raw, info.offset_start + info.offset_size * index, info.offset_size));
    PAIMON_ASSIGN_OR_RAISE(int32_t element_pos, VariantBinaryUtil::CheckedElementPos(
                                                    info.data_start, offset, raw.size()));
    return SubVariant(element_pos);
}

bool GenericVariant::operator==(const GenericVariant& other) const {
    return pos_ == other.pos_ && RawValue() == other.RawValue() && Metadata() == other.Metadata();
}

}  // namespace paimon
