/*
 * 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/predicate/literal.h"

#include <cmath>
#include <cstring>
#include <functional>
#include <sstream>
#include <string_view>
#include <type_traits>
#include <utility>

#include "fmt/format.h"
#include "paimon/common/utils/field_type_utils.h"
#include "paimon/common/utils/fields_comparator.h"
#include "paimon/data/decimal.h"
#include "paimon/data/timestamp.h"
#include "paimon/status.h"

namespace paimon {
class Literal::Impl {
 public:
    void Release() {
        if ((type_ == FieldType::STRING || type_ == FieldType::BINARY ||
             type_ == FieldType::BLOB) &&
            value_.Buffer) {
            if (own_data_) {
                delete[] value_.Buffer;
            }
            value_.Buffer = nullptr;
        }
    }

    size_t CalculateHashCode() const {
        if (is_null_) {
            return 0;
        }
        switch (type_) {
            case FieldType::BOOLEAN:
                return std::hash<bool>{}(value_.BooleanVal);
            case FieldType::TINYINT:
                return std::hash<int8_t>{}(value_.TinyIntVal);
            case FieldType::SMALLINT:
                return std::hash<int16_t>{}(value_.SmallIntVal);
            case FieldType::INT:
                return std::hash<int32_t>{}(value_.IntVal);
            case FieldType::BIGINT:
                return std::hash<int64_t>{}(value_.BigIntVal);
            case FieldType::FLOAT:
                return std::hash<float>{}(value_.FloatVal);
            case FieldType::DOUBLE:
                return std::hash<double>{}(value_.DoubleVal);
            case FieldType::STRING:
            case FieldType::BINARY:
                return std::hash<std::string_view>{}(std::string_view(value_.Buffer, size_));
            case FieldType::TIMESTAMP:
                return std::hash<int64_t>{}(value_.TimestampVal.GetMillisecond()) * 17 +
                       std::hash<int64_t>{}(value_.TimestampVal.GetNanoOfMillisecond());
            case FieldType::DECIMAL:
                return std::hash<int64_t>{}(value_.DecimalVal.HighBits()) * 31 +
                       std::hash<int64_t>{}(value_.DecimalVal.LowBits()) * 17 +
                       std::hash<int32_t>{}(value_.DecimalVal.Scale());
            case FieldType::DATE:
                return std::hash<int32_t>{}(value_.IntVal);
            default:
                return 0;
        }
    }

    union LiteralVal {
        int64_t BigIntVal;
        bool BooleanVal;
        int8_t TinyIntVal;
        int16_t SmallIntVal;
        int32_t IntVal;
        float FloatVal;
        double DoubleVal;
        char* Buffer;
        Decimal DecimalVal;
        Timestamp TimestampVal;
    } value_ = {};

    FieldType type_ = FieldType::UNKNOWN;
    // size of value_ if it is Buffer
    size_t size_ = 0;
    // indicate if this literal is null
    bool is_null_ = false;
    size_t hash_code_ = 0;
    bool own_data_ = true;
};

Literal::Literal(FieldType type) : impl_(std::make_unique<Impl>()) {
    impl_->type_ = type;
    impl_->size_ = 0;
    impl_->is_null_ = true;
}

template <typename T>
Literal::Literal(const T& val) : impl_(std::make_unique<Impl>()) {
    if constexpr (std::is_same_v<T, bool>) {
        impl_->type_ = FieldType::BOOLEAN;
        impl_->value_.BooleanVal = val;
    } else if constexpr (std::is_same_v<T, int8_t>) {
        impl_->type_ = FieldType::TINYINT;
        impl_->value_.TinyIntVal = val;
    } else if constexpr (std::is_same_v<T, int16_t>) {
        impl_->type_ = FieldType::SMALLINT;
        impl_->value_.SmallIntVal = val;
    } else if constexpr (std::is_same_v<T, int32_t>) {
        impl_->type_ = FieldType::INT;
        impl_->value_.IntVal = val;
    } else if constexpr (std::is_same_v<T, int64_t>) {
        impl_->type_ = FieldType::BIGINT;
        impl_->value_.BigIntVal = val;
    } else if constexpr (std::is_same_v<T, float>) {
        impl_->type_ = FieldType::FLOAT;
        impl_->value_.FloatVal = val;
    } else if constexpr (std::is_same_v<T, double>) {
        impl_->type_ = FieldType::DOUBLE;
        impl_->value_.DoubleVal = val;
    } else if constexpr (std::is_same_v<T, Timestamp>) {
        impl_->type_ = FieldType::TIMESTAMP;
        impl_->value_.TimestampVal = val;
    } else if constexpr (std::is_same_v<T, Decimal>) {
        impl_->type_ = FieldType::DECIMAL;
        impl_->value_.DecimalVal = val;
    } else {
        impl_->type_ = FieldType::UNKNOWN;
    }
    impl_->is_null_ = false;
    impl_->hash_code_ = impl_->CalculateHashCode();
}

Literal::Literal(FieldType binary_type, const char* str, size_t size)
    : Literal(binary_type, str, size, /*own_data=*/true) {}

