//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

SETUP_LOG (OfferCreateTransactor)

// Make sure an offer is still valid. If not, mark it unfunded.
bool OfferCreateTransactor::bValidOffer (
    SLE::ref            sleOffer,
    uint256 const&      uOfferIndex,
    const uint160&      uOfferOwnerID,
    const STAmount&     saOfferPays,
    const STAmount&     saOfferGets,
    const uint160&      uTakerAccountID,
    boost::unordered_set<uint256>&  usOfferUnfundedFound,
    boost::unordered_set<uint256>&  usOfferUnfundedBecame,
    boost::unordered_set<uint160>&  usAccountTouched,
    STAmount&           saOfferFunds)                       // <--
{
    bool bValid = false;

    if (sleOffer->isFieldPresent (sfExpiration) && sleOffer->getFieldU32 (sfExpiration) <= mEngine->getLedger ()->getParentCloseTimeNC ())
    {
        // Offer is expired. Expired offers are considered unfunded. Delete it.
        WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered expired offer";

        usOfferUnfundedFound.insert (uOfferIndex);

        bValid  = false;
    }
    else if (uOfferOwnerID == uTakerAccountID)
    {
        // Would take own offer. Consider old offer expired. Delete it.
        WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered taker's own old offer";

        usOfferUnfundedFound.insert (uOfferIndex);

        bValid  = false;
    }
    else if (!saOfferGets.isPositive () || !saOfferPays.isPositive ())
    {
        // Offer has bad amounts. Consider offer expired. Delete it.
        WriteLog (lsWARNING, OfferCreateTransactor) << boost::str (boost::format ("bValidOffer: BAD OFFER: saOfferPays=%s saOfferGets=%s")
                % saOfferPays % saOfferGets);

        usOfferUnfundedFound.insert (uOfferIndex);

        bValid = false;
    }
    else
    {
        WriteLog (lsTRACE, OfferCreateTransactor) << "bValidOffer: saOfferPays=" << saOfferPays.getFullText ();

        saOfferFunds    = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays);

        if (!saOfferFunds.isPositive ())
        {
            // Offer is unfunded, possibly due to previous balance action.
            WriteLog (lsDEBUG, OfferCreateTransactor) << "bValidOffer: offer unfunded: delete";

            boost::unordered_set<uint160>::iterator account = usAccountTouched.find (uOfferOwnerID);

            if (account != usAccountTouched.end ())
            {
                // Previously touched account.
                usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success.
            }
            else
            {
                // Never touched source account.
                usOfferUnfundedFound.insert (uOfferIndex);  // Delete found unfunded offer when possible.
            }

            bValid  = false;
        }
        else
        {
            bValid  = true;
        }
    }

    return bValid;
}

