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

SETUP_LOG (PaymentTransactor)

#define RIPPLE_PATHS_MAX    6

TER PaymentTransactor::doApply ()
{
    // Ripple if source or destination is non-native or if there are paths.
    const uint32    uTxFlags        = mTxn.getFlags ();
    const bool      bPartialPayment = isSetBit (uTxFlags, tfPartialPayment);
    const bool      bLimitQuality   = isSetBit (uTxFlags, tfLimitQuality);
    const bool      bNoRippleDirect = isSetBit (uTxFlags, tfNoRippleDirect);
    const bool      bPaths          = mTxn.isFieldPresent (sfPaths);
    const bool      bMax            = mTxn.isFieldPresent (sfSendMax);
    const uint160   uDstAccountID   = mTxn.getFieldAccount160 (sfDestination);
    const STAmount  saDstAmount     = mTxn.getFieldAmount (sfAmount);
    const STAmount  saMaxAmount     = bMax
                                      ? mTxn.getFieldAmount (sfSendMax)
                                      : saDstAmount.isNative ()
                                      ? saDstAmount
                                      : STAmount (saDstAmount.getCurrency (), mTxnAccountID, saDstAmount.getMantissa (), saDstAmount.getExponent (), saDstAmount.isNegative ());
    const uint160   uSrcCurrency    = saMaxAmount.getCurrency ();
    const uint160   uDstCurrency    = saDstAmount.getCurrency ();
    const bool      bXRPDirect      = uSrcCurrency.isZero () && uDstCurrency.isZero ();

    WriteLog (lsINFO, PaymentTransactor) << boost::str (boost::format ("Payment> saMaxAmount=%s saDstAmount=%s")
                                         % saMaxAmount.getFullText ()
                                         % saDstAmount.getFullText ());

    if (!saDstAmount.isLegalNet () || !saMaxAmount.isLegalNet ())
        return temBAD_AMOUNT;

    if (uTxFlags & tfPaymentMask)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Invalid flags set.";

        return temINVALID_FLAG;
    }
    else if (!uDstAccountID)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Payment destination account not specified.";

        return temDST_NEEDED;
    }
    else if (bMax && !saMaxAmount.isPositive ())
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: bad max amount: " << saMaxAmount.getFullText ();

        return temBAD_AMOUNT;
    }
    else if (!saDstAmount.isPositive ())
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: bad dst amount: " << saDstAmount.getFullText ();

        return temBAD_AMOUNT;
    }
    else if (CURRENCY_BAD == uSrcCurrency || CURRENCY_BAD == uDstCurrency)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Bad currency.";

        return temBAD_CURRENCY;
    }
    else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths)
    {
        WriteLog (lsINFO, PaymentTransactor) << boost::str (boost::format ("Payment: Malformed transaction: Redundant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s")
                                             % mTxnAccountID.ToString ()
                                             % uDstAccountID.ToString ()
                                             % uSrcCurrency.ToString ()
                                             % uDstCurrency.ToString ());

        return temREDUNDANT;
    }
    else if (bMax && saMaxAmount == saDstAmount && saMaxAmount.getCurrency () == saDstAmount.getCurrency ())
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Redundant SendMax.";

        return temREDUNDANT_SEND_MAX;
    }
    else if (bXRPDirect && bMax)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: SendMax specified for XRP to XRP.";

        return temBAD_SEND_XRP_MAX;
    }
    else if (bXRPDirect && bPaths)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Paths specified for XRP to XRP.";

        return temBAD_SEND_XRP_PATHS;
    }
    else if (bXRPDirect && bPartialPayment)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Partial payment specified for XRP to XRP.";

        return temBAD_SEND_XRP_PARTIAL;
    }
    else if (bXRPDirect && bLimitQuality)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: Limit quality specified for XRP to XRP.";

        return temBAD_SEND_XRP_LIMIT;
    }
    else if (bXRPDirect && bNoRippleDirect)
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: No ripple direct specified for XRP to XRP.";

        return temBAD_SEND_XRP_NO_DIRECT;
    }

    SLE::pointer    sleDst  = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID));

    if (!sleDst)
    {
        // Destination account does not exist.

        if (!saDstAmount.isNative ())
        {
            WriteLog (lsINFO, PaymentTransactor) << "Payment: Delay transaction: Destination account does not exist.";

            // Another transaction could create the account and then this transaction would succeed.
            return tecNO_DST;
        }
        else if (isSetBit (mParams, tapOPEN_LEDGER) && bPartialPayment)
        {
            WriteLog (lsINFO, PaymentTransactor) << "Payment: Delay transaction: Partial payment not allowed to create account.";
            // Make retry work smaller, by rejecting this.

            // Another transaction could create the account and then this transaction would succeed.
            return telNO_DST_PARTIAL;
        }
        else if (saDstAmount.getNValue () < mEngine->getLedger ()->getReserve (0)) // Reserve is not scaled by load.
        {
            WriteLog (lsINFO, PaymentTransactor) << "Payment: Delay transaction: Destination account does not exist. Insufficent payment to create account.";

            // Another transaction could create the account and then this transaction would succeed.
            return tecNO_DST_INSUF_XRP;
        }

        // Create the account.
        sleDst  = mEngine->entryCreate (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID));

        sleDst->setFieldAccount (sfAccount, uDstAccountID);
        sleDst->setFieldU32 (sfSequence, 1);
    }
    else if ((sleDst->getFlags () & lsfRequireDestTag) && !mTxn.isFieldPresent (sfDestinationTag))
    {
        WriteLog (lsINFO, PaymentTransactor) << "Payment: Malformed transaction: DestinationTag required.";

        return tefDST_TAG_NEEDED;
    }
    else
    {
        mEngine->entryModify (sleDst);
    }

    TER         terResult;
    // XXX Should bMax be sufficient to imply ripple?
    const bool  bRipple = bPaths || bMax || !saDstAmount.isNative ();

    if (bRipple)
    {
        // Ripple payment

        STPathSet   spsPaths = mTxn.getFieldPathSet (sfPaths);
        std::vector<PathState::pointer> vpsExpanded;
        STAmount    saMaxAmountAct;
        STAmount    saDstAmountAct;

        try
        {
            terResult   = isSetBit (mParams, tapOPEN_LEDGER) && spsPaths.size () > RIPPLE_PATHS_MAX
                          ? telBAD_PATH_COUNT         // Too many paths for proposed ledger.
                          : RippleCalc::rippleCalc (
                              mEngine->getNodes (),
                              saMaxAmountAct,
                              saDstAmountAct,
                              vpsExpanded,
                              saMaxAmount,
                              saDstAmount,
                              uDstAccountID,
                              mTxnAccountID,
                              spsPaths,
                              bPartialPayment,
                              bLimitQuality,
                              bNoRippleDirect,        // Always compute for finalizing ledger.
                              false,                  // Not standalone, delete unfundeds.
                              isSetBit (mParams, tapOPEN_LEDGER));
        }
        catch (const std::exception& e)
        {
            WriteLog (lsINFO, PaymentTransactor) << "Payment: Caught throw: " << e.what ();

            terResult   = tefEXCEPTION;
        }
    }
    else
    {
        // Direct XRP payment.

        const uint32    uOwnerCount     = mTxnAccount->getFieldU32 (sfOwnerCount);
        const uint64    uReserve        = mEngine->getLedger ()->getReserve (uOwnerCount);

        // Make sure have enough reserve to send. Allow final spend to use reserve for fee.
        if (mPriorBalance < saDstAmount + uReserve)     // Reserve is not scaled by fee.
        {
            // Vote no. However, transaction might succeed, if applied in a different order.
            WriteLog (lsINFO, PaymentTransactor) << "";
            WriteLog (lsINFO, PaymentTransactor) << boost::str (boost::format ("Payment: Delay transaction: Insufficient funds: %s / %s (%d)")
                                                 % mPriorBalance.getText () % (saDstAmount + uReserve).getText () % uReserve);

            terResult   = tecUNFUNDED_PAYMENT;
        }
        else
        {
            mTxnAccount->setFieldAmount (sfBalance, mSourceBalance - saDstAmount);
            sleDst->setFieldAmount (sfBalance, sleDst->getFieldAmount (sfBalance) + saDstAmount);

            // re-arm the password change fee if we can and need to
            if ((sleDst->getFlags () & lsfPasswordSpent))
                sleDst->clearFlag (lsfPasswordSpent);

            terResult   = tesSUCCESS;
        }
    }

    std::string strToken;
    std::string strHuman;

    if (transResultInfo (terResult, strToken, strHuman))
    {
        WriteLog (lsINFO, PaymentTransactor) << boost::str (boost::format ("Payment: %s: %s") % strToken % strHuman);
    }
    else
    {
        assert (false);
    }

    return terResult;
}

// vim:ts=4