Literal::Literal(FieldType binary_type, const char* str, size_t size, bool own_data)
    : impl_(std::make_unique<Impl>()) {
    impl_->type_ = binary_type;
    impl_->size_ = size;
    impl_->is_null_ = false;
    impl_->own_data_ = own_data;
    if (own_data) {
        impl_->value_.Buffer = new char[size];
        if (size > 0) {
            memcpy(impl_->value_.Buffer, str, size);
        }
        impl_->hash_code_ = impl_->CalculateHashCode();
    } else {
        impl_->value_.Buffer = const_cast<char*>(str);
        impl_->hash_code_ = impl_->CalculateHashCode();
    }
}

Literal::Literal(FieldType date_type, int32_t date_value) : impl_(std::make_unique<Impl>()) {
    impl_->type_ = date_type;
    impl_->is_null_ = false;
    impl_->value_.IntVal = date_value;
    impl_->hash_code_ = impl_->CalculateHashCode();
}

Literal::Literal(Literal&& other) {
    *this = std::move(other);
}

Literal::Literal(const Literal& other) {
    *this = other;
}

Literal& Literal::operator=(Literal&& other) {
    if (&other == this) {
        return *this;
    }
    if (this->impl_) {
        this->impl_->Release();
    } else {
        impl_ = std::make_unique<Impl>();
    }
    impl_->type_ = other.impl_->type_;
    impl_->size_ = other.impl_->size_;
    impl_->is_null_ = other.impl_->is_null_;
    impl_->hash_code_ = other.impl_->hash_code_;
    impl_->own_data_ = other.impl_->own_data_;
    impl_->value_ = other.impl_->value_;
    if (impl_->type_ == FieldType::STRING || impl_->type_ == FieldType::BINARY ||
        impl_->type_ == FieldType::BLOB) {
        other.impl_->value_.Buffer = nullptr;
    }
    return *this;
}

Literal& Literal::operator=(const Literal& other) {
    if (&other == this) {
        return *this;
    }
    if (this->impl_) {
        this->impl_->Release();
    } else {
        impl_ = std::make_unique<Impl>();
    }
    impl_->type_ = other.impl_->type_;
    impl_->size_ = other.impl_->size_;
    impl_->is_null_ = other.impl_->is_null_;
    impl_->hash_code_ = other.impl_->hash_code_;
    impl_->own_data_ = other.impl_->own_data_;
    if ((impl_->type_ == FieldType::STRING || impl_->type_ == FieldType::BINARY ||
         impl_->type_ == FieldType::BLOB) &&
        impl_->own_data_) {
        impl_->value_.Buffer = new char[other.impl_->size_];
        if (other.impl_->size_ > 0) {
            memcpy(impl_->value_.Buffer, other.impl_->value_.Buffer, other.impl_->size_);
        }
    } else {
        impl_->value_ = other.impl_->value_;
    }
    return *this;
}

