/* Copyright (c) 2018 vesoft inc. All rights reserved.
 *
 * This source code is licensed under Apache 2.0 License,
 * attached with Common Clause Condition 1.0, found in the LICENSES directory.
 */

#ifndef COMMON_BASE_STATUSOR_H_
#define COMMON_BASE_STATUSOR_H_

#include "base/Base.h"
#include "base/Status.h"

namespace nebula {

template <typename T>
class StatusOr final {
public:
    // Make friends with other compatible StatusOr<U>
    template <typename U>
    friend class StatusOr;

    // Convenience type traits
    template <typename U>
    static constexpr bool is_status_v = std::is_same<Status, std::decay_t<U>>::value;

    // Tell if `U' is of type `StatusOr<V>'
    template <typename U>
    struct is_status_or {
        static auto sfinae(...) -> uint8_t { return 0; }
        template <typename V>
        static auto sfinae(const StatusOr<V>&) -> uint16_t { return 0; }
        static constexpr auto value = (sizeof(sfinae(std::declval<U>())) == sizeof(uint16_t));
    };

    template <typename U>
    static constexpr bool is_status_or_v = is_status_or<U>::value;

    // Tell if `T' is initializable from `U'.
    // We simply use `is_constructible_v' for now,
    // because we are only utilizing direct-initializations.
    // Besides, we must exclude `Status' and `StatusOr<V>' from `U'.
    //
    // TODO(dutor) we may take other cases into account in future,
    // e.g. convertible but not constructible.
    template <typename U>
    static constexpr bool is_initializable_v = is_constructible_v<T, U> &&
                                               std::is_convertible<U, T>::value &&
                                               !is_status_or_v<U> &&
                                               !is_status_v<U>;

    // Assert that `T' must be copy/move constructible
    static_assert(is_copy_or_move_constructible_v<T>, "`T' must be copy/move constructible");

    // Assert that `T' must not be of type reference
    static_assert(!std::is_reference<T>::value, "`T' must not be of type reference");

    // Assert that `T' must not be of type `Status'
    static_assert(!is_status_v<T>, "`T' must not be of type `Status'");

    // Assert that `T' must not be of type `StatusOr'
    static_assert(!is_status_or_v<T>, "`T' must not be of type `StatusOr'");

    // `StatusOr<T>' contains neither a Status nor a value
    // in the default-constructed case.
    // From the semantics aspect, it must have been associated with
    // a Status or value eventualy before being used.
    StatusOr() {
        state_ = kVoid;
    }

    // Destruct the `Status' or value if it's holding one.
    ~StatusOr() {
        destruct();
    }

    // Copy/move construct from `Status'
    // Not explicit to allow construct from a `Status', e.g. in the `return' statement
    template <typename U>
    StatusOr(U &&status, std::enable_if_t<is_status_v<U>>* = nullptr)   // NOLINT
        : variant_(std::forward<U>(status)) {
        state_ = kStatus;
    }

    // Copy/move construct with a value of any compatible type
    // Not explicit to allow construct from a value, e.g. in the `return' statement
    template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
    StatusOr(U &&value)     // NOLINT
        : variant_(std::forward<U>(value)) {
        state_ = kValue;
    }

    StatusOr(T &&value)     // NOLINT
        : variant_(std::move(value)) {
        state_ = kValue;
    }

    // Copy constructor
    StatusOr(const StatusOr &rhs) : state_(rhs.state_) {
        if (hasValue()) {
            new (&variant_) Variant(rhs.variant_.value_);
        } else if (hasStatus()) {
            new (&variant_) Variant(rhs.variant_.status_);
        }
    }

    // Copy construct from a lvalue of `StatusOr<U>'
    template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
    StatusOr(const StatusOr<U> &rhs) : state_(rhs.state_) {
        if (hasValue()) {
            new (&variant_) Variant(rhs.variant_.value_);
        } else if (hasStatus()) {
            new (&variant_) Variant(rhs.variant_.status_);
        }
    }

    // Copy assignment operator
    StatusOr& operator=(const StatusOr &rhs) {
        if (&rhs == this) {
            return *this;
        }
        reset();
        if (rhs.hasValue()) {
            new (&variant_) Variant(rhs.variant_.value_);
            state_ = kValue;
        } else if (rhs.hasStatus()) {
            new (&variant_) Variant(rhs.variant_.status_);
            state_ = kStatus;
        }
        return *this;
    }

    // Move constructor
    StatusOr(StatusOr &&rhs) noexcept : state_(rhs.state_) {
        if (hasValue()) {
            new (&variant_) Variant(std::move(rhs.variant_.value_));
            rhs.resetValue();
        } else if (rhs.hasStatus()) {
            new (&variant_) Variant(std::move(rhs.variant_.status_));
            rhs.resetStatus();
        }
    }

