#ifndef TILE_BASE_FUTURE_CORE_H
#define TILE_BASE_FUTURE_CORE_H

#pragma once

#include "tile/base/future/boxed.h"
#include "tile/base/future/executor.h"
#include "tile/base/internal/logging.h"
#include "tile/base/internal/move_on_copy.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/unique_lock.h"

namespace tile {
namespace future {
template<typename... Ts>
class Core {
public:
    using value_type  = Boxed<Ts...>;
    using action_type = std::function<void(value_type &&) noexcept>;

    explicit Core(Executor executor) : executor_(std::move(executor)) {}

    void SetBoxed(value_type &&value) noexcept;
    void ChainAction(action_type action) noexcept;
    Executor GetExecutor() const noexcept;

private:
    struct WaitingForSingleObject {
        WaitingForSingleObject() {}

        WaitingForSingleObject(action_type &&action) : on_satisfied(std::move(action)) {}

        action_type on_satisfied;
    };

    struct Satisfied {
        Satisfied(value_type &&v, bool e) : value(std::move(v)), ever_called_continuation(e) {}

        value_type value;
        bool ever_called_continuation{false};
    };

private:
    // std::mutex lock_;
    Mutex lock_;
    mpark::variant<WaitingForSingleObject, Satisfied> state_;
    Executor executor_;
};

template<typename... Ts>
void
Core<Ts...>::SetBoxed(value_type &&value) noexcept
{
    UniqueLock<Mutex> lk(lock_);
    action_type action = std::move(mpark::get<WaitingForSingleObject>(state_).on_satisfied);

    state_.template emplace<Satisfied>(Satisfied{std::move(value), !!action});

    if (action) {
        lk.Unlock();

        auto moved_action = MakeMoveOnCopy(std::move(action));
        auto moved_value  = MakeMoveOnCopy(std::move(mpark::get<Satisfied>(state_).value));
        executor_.Execute([moved_action, moved_value]() mutable noexcept { moved_action.Ref()(moved_value.Move()); });
    }
}

template<typename... Ts>
void
Core<Ts...>::ChainAction(action_type action) noexcept
{
    UniqueLock<Mutex> lk(lock_);
    if (state_.index() == 0) {
        // waiting for value
        state_.template emplace<WaitingForSingleObject>(WaitingForSingleObject{std::move(action)});
    } else {
        TILE_CHECK_EQ(state_.index(), 1);
        auto &&s = mpark::get<Satisfied>(state_);
        TILE_CHECK(!s.ever_called_continuation, "Action may not be chained for multiple times.");

        s.ever_called_continuation = true;
        lk.Unlock();
        auto moved_action = MakeMoveOnCopy(std::move(action));
        auto moved_value  = MakeMoveOnCopy(std::move(s.value));
        executor_.Execute([moved_action, moved_value]() mutable noexcept { moved_action.Ref()(moved_value.Move()); });
    }
}

template<typename... Ts>
inline Executor
Core<Ts...>::GetExecutor() const noexcept
{
    return executor_;
}
}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_CORE_H
