QJSValue DecisionGeneratorApi::getDecisions(int count) {
    if (count > std::numeric_limits<uint8_t>::max())
        // Fail hard for this; the GUI should guarantee count is valid
        qFatal("Application bug: count must fit in a uint8_t");

    KJ_LOG(DBG, "Requesting decisions", count);
    auto request = generator.getValuesRequest();
    request.setCount(count);
    m_isFetchingDecisions = true;
    emit this->isFetchingDecisionsChanged(true);

    auto pass = [this](auto&& e) {
        KJ_DBG(e);
        m_isFetchingDecisions = false;
        emit this->isFetchingDecisionsChanged(false);
        return kj::mv(e);
    };

    auto contestsPromise = request.send().then([this, count](capnp::Response<DecisionGenerator::GetValuesResults> r) {
        if (!r.hasValues() || r.getValues().size() < count) {
            m_isOutOfDecisions = true;
            emit this->isOutOfDecisionsChanged(true);
        }
        m_isFetchingDecisions = false;
        emit this->isFetchingDecisionsChanged(false);
        return kj::mv(r);
    }, pass);

    return converter.convert(kj::mv(contestsPromise),
                             [](capnp::Response<DecisionGenerator::GetValuesResults> r) -> QVariant {
        KJ_LOG(DBG, "Got decisions", r.getValues().size());
        QVariantList decisions;
        for (auto decisionWrapper : r.getValues()) {
            auto decision = decisionWrapper.getValue();
            decisions.append(
                        QVariantMap{
                            {"decisionId", QString(convertBlob(ReaderPacker(decision.getId()).array()).toHex())},
                            {"counted", decision.getCounted()}
                        });
        }
        return QVariant(decisions);
    });
}
QJSValue ContestGeneratorApi::getContests(int count) {
    KJ_LOG(DBG, "Requesting contests", count);
    auto request = generator.getValuesRequest();
    request.setCount(count);
    m_isFetchingContests = true;
    emit this->isFetchingContestsChanged(true);

    auto pass = [this](auto&& e) {
        m_isFetchingContests = false;
        emit this->isFetchingContestsChanged(false);
        return kj::mv(e);
    };

    auto contestsPromise = request.send().then([this, count](capnp::Response<ContestGenerator::GetValuesResults> r) {
        if (!r.hasValues() || r.getValues().size() < count) {
            m_isOutOfContests = true;
            emit this->isOutOfContestsChanged(true);
        }
        m_isFetchingContests = false;
        emit this->isFetchingContestsChanged(false);
        return kj::mv(r);
    }, pass);

    return converter.convert(kj::mv(contestsPromise),
                             [this](capnp::Response<ContestGenerator::GetValuesResults> r) -> QVariant {
        KJ_LOG(DBG, "Got contests", r.getValues().size());
        QVariantList contests;
        for (auto contestWrapper : r.getValues()) {
            auto contest = contestWrapper.getValue();
            auto results = new ContestResultsApi(contest.getContestResults(), converter);
            QQmlEngine::setObjectOwnership(results, QQmlEngine::JavaScriptOwnership);
            // TODO: Set engagement notification API (when server defines it)
            contests.append(
                        QVariantMap{
                            {"contestId", QString(convertBlob(ReaderPacker(contest.getContestId()).array()).toHex())},
                            {"votingStake", qint64(contest.getVotingStake())},
                            {"resultsApi", QVariant::fromValue(results)}
                        });
        }
        return {QVariant(contests)};
    });
}
kj::Promise<OwningWrapper<DecisionWrapper>*> ChainAdaptorWrapper::_getDecision(QString owner, QString contestId)
{
    if (!hasAdaptor()) return KJ_EXCEPTION(FAILED, "No blockchain adaptor is set.");

    using Reader = ::Balance::Reader;
    auto promise = m_adaptor->getContest(QByteArray::fromHex(contestId.toLocal8Bit())).then([=](::Contest::Reader c) {
        return m_adaptor->getBalancesForOwner(owner).then([c](kj::Array<Reader> balances) {
            return std::make_tuple(c, kj::mv(balances));
        });
    }).then([=](std::tuple<::Contest::Reader, kj::Array<Reader>> contestAndBalances) {
        ::Contest::Reader contest;
        kj::Array<Reader> balances;
        std::tie(contest, balances) = kj::mv(contestAndBalances);

        auto newEnd = std::remove_if(balances.begin(), balances.end(), [contest](Reader balance) {
            return balance.getType() != contest.getContest().getCoin();
        });
        KJ_REQUIRE(newEnd - balances.begin() > 0, "No balances found in the contest's coin, so no decision exists.");

        // This struct is basically a lambda on steroids. I'm using it to transform the array of balances into an array
        // of datagrams containing the decision I want on each of those balances. Also, make sure the newest balance's
        // decision is in the front (I don't care about preserving order) so that the newest decision will be returned.
        struct {
            // Capture this
            ChainAdaptorWrapper* wrapper;
            // Capture contestId
            QString contestId;
            // For each balance, look up the datagram containing the relevant decision. Store the promises in this array
            kj::ArrayBuilder<kj::Promise<kj::Maybe<::Datagram::Reader>>> datagramPromises;

            Reader newestBalance;
            void operator() (Reader balance) {
                if (datagramPromises.size() == 0) {
                    // First balance. Nothing to compare.
                    newestBalance = balance;
                }

                // Start a lookup for the datagram and store the promise.
                auto promise = wrapper->m_adaptor->getDatagram(convertBlob(balance.getId()),
                                                               Datagram::DatagramType::DECISION,
                                                               contestId);
                datagramPromises.add(promise.then([](::Datagram::Reader r) -> kj::Maybe<::Datagram::Reader> {
                        return r;
                    }, [](kj::Exception e) -> kj::Maybe<::Datagram::Reader> {
                        qInfo() << "Got exception when fetching datagram on balance:" << e.getDescription().cStr();
                        return {};
                    })
                );

                // If this balance is newer than the previous newest, move its promise to the front
                if (balance.getCreationOrder() > newestBalance.getCreationOrder()) {
                    newestBalance = balance;
                    std::swap(datagramPromises.front(), datagramPromises.back());
                }
            }
        } accumulator{this, contestId,
                    kj::heapArrayBuilder<kj::Promise<kj::Maybe<::Datagram::Reader>>>(newEnd - balances.begin()), {}};

        // Process all of the balances. Fetch the appropriate decision for each.
        qDebug() << "Looking for decisions on" << newEnd - balances.begin() << "balances.";
        accumulator = std::for_each(balances.begin(), newEnd, kj::mv(accumulator));

        return kj::joinPromises(accumulator.datagramPromises.finish());
    }).then([=](kj::Array<kj::Maybe<::Datagram::Reader>> datagrams) {
        std::unique_ptr<OwningWrapper<swv::DecisionWrapper>> decision;
        for (auto datagramMaybe : datagrams) {
            KJ_IF_MAYBE(datagram, datagramMaybe) {
                decision.reset(OwningWrapper<DecisionWrapper>::deserialize(convertBlob(datagram->getContent())));
                break;
            }
        }