    // Move construct from a rvalue of StatusOr<U>
    template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
    StatusOr(StatusOr<U> &&rhs) noexcept : state_(rhs.state_) {
        if (hasValue()) {
            new (&variant_) Variant(std::move(rhs.variant_.value_));
            rhs.resetValue();
        } else if (rhs.hasStatus()) {
            new (&variant_) Variant(std::move(rhs.variant_.status_));
            rhs.resetStatus();
        } else {
            // do nothing on void
        }
    }

    // Move assignment operator
    StatusOr& operator=(StatusOr &&rhs) noexcept {
        if (&rhs == this) {
            return *this;
        }
        reset();
        if (rhs.hasValue()) {
            new (&variant_) Variant(std::move(rhs.variant_.value_));
            rhs.resetValue();
            state_ = kValue;
        } else if (rhs.hasStatus()) {
            new (&variant_) Variant(std::move(rhs.variant_.status_));
            rhs.resetStatus();
            state_ = kStatus;
        } else {
            // do nothing on void, since it's already reset
        }
        return *this;
    }

    // Move assigment operator from a rvalue of `StatusOr<U>'
    template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
    StatusOr& operator=(StatusOr<U> &&rhs) noexcept {
        reset();
        if (rhs.hasValue()) {
            new (&variant_) Variant(std::move(rhs.variant_.value_));
            rhs.resetValue();
            state_ = kValue;
        } else if (rhs.hasStatus()) {
            new (&variant_) Variant(std::move(rhs.variant_.status_));
            rhs.resetStatus();
            state_ = kStatus;
        } else {
            // do nothing on void, since it's already reset
        }
        return *this;
    }

    // Move assigment operator from a rvalue of any compatible type with `T'
    template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
    StatusOr& operator=(U &&value) noexcept {
        destruct();
        new (&variant_) Variant(std::forward<U>(value));
        state_ = kValue;
        return *this;
    }

    // Copy assign from a lvalue of `Status'
    StatusOr& operator=(const Status &status) {
        destruct();
        new (&variant_) Variant(status);
        state_ = kStatus;
        return *this;
    }

    // Move assign from a rvalue of `Status'
    StatusOr& operator=(Status &&status) noexcept {
        destruct();
        new (&variant_) Variant(std::move(status));
        state_ = kStatus;
        return *this;
    }

    // Tell if `*this' contains a value
    bool ok() const {
        return hasValue();
    }

    // Type conversion operator, i.e. alias of `ok()'
    explicit operator bool() const {
        return ok();
    }

    // Return the associated `Status' if and only if it has one,
    //
    Status status() const & {
        CHECK(hasStatus());
        return variant_.status_;
    }

    Status status() && {
        CHECK(hasStatus());
        auto status = std::move(variant_.status_);
        resetStatus();
        return status;
    }

    // Return the non-const lvalue reference to the associated value
    // `ok()' is DCHECKed
    T& value() & {
        DCHECK(ok());
        return variant_.value_;
    }

    // Return the const lvalue reference to the associated value
    const T& value() const & {
        DCHECK(ok());
        return variant_.value_;
    }

    // Move the associated value out.
    // `*this' must be a rvalue
    T value() && {
        DCHECK(ok());
        auto value = std::move(variant_.value_);
        resetValue();
        return value;
    }

private:
    bool hasValue() const {
        return state_ == kValue;
    }

    bool hasStatus() const {
        return state_ == kStatus;
    }

    bool isVoid() const {
        return state_ == kVoid;
    }

    void destructValue() {
        variant_.value_.~T();
    }

    void destructStatus() {
        variant_.status_.~Status();
    }

    void resetValue() {
        destructValue();
        state_ = kVoid;
    }

    void resetStatus() {
        destructStatus();
        state_ = kVoid;
    }

    void destruct() {
        if (isVoid()) {
            return;
        }
        if (hasValue()) {
            destructValue();
        } else {
            destructStatus();
        }
    }

    void reset() {
        destruct();
        state_ = kVoid;
    }

private:
    // Variant type to hold a `Status', or a `T', or nothing
    union Variant {
        Variant() {}
        Variant(const Status &status) : status_(status) {}
        Variant(Status &&status) : status_(std::move(status)) {}
        template <typename U, typename = std::enable_if_t<is_initializable_v<U>>>
        Variant(U &&value) : value_(std::forward<U>(value)) {}
        ~Variant() {}

        Status      status_;
        T           value_;
    };

    static constexpr uint8_t kVoid = 0;
    static constexpr uint8_t kStatus = 1;
    static constexpr uint8_t kValue = 2;

    Variant         variant_;
    uint8_t         state_;
};

}   // namespace nebula

#endif  // COMMON_BASE_STATUSOR_H_
