/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed 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 <functional>
#include <type_traits>
#include <variant>

namespace gfxstream {

template <typename E>
class unexpected;

template<class E>
unexpected(E) -> unexpected<E>;

#define ENABLE_IF(...) typename std::enable_if<__VA_ARGS__>::type* = nullptr

template <typename T, typename E>
class expected {
  public:
    constexpr expected() = default;
    constexpr expected(const expected& rhs) = default;
    constexpr expected(expected&& rhs) = default;

    template <typename CopyT = T, ENABLE_IF(!std::is_void<CopyT>::value)>
    constexpr expected(T&& v)
        : mVariant(std::in_place_index<0>, std::forward<T>(v)) {}

    template<class... Args, ENABLE_IF(std::is_constructible<T, Args&&...>::value)>
    constexpr expected(std::in_place_t, Args&&... args)
        : mVariant(std::in_place_index<0>, std::forward<Args>(args)...) {}

    constexpr expected(const unexpected<E>& u)
        : mVariant(std::in_place_index<1>, u.value()) {}

    template<class OtherE = E, ENABLE_IF(std::is_constructible<E, const OtherE&>::value)>
    constexpr expected(const unexpected<OtherE>& e)
        : mVariant(std::in_place_index<1>, e.value()) {}

    constexpr const T* operator->() const { return std::addressof(value()); }
    constexpr T* operator->() { return std::addressof(value()); }
    constexpr const T& operator*() const& { return value(); }
    constexpr T& operator*() & { return value(); }
    constexpr const T&& operator*() const&& { return std::move(std::get<T>(mVariant)); }
    constexpr T&& operator*() && { return std::move(std::get<T>(mVariant)); }

    constexpr bool has_value() const { return mVariant.index() == 0; }
    constexpr bool ok() const { return has_value(); }

    template <typename T2 = T, ENABLE_IF(!std::is_void<T>::value)>
    constexpr const T& value() const& { return std::get<T>(mVariant); }
    template <typename T2 = T, ENABLE_IF(!std::is_void<T>::value)>
    constexpr T& value() & { return std::get<T>(mVariant); }

    constexpr const T&& value() const&& { return std::move(std::get<T>(mVariant)); }
    constexpr T&& value() && { return std::move(std::get<T>(mVariant)); }

    constexpr const E& error() const& { return std::get<E>(mVariant); }
    constexpr E& error() & { return std::get<E>(mVariant); }
    constexpr const E&& error() const&& { return std::move(std::get<E>(mVariant)); }
    constexpr E&& error() && { return std::move(std::get<E>(mVariant)); }

    template <typename F,
              typename NewE = std::remove_cv_t<std::invoke_result_t<F, E>>>
    constexpr expected<T, NewE> transform_error(F&& function) {
        if (ok()) {
            if constexpr (std::is_void_v<T>) {
                return expected<T, NewE>();
            } else {
                return expected<T, NewE>(std::in_place, value());
            }
        } else {
            return unexpected(std::invoke(std::forward<F>(function), error()));
        }
    }

  private:
    std::variant<T, E> mVariant;
};

template <typename E>
class unexpected {
  public:
    constexpr unexpected(const unexpected&) = default;

    template <typename T>
    constexpr explicit unexpected(T&& e)
        : mError(std::forward<T>(e)) {}

    template<class... Args, ENABLE_IF(std::is_constructible<E, Args&&...>::value)>
    constexpr explicit unexpected(std::in_place_t, Args&&... args)
        : mError(std::forward<Args>(args)...) {}

    constexpr const E& value() const& noexcept { return mError; }
    constexpr E& value() & noexcept { return mError; }
    constexpr const E&& value() const&& noexcept { return std::move(mError); }
    constexpr E&& value() && noexcept { return std::move(mError); }

  private:
    E mError;
};

#define GFXSTREAM_EXPECT(x)                                       \
    ({                                                            \
        auto local_expected = (x);                                \
        if (!local_expected.ok()) {                               \
            return gfxstream::unexpected(local_expected.error()); \
        };                                                        \
        std::move(local_expected.value());                        \
     })

class Ok {};

}  // namespace gfxstream