// Take as much as possible. Adjusts account balances. Charges fees on top to taker.
// -->    uBookBase: The order book to take against.
// -->  saTakerPays: What the taker offers (w/ issuer)
// -->  saTakerGets: What the taker wanted (w/ issuer)
// <--  saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer.
// <--   saTakerGot: What taker got not including fees. To reduce an offer.
// <--    terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING
// <--    bUnfunded: if tesSUCCESS, consider offer unfunded after taking.
TER OfferCreateTransactor::takeOffers (
    const bool          bOpenLedger,
    const bool          bPassive,
    const bool          bSell,
    uint256 const&      uBookBase,
    const uint160&      uTakerAccountID,
    SLE::ref            sleTakerAccount,
    const STAmount&     saTakerPays,
    const STAmount&     saTakerGets,
    STAmount&           saTakerPaid,
    STAmount&           saTakerGot,
    bool&               bUnfunded)
{
    // The book has the most elements. Take the perspective of the book.
    // Book is ordered for taker: taker pays / taker gets (smaller is better)

    // The order is for the other books currencys for get and pays are opposites.
    // We want the same ratio for the respective currencies.
    // So we swap paid and gets for determing take quality.

    assert (saTakerPays && saTakerGets);

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString ();

    LedgerEntrySet&         lesActive           = mEngine->getNodes ();
    uint256                 uTipIndex           = uBookBase;
    const uint256           uBookEnd            = Ledger::getQualityNext (uBookBase);
    const uint64            uTakeQuality        = STAmount::getRate (saTakerGets, saTakerPays);
    STAmount                saTakerRate         = STAmount::setRate (uTakeQuality);
    const uint160           uTakerPaysAccountID = saTakerPays.getIssuer ();
    const uint160           uTakerGetsAccountID = saTakerGets.getIssuer ();
    TER                     terResult           = temUNCERTAIN;

    boost::unordered_set<uint256>   usOfferUnfundedBecame;  // Offers that became unfunded.
    boost::unordered_set<uint160>   usAccountTouched;       // Accounts touched.

    saTakerPaid     = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ());
    saTakerGot      = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ());
    bUnfunded       = false;

    while (temUNCERTAIN == terResult)
    {
        SLE::pointer    sleOfferDir;
        uint64          uTipQuality     = 0;
        STAmount        saTakerFunds    = lesActive.accountFunds (uTakerAccountID, saTakerPays);
        STAmount        saSubTakerPays  = saTakerPays - saTakerPaid; // How much more to spend.
        STAmount        saSubTakerGets  = saTakerGets - saTakerGot; // How much more is wanted.

        // Figure out next offer to take, if needed.
        if (saTakerFunds.isPositive ()          // Taker has funds available.
                && saSubTakerPays.isPositive ()
                && saSubTakerGets.isPositive ())
        {
            sleOfferDir     = mEngine->entryCache (ltDIR_NODE, lesActive.getNextLedgerIndex (uTipIndex, uBookEnd));

            if (sleOfferDir)
            {
                uTipIndex       = sleOfferDir->getIndex ();
                uTipQuality     = Ledger::getQuality (uTipIndex);

                WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: possible counter offer found: uTipQuality=%d uTipIndex=%s")
                        % uTipQuality
                        % uTipIndex.ToString ());

            }
            else
            {
                WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: counter offer book is empty: "
                        << uTipIndex.ToString ()
                        << " ... "
                        << uBookEnd.ToString ();
            }
        }

        if (!saTakerFunds.isPositive ())                    // Taker has no funds.
        {
            // Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here.
            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: done: taker unfunded.";

            bUnfunded   = true;                             // Don't create an order.
            terResult   = tesSUCCESS;
        }
        else if (!sleOfferDir                               // No offer directory to take.
                 || uTakeQuality < uTipQuality                   // No offers of sufficient quality available.
                 || (bPassive && uTakeQuality == uTipQuality))
        {
            // Done.
            STAmount    saTipRate           = sleOfferDir ? STAmount::setRate (uTipQuality) : saTakerRate;

            WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d")
                    % !!sleOfferDir
                    % uTakeQuality
                    % (uTakeQuality == uTipQuality
                       ? '='
                       : uTakeQuality < uTipQuality
                       ? '<'
                       : '>')
                    % uTipQuality
                    % saTakerRate
                    % (saTakerRate == saTipRate
                       ? '='
                       : saTakerRate < saTipRate
                       ? '<'
                       : '>')
                    % saTipRate
                    % bPassive);

            terResult   = tesSUCCESS;
        }
        else
        {
            // Have an offer directory to consider.
            WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: considering dir: " << sleOfferDir->getJson (0);

            SLE::pointer    sleBookNode;
            unsigned int    uBookEntry;
            uint256         uOfferIndex;

            lesActive.dirFirst (uTipIndex, sleBookNode, uBookEntry, uOfferIndex);

            SLE::pointer    sleOffer        = mEngine->entryCache (ltOFFER, uOfferIndex);

            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0);

            const uint160   uOfferOwnerID   = sleOffer->getFieldAccount160 (sfAccount);
            STAmount        saOfferPays     = sleOffer->getFieldAmount (sfTakerGets);
            STAmount        saOfferGets     = sleOffer->getFieldAmount (sfTakerPays);

            STAmount        saOfferFunds;   // Funds of offer owner to payout.
            bool            bValid;

            bValid  =  bValidOffer (
                           sleOffer, uOfferIndex, uOfferOwnerID, saOfferPays, saOfferGets,
                           uTakerAccountID,
                           usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched,
                           saOfferFunds);

            if (bValid)
            {
                STAmount    saSubTakerPaid;
                STAmount    saSubTakerGot;
                STAmount    saTakerIssuerFee;
                STAmount    saOfferIssuerFee;
                STAmount    saOfferRate = STAmount::setRate (uTipQuality);

                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPaid: " << saTakerPaid.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerFunds: " << saTakerFunds.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saOfferFunds: " << saOfferFunds.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferPays: " << saOfferPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferGets: " << saOfferGets.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saOfferRate: " << saOfferRate.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerPays: " << saTakerPays.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerGets: " << saTakerGets.getFullText ();

                bool    bOfferDelete    = STAmount::applyOffer (
                                              bSell,
                                              lesActive.rippleTransferRate (uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID),
                                              lesActive.rippleTransferRate (uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID),
                                              saOfferRate,
                                              saOfferFunds,
                                              saTakerFunds,
                                              saOfferPays,
                                              saOfferGets,
                                              saSubTakerPays,
                                              saSubTakerGets,
                                              saSubTakerPaid,
                                              saSubTakerGot,
                                              saTakerIssuerFee,
                                              saOfferIssuerFee);

                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText ();
                WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();

                // Adjust offer

                // Offer owner will pay less.  Subtract what taker just got.
                sleOffer->setFieldAmount (sfTakerGets, saOfferPays -= saSubTakerGot);

                // Offer owner will get less.  Subtract what owner just paid.
                sleOffer->setFieldAmount (sfTakerPays, saOfferGets -= saSubTakerPaid);

                mEngine->entryModify (sleOffer);

                if (bOfferDelete)
                {
                    // Offer now fully claimed or now unfunded.
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete.";

                    usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success.

                    // Offer owner's account is no longer pristine.
                    usAccountTouched.insert (uOfferOwnerID);
                }
                else if (saSubTakerGot)
                {
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim.";

                    if (!saOfferPays.isPositive () || !saOfferGets.isPositive ())
                    {
                        WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT.";
                        bUnfunded   = true;
                        terResult   = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
                    }
                }
                else
                {
                    // Taker got nothing, probably due to rounding. Consider taker unfunded.
                    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: No claim.";

                    bUnfunded   = true;
                    terResult   = tesSUCCESS;                   // Done.
                }

                assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ());
                assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ());

                if (!bUnfunded)
                {
                    // Distribute funds. The sends charge appropriate fees which are implied by offer.

                    terResult   = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot);            // Offer owner pays taker.

                    if (tesSUCCESS == terResult)
                        terResult   = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid);           // Taker pays offer owner.

                    if (!bSell)
                    {
                        // Buy semantics: Reduce amount considered paid by taker's rate. Not by actual cost which is lower.
                        // That is, take less as to just satify our buy requirement.
                        STAmount    saTakerCould    = saTakerPays - saTakerPaid;    // Taker could pay.

                        if (saTakerFunds < saTakerCould)
                            saTakerCould    = saTakerFunds;

                        STAmount    saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays);

                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:   saTakerCould: " << saTakerCould.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:  saSubTakerGot: " << saSubTakerGot.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerRate: " << saTakerRate.getFullText ();
                        WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer:    saTakerUsed: " << saTakerUsed.getFullText ();

                        saSubTakerPaid  = std::min (saTakerCould, saTakerUsed);
                    }

                    saTakerPaid     += saSubTakerPaid;
                    saTakerGot      += saSubTakerGot;

                    if (tesSUCCESS == terResult)
                        terResult   = temUNCERTAIN;
                }
            }
        }
    }

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult);

    if (tesSUCCESS == terResult)
    {
        // On success, delete offers that became unfunded.
        BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame)
        {
            WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString ();

            terResult   = lesActive.offerDelete (uOfferIndex);

            if (tesSUCCESS != terResult)
                break;
        }
    }

    WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers< " << transToken (terResult);

    return terResult;
}