Literal::~Literal() {
    impl_->Release();
}

bool Literal::IsNull() const {
    return impl_->is_null_;
}

FieldType Literal::GetType() const {
    return impl_->type_;
}

size_t Literal::HashCode() const {
    return impl_->hash_code_;
}

std::string Literal::ToString() const {
    if (impl_->is_null_) {
        return "null";
    }

    std::ostringstream sstream;
    std::string str;
    switch (impl_->type_) {
        case FieldType::BOOLEAN:
            sstream << (impl_->value_.BooleanVal ? "true" : "false");
            break;
        case FieldType::TINYINT:
            sstream << impl_->value_.TinyIntVal;
            break;
        case FieldType::SMALLINT:
            sstream << impl_->value_.SmallIntVal;
            break;
        case FieldType::INT:
            sstream << impl_->value_.IntVal;
            break;
        case FieldType::BIGINT:
            sstream << impl_->value_.BigIntVal;
            break;
        case FieldType::FLOAT:
            sstream << impl_->value_.FloatVal;
            break;
        case FieldType::DOUBLE:
            sstream << impl_->value_.DoubleVal;
            break;
        case FieldType::STRING:
        case FieldType::BINARY:
            str.assign(impl_->value_.Buffer, impl_->size_);
            sstream << str;
            break;
        case FieldType::TIMESTAMP:
            sstream << impl_->value_.TimestampVal.ToString();
            break;
        case FieldType::DECIMAL:
            sstream << impl_->value_.DecimalVal.ToString();
            break;
        case FieldType::DATE:
            sstream << impl_->value_.IntVal;
            break;
        default:
            sstream << "unknown type id:" << FieldTypeUtils::FieldTypeToString(impl_->type_);
    }
    return sstream.str();
}

Result<int32_t> Literal::CompareTo(const Literal& other) const {
    if (this == &other) {
        return 0;
    }
    // TODO(xinyu.lxy): compare with BIGINT and FLOAT/ INT and BIGINT
    if (impl_->type_ != other.impl_->type_) {
        return Status::Invalid(
            fmt::format("cannot compare with different type [{}: {}], [{}: {}]", ToString(),
                        FieldTypeUtils::FieldTypeToString(impl_->type_), other.ToString(),
                        FieldTypeUtils::FieldTypeToString(other.impl_->type_)));
    }
    if (impl_->is_null_ && other.impl_->is_null_) {
        return 0;
    }
    if (impl_->is_null_ || other.impl_->is_null_) {
        return Status::Invalid("cannot compare with null");
    }
    switch (impl_->type_) {
        case FieldType::BOOLEAN:
            return impl_->value_.BooleanVal == other.impl_->value_.BooleanVal
                       ? 0
                       : ((impl_->value_.BooleanVal < other.impl_->value_.BooleanVal) ? -1 : 1);
        case FieldType::TINYINT:
            return impl_->value_.TinyIntVal == other.impl_->value_.TinyIntVal
                       ? 0
                       : ((impl_->value_.TinyIntVal < other.impl_->value_.TinyIntVal) ? -1 : 1);
        case FieldType::SMALLINT:
            return impl_->value_.SmallIntVal == other.impl_->value_.SmallIntVal
                       ? 0
                       : ((impl_->value_.SmallIntVal < other.impl_->value_.SmallIntVal) ? -1 : 1);
        case FieldType::INT:
            return impl_->value_.IntVal == other.impl_->value_.IntVal
                       ? 0
                       : ((impl_->value_.IntVal < other.impl_->value_.IntVal) ? -1 : 1);
        case FieldType::BIGINT:
            return impl_->value_.BigIntVal == other.impl_->value_.BigIntVal
                       ? 0
                       : ((impl_->value_.BigIntVal < other.impl_->value_.BigIntVal) ? -1 : 1);
        case FieldType::FLOAT:
            return FieldsComparator::CompareFloatingPoint(impl_->value_.FloatVal,
                                                          other.impl_->value_.FloatVal);
        case FieldType::DOUBLE:
            return FieldsComparator::CompareFloatingPoint(impl_->value_.DoubleVal,
                                                          other.impl_->value_.DoubleVal);
        case FieldType::STRING:
        case FieldType::BINARY: {
            std::string_view v1(impl_->value_.Buffer, impl_->size_);
            std::string_view v2(other.impl_->value_.Buffer, other.impl_->size_);
            int32_t cmp = v1.compare(v2);
            return cmp < 0 ? -1 : (cmp > 0 ? 1 : 0);
        }
        case FieldType::TIMESTAMP:
            return impl_->value_.TimestampVal == other.impl_->value_.TimestampVal
                       ? 0
                       : (impl_->value_.TimestampVal < other.impl_->value_.TimestampVal ? -1 : 1);
        case FieldType::DECIMAL:
            return impl_->value_.DecimalVal.CompareTo(other.impl_->value_.DecimalVal);
        case FieldType::DATE:
            return impl_->value_.IntVal == other.impl_->value_.IntVal
                       ? 0
                       : ((impl_->value_.IntVal < other.impl_->value_.IntVal) ? -1 : 1);
        default:
            return Status::Invalid(fmt::format("unsupported type {}",
                                               FieldTypeUtils::FieldTypeToString(impl_->type_)));
    }
}

