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

#pragma once

#include <cassert>
#include <type_traits>
#include <utility>

#include "paimon/status.h"
#include "paimon/traits.h"

namespace paimon {
/// A `Result<T>` object holds either a value of type T or a `Status` object explaining
/// why such a value is not present.
template <typename T>
class PAIMON_MUST_USE_TYPE PAIMON_EXPORT Result {
 public:
    /// Destructor that properly cleans up the stored value or status.
    ~Result() {
        if (ok()) {
            data_.~T();
        }
        status_.~Status();
    }

    // NOLINTBEGIN(google-explicit-constructor, runtime/explicit)
    /// Construct a successful result with a copy of the given value.
    /// @param data The value to store in result.
    Result(const T& data) : status_(), data_(data) {}

    /// Construct a successful result by moving the given value.
    /// @param data The value to move into result.
    Result(T&& data) : status_(), data_(std::move(data)) {}

    /// Template constructor for converting compatible pointer types.
    /// Support T = std::unique_ptr<B> and U = std::unique_ptr<D> convert, where D is derived class
    /// of B also supports T = std::unique_ptr<B, AllocatorDelete>
    /// @param data The value to move into result.
    template <typename U,
              std::enable_if_t<
                  is_pointer<U>::value && is_pointer<T>::value &&
                  std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr>
    Result(U&& data) : status_(), data_(std::move(data)) {}

    /// Construct a failed result with the given status.
    /// @param status The status object describing the error.
    Result(const Status& status) : status_(status) {}
    // NOLINTEND(google-explicit-constructor, runtime/explicit)

    /// Copy constructor.
    /// @param other The result to copy from.
    Result(const Result& other) {
        if (other.ok()) {
            MakeValue(other.data_);
        }
        MakeStatus(other.status_);
    }

    /// Move constructor.
    /// @param other The result to move from.
    Result(Result&& other) noexcept {
        if (other.ok()) {
            MakeValue(std::move(other.data_));
        }
        // If we moved the status, the other status may become ok but the other
        // value hasn't been constructed => crash on other destructor.
        MakeStatus(other.status_);
    }

    // NOLINTBEGIN(google-explicit-constructor, runtime/explicit)
    /// Template move constructor for converting compatible `Result` types.
    /// @param other The result to move from.
    template <typename U,
              std::enable_if_t<
                  is_pointer<U>::value && is_pointer<T>::value &&
                  std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr>
    Result(Result<U>&& other) noexcept {
        if (other.ok()) {
            MakeValue(std::move(other).value());
        }
        // If we moved the status, the other status may become ok but the other
        // value hasn't been constructed => crash on other destructor.
        MakeStatus(other.status());
    }
    // NOLINTEND(google-explicit-constructor, runtime/explicit)

    /// Copy assignment operator.
    /// @param other The result to copy from.
    /// @return Reference to this result.
    Result& operator=(const Result& other) {
        if (this == &other) {
            return *this;
        }
        if (other.ok()) {
            if (ok()) {
                data_ = other.data_;
            } else {
                MakeValue(other.value());
            }
        } else {
            if (ok()) {
                data_.~T();
            }
        }
        status_ = other.status_;
        return *this;
    }

    /// Move assignment operator.
    /// @param other The result to move from.
    /// @return Reference to this result.
    Result& operator=(Result&& other) {
        if (this == &other) {
            return *this;
        }
        if (other.ok()) {
            if (ok()) {
                data_ = std::move(other.data_);
            } else {
                MakeValue(std::move(other).value());
            }
        } else {
            if (ok()) {
                data_.~T();
            }
        }
        status_ = other.status_;
        return *this;
    }

    /// Template move assignment operator for converting compatible `Result` types.
    /// @param other The result to move from.
    /// @return Reference to this result.
    template <typename U,
              std::enable_if_t<
                  is_pointer<U>::value && is_pointer<T>::value &&
                  std::is_convertible_v<value_type_traits_t<U>, value_type_traits_t<T>>>* = nullptr>
    Result& operator=(Result<U>&& other) {
        if (other.ok()) {
            if (ok()) {
                data_ = std::move(other.data_);
            } else {
                MakeValue(std::move(other).value());
            }
        } else {
            if (ok()) {
                data_.~T();
            }
        }
        status_ = other.status();
        return *this;
    }