TER OfferCreateTransactor::doApply ()
{
    WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate> " << mTxn.getJson (0);
	const uint32			uTxFlags				= mTxn.getFlags ();
	const bool				bPassive				= isSetBit (uTxFlags, tfPassive);
	const bool				bImmediateOrCancel		= isSetBit (uTxFlags, tfImmediateOrCancel);
	const bool				bFillOrKill				= isSetBit (uTxFlags, tfFillOrKill);
	const bool				bSell					= isSetBit (uTxFlags, tfSell);
	STAmount				saTakerPays				= mTxn.getFieldAmount (sfTakerPays);
	STAmount				saTakerGets				= mTxn.getFieldAmount (sfTakerGets);

	if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ())
		return temBAD_AMOUNT;

	WriteLog (lsTRACE, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: saTakerPays=%s saTakerGets=%s")
			% saTakerPays.getFullText ()
			% saTakerGets.getFullText ());

	const uint160			uPaysIssuerID			= saTakerPays.getIssuer ();
	const uint160			uGetsIssuerID			= saTakerGets.getIssuer ();
	const uint32			uExpiration				= mTxn.getFieldU32 (sfExpiration);
	const bool				bHaveExpiration			= mTxn.isFieldPresent (sfExpiration);
	const bool				bHaveCancel				= mTxn.isFieldPresent (sfOfferSequence);
	const uint32			uCancelSequence			= mTxn.getFieldU32 (sfOfferSequence);
	const uint32			uAccountSequenceNext	= mTxnAccount->getFieldU32 (sfSequence);
	const uint32			uSequence				= mTxn.getSequence ();

	const uint256			uLedgerIndex			= Ledger::getOfferIndex (mTxnAccountID, uSequence);

	WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: Creating offer node: " << uLedgerIndex.ToString () << " uSequence=" << uSequence;

	const uint160			uPaysCurrency			= saTakerPays.getCurrency ();
	const uint160			uGetsCurrency			= saTakerGets.getCurrency ();
	const uint64			uRate					= STAmount::getRate (saTakerGets, saTakerPays);

	TER						terResult				= tesSUCCESS;
	uint256					uDirectory;		// Delete hints.
	uint64					uOwnerNode;
	uint64					uBookNode;

	LedgerEntrySet&			lesActive				= mEngine->getNodes ();
	LedgerEntrySet			lesCheckpoint			= lesActive;							// Checkpoint with just fees paid.
	lesActive.bumpSeq ();																	// Begin ledger variance.

	SLE::pointer			sleCreator				= mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID));

    if (uTxFlags & tfOfferCreateMask)
    {
        WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: Malformed transaction: Invalid flags set.";

        return temINVALID_FLAG;
    }
    else if (bImmediateOrCancel && bFillOrKill)
    {
        WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: Malformed transaction: both IoC and FoK set.";

        return temINVALID_FLAG;
    }
    else if (bHaveExpiration && !uExpiration)
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad expiration";

        terResult   = temBAD_EXPIRATION;
    }
    else if (bHaveExpiration && mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration)
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Expired transaction: offer expired";

        terResult   = tesSUCCESS;               // Only charged fee.
    }
    else if (saTakerPays.isNative () && saTakerGets.isNative ())
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: XRP for XRP";

        terResult   = temBAD_OFFER;
    }
    else if (!saTakerPays.isPositive () || !saTakerGets.isPositive ())
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad amount";

        terResult   = temBAD_OFFER;
    }
    else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: redundant offer";

        terResult   = temREDUNDANT;
    }
    else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency)
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: Bad currency.";

        terResult   = temBAD_CURRENCY;
    }
    else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID)
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad issuer";

        terResult   = temBAD_ISSUER;
    }
    else if (!lesActive.accountFunds (mTxnAccountID, saTakerGets).isPositive ())
    {
        WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: Offers must be at least partially funded.";

        terResult   = tecUNFUNDED_OFFER;
    }
    else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
    {
        WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uCancelSequence;

        terResult   = temBAD_SEQUENCE;
    }

	// Cancel offer.
    if (tesSUCCESS == terResult && bHaveCancel)
	{
        const uint256   uCancelIndex = Ledger::getOfferIndex (mTxnAccountID, uCancelSequence);
        SLE::pointer    sleCancel    = mEngine->entryCache (ltOFFER, uCancelIndex);

        if (sleCancel)
        {
            WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: uCancelSequence=" << uCancelSequence;

            terResult   = mEngine->getNodes ().offerDelete (sleCancel, uCancelIndex, mTxnAccountID);
        }
        else
        {
            WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: offer not found: "
                    << RippleAddress::createHumanAccountID (mTxnAccountID)
                    << " : " << uCancelSequence
                    << " : " << uCancelIndex.ToString ();
        }
	}

	// Make sure authorized to hold what taker will pay.
    if (tesSUCCESS == terResult && !saTakerPays.isNative ())
    {
        SLE::pointer        sleTakerPays    = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uPaysIssuerID));

        if (!sleTakerPays)
        {
            WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (uPaysIssuerID);

            terResult   = terNO_ACCOUNT;
        }
        else if (isSetBit (sleTakerPays->getFieldU32 (sfFlags), lsfRequireAuth))
        {
            SLE::pointer    sleRippleState  = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uPaysIssuerID, uPaysCurrency));
            bool            bHigh           = mTxnAccountID > uPaysIssuerID;

            if (!sleRippleState
                    || !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))
            {
                WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: can't receive IOUs from issuer without auth.";

                terResult   = terNO_AUTH;
            }
        }
    }

    STAmount        saPaid;
    STAmount        saGot;
    bool            bUnfunded   = false;
    const bool      bOpenLedger = isSetBit (mParams, tapOPEN_LEDGER);

    if (tesSUCCESS == terResult)
    {
        const uint256   uTakeBookBase   = Ledger::getBookBase (uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID);

        WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: take against book: %s for %s -> %s")
                % uTakeBookBase.ToString ()
                % saTakerGets.getFullText ()
                % saTakerPays.getFullText ());

        // Take using the parameters of the offer.
        WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText ();

        terResult   = takeOffers (
                          bOpenLedger,
                          bPassive,
                          bSell,
                          uTakeBookBase,
                          mTxnAccountID,
                          sleCreator,
                          saTakerGets,    // Reverse as we are the taker for taking.
                          saTakerPays,
                          saPaid,         // Buy semantics: how much would have sold at full price. Sell semantics: how much was sold.
                          saGot,          // How much was got.
                          bUnfunded);

        WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers=" << terResult;
        WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saPaid=" << saPaid.getFullText ();
        WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers:  saGot=" << saGot.getFullText ();

        if (tesSUCCESS == terResult && !bUnfunded)
        {
            saTakerPays     -= saGot;               // Reduce pay in from takers by what offer just got.
            saTakerGets     -= saPaid;              // Reduce pay out to takers by what srcAccount just paid.

            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: AFTER saTakerPays=" << saTakerPays.getFullText ();
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText ();
        }
    }

    WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText ();
    WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText ();
    WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID (mTxnAccountID);
    WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers:         FUNDS=" << lesActive.accountFunds (mTxnAccountID, saTakerGets).getFullText ();

    // WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID);
    // WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID);

    if (tesSUCCESS != terResult)
    {
        // Fail as is.
        nothing ();
    }
    else if (saTakerPays.isNegative () || saTakerGets.isNegative ())
    {
        // If ledger is not final, can vote no.
        terResult   = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING;
    }
    else if (bFillOrKill && (saTakerPays || saTakerGets))
    {
        // Fill or kill and have leftovers.
        lesActive.swapWith (lesCheckpoint);                                 // Restore with just fees paid.
    }
    else if (
        !saTakerPays                                                        // Wants nothing more.
        || !saTakerGets                                                     // Offering nothing more.
        || bImmediateOrCancel                                               // Do not persist.
        || !lesActive.accountFunds (mTxnAccountID, saTakerGets).isPositive () // Not funded.
        || bUnfunded)                                                       // Consider unfunded.
    {
        // Complete as is.
        nothing ();
    }
    else if (mPriorBalance.getNValue () < mEngine->getLedger ()->getReserve (sleCreator->getFieldU32 (sfOwnerCount) + 1))
    {
        if (bOpenLedger) // Ledger is not final, can vote no.
        {
            // Hope for more reserve to come in or more offers to consume.
            terResult   = tecINSUF_RESERVE_OFFER;
        }
        else if (!saPaid && !saGot)
        {
            // Ledger is final, insufficent reserve to create offer, processed nothing.

            terResult   = tecINSUF_RESERVE_OFFER;
        }
        else
        {
            // Ledger is final, insufficent reserve to create offer, processed something.

            // Consider the offer unfunded. Treat as tesSUCCESS.
            nothing ();
        }
    }
    else
    {
        // We need to place the remainder of the offer into its order book.
        WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s")
                % saTakerPays.getFullText ()
                % saTakerGets.getFullText ());

        // Add offer to owner's directory.
        terResult   = lesActive.dirAdd (uOwnerNode, Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
                                        BIND_TYPE (&Ledger::ownerDirDescriber, P_1, mTxnAccountID));


        if (tesSUCCESS == terResult)
        {
            lesActive.ownerCountAdjust (mTxnAccountID, 1, sleCreator); // Update owner count.

            uint256 uBookBase   = Ledger::getBookBase (uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID);

            WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: adding to book: %s : %s/%s -> %s/%s")
                    % uBookBase.ToString ()
                    % saTakerPays.getHumanCurrency ()
                    % RippleAddress::createHumanAccountID (saTakerPays.getIssuer ())
                    % saTakerGets.getHumanCurrency ()
                    % RippleAddress::createHumanAccountID (saTakerGets.getIssuer ()));

            uDirectory  = Ledger::getQualityIndex (uBookBase, uRate);   // Use original rate.

            // Add offer to order book.
            terResult   = lesActive.dirAdd (uBookNode, uDirectory, uLedgerIndex,
                                            BIND_TYPE (&Ledger::qualityDirDescriber, P_1, saTakerPays.getCurrency (), uPaysIssuerID,
                                                    saTakerGets.getCurrency (), uGetsIssuerID, uRate));
        }

        if (tesSUCCESS == terResult)
        {
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: sfAccount=" << RippleAddress::createHumanAccountID (mTxnAccountID);
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID (uPaysIssuerID);
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uGetsIssuerID=" << RippleAddress::createHumanAccountID (uGetsIssuerID);
            WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative ();
            WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative ();
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency ();
            WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency ();

            SLE::pointer            sleOffer        = mEngine->entryCreate (ltOFFER, uLedgerIndex);

            sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
            sleOffer->setFieldU32 (sfSequence, uSequence);
            sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
            sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
            sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
            sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
            sleOffer->setFieldU64 (sfBookNode, uBookNode);

            if (uExpiration)
                sleOffer->setFieldU32 (sfExpiration, uExpiration);

            if (bPassive)
                sleOffer->setFlag (lsfPassive);

            if (bSell)
                sleOffer->setFlag (lsfSell);

            WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: final terResult=%s sleOffer=%s")
                    % transToken (terResult)
                    % sleOffer->getJson (0));
        }
    }

    // On storing meta data, delete offers that were found unfunded to prevent encountering them in future.
    if (tesSUCCESS == terResult)
    {
        BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedFound)
        {

            WriteLog (lsINFO, OfferCreateTransactor) << "takeOffers: found unfunded: " << uOfferIndex.ToString ();

            terResult   = lesActive.offerDelete (uOfferIndex);

            if (tesSUCCESS != terResult)
                break;
        }
    }

    CondLog (tesSUCCESS != terResult, lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: final terResult=%s") % transToken (terResult));

    if (isTesSuccess (terResult))
        getApp().getOrderBookDB ().invalidate ();

    return terResult;
}

// vim:ts=4
