OfferExchange::CrossOfferResult
OfferExchange::crossOffer(OfferFrame& sellingWheatOffer,
                          int64_t maxWheatReceived, int64_t& numWheatReceived,
                          int64_t maxSheepSend, int64_t& numSheepSend)
{
    Asset& sheep = sellingWheatOffer.getOffer().buying;
    Asset& wheat = sellingWheatOffer.getOffer().selling;
    AccountID& accountBID = sellingWheatOffer.getOffer().sellerID;

    Database& db = mLedgerManager.getDatabase();

    AccountFrame::pointer accountB;
    accountB = AccountFrame::loadAccount(accountBID, db);
    if (!accountB)
    {
        throw std::runtime_error(
            "invalid database state: offer must have matching account");
    }

    TrustFrame::pointer wheatLineAccountB;
    if (wheat.type() != ASSET_TYPE_NATIVE)
    {
        wheatLineAccountB = TrustFrame::loadTrustLine(accountBID, wheat, db);
        if (!wheatLineAccountB)
        {
            throw std::runtime_error(
                "invalid database state: offer must have matching trust line");
        }
    }

    TrustFrame::pointer sheepLineAccountB;

    if (sheep.type() == ASSET_TYPE_NATIVE)
    {
        numWheatReceived = INT64_MAX;
    }
    else
    {
        sheepLineAccountB = TrustFrame::loadTrustLine(accountBID, sheep, db);
        if (!sheepLineAccountB)
        {
            throw std::runtime_error(
                "invalid database state: offer must have matching trust line");
        }

        // compute numWheatReceived based on what the account can receive
        int64_t sellerMaxSheep = sheepLineAccountB->getMaxAmountReceive();

        if (!bigDivide(numWheatReceived, sellerMaxSheep,
                       sellingWheatOffer.getOffer().price.d,
                       sellingWheatOffer.getOffer().price.n))
        {
            numWheatReceived = INT64_MAX;
        }
    }

    // adjust numWheatReceived with what the seller has
    {
        int64_t wheatCanSell;
        if (wheat.type() == ASSET_TYPE_NATIVE)
        {
            // can only send above the minimum balance
            wheatCanSell = accountB->getBalanceAboveReserve(mLedgerManager);
        }
        else
        {
            if (wheatLineAccountB->isAuthorized())
            {
                wheatCanSell = wheatLineAccountB->getBalance();
            }
            else
            {
                wheatCanSell = 0;
            }
        }
        if (numWheatReceived > wheatCanSell)
        {
            numWheatReceived = wheatCanSell;
        }
    }
    // you can receive the lesser of the amount of wheat offered or
    // the amount the guy has

    if (numWheatReceived >= sellingWheatOffer.getOffer().amount)
    {
        numWheatReceived = sellingWheatOffer.getOffer().amount;
    }
    else
    {
        // update the offer based on the balance (to determine if it should be
        // deleted or not)
        // note that we don't need to write into the db at this point as the
        // actual update
        // is done further down
        sellingWheatOffer.getOffer().amount = numWheatReceived;
    }

    bool reducedOffer = false;

    if (numWheatReceived > maxWheatReceived)
    {
        numWheatReceived = maxWheatReceived;
        reducedOffer = true;
    }

    // this guy can get X wheat to you. How many sheep does that get him?
    if (!bigDivide(numSheepSend, numWheatReceived,
                   sellingWheatOffer.getOffer().price.n,
                   sellingWheatOffer.getOffer().price.d))
    {
        numSheepSend = INT64_MAX;
    }

    if (numSheepSend > maxSheepSend)
    {
        // reduce the number even more if there is a limit on Sheep
        numSheepSend = maxSheepSend;
        reducedOffer = true;
    }

    // bias towards seller (this cannot overflow at this point)
    numWheatReceived =
        bigDivide(numSheepSend, sellingWheatOffer.getOffer().price.d,
                  sellingWheatOffer.getOffer().price.n);

    bool offerTaken = false;

    if (numWheatReceived == 0 || numSheepSend == 0)
    {
        if (reducedOffer)
        {
            return eOfferCantConvert;
        }
        else
        {
            // force delete the offer as it represents a bogus offer
            numWheatReceived = 0;
            numSheepSend = 0;
            offerTaken = true;
        }
    }

    offerTaken =
        offerTaken || sellingWheatOffer.getOffer().amount <= numWheatReceived;
    if (offerTaken)
    {   // entire offer is taken
        sellingWheatOffer.storeDelete(mDelta, db);

        accountB->addNumEntries(-1, mLedgerManager);
        accountB->storeChange(mDelta, db);
    }
    else
    {
        sellingWheatOffer.getOffer().amount -= numWheatReceived;
        sellingWheatOffer.storeChange(mDelta, db);
    }

    // Adjust balances
    if (sheep.type() == ASSET_TYPE_NATIVE)
    {
        accountB->getAccount().balance += numSheepSend;
        accountB->storeChange(mDelta, db);
    }
    else
    {
        if (!sheepLineAccountB->addBalance(numSheepSend))
        {
            return eOfferCantConvert;
        }
        sheepLineAccountB->storeChange(mDelta, db);
    }

    if (wheat.type() == ASSET_TYPE_NATIVE)
    {
        accountB->getAccount().balance -= numWheatReceived;
        accountB->storeChange(mDelta, db);
    }
    else
    {
        if (!wheatLineAccountB->addBalance(-numWheatReceived))
        {
            return eOfferCantConvert;
        }
        wheatLineAccountB->storeChange(mDelta, db);
    }

    mOfferTrail.push_back(
        ClaimOfferAtom(accountB->getID(), sellingWheatOffer.getOfferID(), wheat,
                       numWheatReceived, sheep, numSheepSend));

    return offerTaken ? eOfferTaken : eOfferPartial;
}