    /// Check if this result contains a valid value (i.e., the operation succeeded).
    /// @return `true` if the result contains a valid value, `false` if it contains an error status.
    inline bool ok() const {
        return status_.ok();
    }

    /// Return the value stored in result.
    /// @return Const reference to the stored value.
    /// @note Calling this method when `ok()` is false results in undefined behavior.
    inline const T& value() const& {
        return data_;
    }

    /// Return the value stored in result.
    /// @return Rvalue reference to the stored value.
    /// @note Calling this method when `ok()` is false results in undefined behavior.
    inline T&& value() && {
        return std::move(data_);
    }

    /// Get the status of the result.
    /// @return Const reference to the status object.
    /// @note `status()` is always active, regardless of whether `ok()` is `true` or `false`.
    inline const Status& status() const {
        return status_;
    }

    /// Return the stored value if `ok()`, otherwise return the default value.
    /// @param default_value The value to return if this result contains an error.
    /// @return The stored value if `ok()`, otherwise the default_value.
    inline T value_or(T&& default_value) const& {
        if (ok()) {
            return data_;
        } else {
            return std::forward<T>(default_value);
        }
    }

    /// Return the stored value if `ok()`, otherwise return the default value (rvalue version).
    /// @param default_value The value to return if this result contains an error.
    /// @return The stored value if `ok()`, otherwise the default_value.
    inline T value_or(T&& default_value) && {
        if (ok()) {
            return std::move(data_);
        } else {
            return std::forward<T>(default_value);
        }
    }

 private:
    /// Construct the value(data_) through placement new with the passed arguments.
    /// @param args Arguments to forward to T's constructor.
    template <typename... Arg>
    void MakeValue(Arg&&... args) {
        new (&dummy_) T(std::forward<Arg>(args)...);
    }

    /// Construct the status (status_) through placement new with the passed arguments.
    /// @param args Arguments to forward to status constructor.
    template <typename... Arg>
    void MakeStatus(Arg&&... args) {
        new (&status_) Status(std::forward<Arg>(args)...);
    }

 private:
    // status_ will always be active after the constructor.
    // We make it a union to be able to initialize exactly how we need without
    // waste.
    // Eg. in the copy constructor we use the default constructor of `Status` in
    // the ok() path to avoid an extra Ref call.
    union {
        Status status_;
    };

    union {
        // When T is const, we need some non-const object we can cast to void* for
        // the placement new. dummy_ is that object.
        std::aligned_storage_t<sizeof(T), alignof(T)> dummy_;
        T data_;
    };
};

namespace internal {

/// Extract `Status` from `Result` (const lvalue reference version).
/// @param res The result to extract the status from.
/// @return Const reference to the status.
template <typename T>
inline const Status& GenericToStatus(const Result<T>& res) {
    return res.status();
}

/// Extract `Status` from `Result` (rvalue reference version).
/// @param res The result to extract the status from.
/// @return status object (moved)
template <typename T>
inline Status GenericToStatus(Result<T>&& res) {
    return std::move(res).status();
}

}  // namespace internal

#define PAIMON_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr)                                 \
    auto&& result_name = (rexpr);                                                            \
    PAIMON_RETURN_IF_(!(result_name).ok(), (result_name).status(), PAIMON_STRINGIFY(rexpr)); \
    lhs = std::move(result_name).value()

#define PAIMON_ASSIGN_OR_RAISE_NAME(x, y) PAIMON_CONCAT(x, y)

#define PAIMON_ASSIGN_OR_RAISE(lhs, rexpr)                                                      \
    PAIMON_ASSIGN_OR_RAISE_IMPL(PAIMON_ASSIGN_OR_RAISE_NAME(_error_or_value, __COUNTER__), lhs, \
                                (rexpr))
}  // namespace paimon