bool Literal::operator==(const Literal& other) const {
    if (this == &other) {
        return true;
    }
    auto result = CompareTo(other);
    if (!result.ok()) {
        return false;
    }
    return result.value() == 0;
}

bool Literal::operator!=(const Literal& r) const {
    return !(*this == r);
}

template <typename T>
T Literal::GetValue() const {
    if constexpr (std::is_same_v<T, bool>) {
        return impl_->value_.BooleanVal;
    } else if constexpr (std::is_same_v<T, int8_t>) {
        return impl_->value_.TinyIntVal;
    } else if constexpr (std::is_same_v<T, int16_t>) {
        return impl_->value_.SmallIntVal;
    } else if constexpr (std::is_same_v<T, int32_t>) {
        return impl_->value_.IntVal;
    } else if constexpr (std::is_same_v<T, int64_t>) {
        return impl_->value_.BigIntVal;
    } else if constexpr (std::is_same_v<T, float>) {
        return impl_->value_.FloatVal;
    } else if constexpr (std::is_same_v<T, double>) {
        return impl_->value_.DoubleVal;
    } else if constexpr (std::is_same_v<T, std::string>) {
        return std::string(impl_->value_.Buffer, impl_->size_);
    } else if constexpr (std::is_same_v<T, Timestamp>) {
        return impl_->value_.TimestampVal;
    } else if constexpr (std::is_same_v<T, Decimal>) {
        return impl_->value_.DecimalVal;
    } else {
        return T();
    }
}

template Literal::Literal(const bool&);
template Literal::Literal(const int8_t&);
template Literal::Literal(const int16_t&);
template Literal::Literal(const int32_t&);
template Literal::Literal(const int64_t&);
template Literal::Literal(const float&);
template Literal::Literal(const double&);
template Literal::Literal(const Timestamp&);
template Literal::Literal(const Decimal&);

template bool Literal::GetValue() const;
template int8_t Literal::GetValue() const;
template int16_t Literal::GetValue() const;
template int32_t Literal::GetValue() const;
template int64_t Literal::GetValue() const;
template float Literal::GetValue() const;
template double Literal::GetValue() const;
template std::string Literal::GetValue() const;
template Timestamp Literal::GetValue() const;
template Decimal Literal::GetValue() const;
}  // namespace paimon
