package com.braintreepayments.api;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AppCompatActivity;

import android.text.TextUtils;
import android.util.Base64;

import com.braintreepayments.api.exceptions.BraintreeException;
import com.braintreepayments.api.exceptions.BrowserSwitchException;
import com.braintreepayments.api.exceptions.ErrorWithResponse;
import com.braintreepayments.api.interfaces.ConfigurationListener;
import com.braintreepayments.api.interfaces.HttpResponseCallback;
import com.braintreepayments.api.interfaces.PayPalApprovalCallback;
import com.braintreepayments.api.interfaces.PayPalApprovalHandler;
import com.braintreepayments.api.interfaces.PaymentMethodNonceCallback;
import com.braintreepayments.api.internal.BraintreeSharedPreferences;
import com.braintreepayments.api.internal.ManifestValidator;
import com.braintreepayments.api.models.BraintreeRequestCodes;
import com.braintreepayments.api.models.ClientToken;
import com.braintreepayments.api.models.Configuration;
import com.braintreepayments.api.models.PayPalAccountBuilder;
import com.braintreepayments.api.models.PayPalAccountNonce;
import com.braintreepayments.api.models.PayPalConfiguration;
import com.braintreepayments.api.models.PayPalPaymentResource;
import com.braintreepayments.api.models.PayPalRequest;
import com.braintreepayments.api.models.PaymentMethodNonce;
import com.braintreepayments.api.models.PostalAddress;
import com.braintreepayments.api.models.PostalAddressParser;
import com.paypal.android.sdk.onetouch.core.BillingAgreementRequest;
import com.paypal.android.sdk.onetouch.core.CheckoutRequest;
import com.paypal.android.sdk.onetouch.core.PayPalOneTouchCore;
import com.paypal.android.sdk.onetouch.core.Request;
import com.paypal.android.sdk.onetouch.core.Result;
import com.paypal.android.sdk.onetouch.core.enums.RequestTarget;
import com.paypal.android.sdk.onetouch.core.network.EnvironmentManager;
import com.paypal.android.sdk.onetouch.core.sdk.PendingRequest;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

/**
 * Used to create and tokenize PayPal accounts. For more information see the
 * documentation
 */
public class PayPal {
    private static final String REQUEST_KEY = "com.braintreepayments.api.PayPal.REQUEST_KEY";
    private static final String REQUEST_TYPE_KEY = "com.braintreepayments.api.PayPal.REQUEST_TYPE_KEY";
    private static final String PAYPAL_REQUEST_KEY = "com.braintreepayments.api.PayPal.PAYPAL_REQUEST_KEY";
    private static final String SETUP_BILLING_AGREEMENT_ENDPOINT = "paypal_hermes/setup_billing_agreement";
    private static final String CREATE_SINGLE_PAYMENT_ENDPOINT = "paypal_hermes/create_payment_resource";
    private static final String NO_SHIPPING_KEY = "no_shipping";
    private static final String ADDRESS_OVERRIDE_KEY = "address_override";
    private static final String LOCALE_CODE_KEY = "locale_code";
    private static final String DESCRIPTION_KEY = "description";
    private static final String AUTHORIZATION_FINGERPRINT_KEY = "authorization_fingerprint";
    private static final String TOKENIZATION_KEY = "client_key";
    private static final String RETURN_URL_KEY = "return_url";
    private static final String OFFER_CREDIT_KEY = "offer_paypal_credit";
    private static final String CANCEL_URL_KEY = "cancel_url";
    private static final String EXPERIENCE_PROFILE_KEY = "experience_profile";
    private static final String AMOUNT_KEY = "amount";
    private static final String CURRENCY_ISO_CODE_KEY = "currency_iso_code";
    private static final String INTENT_KEY = "intent";
    private static final String LANDING_PAGE_TYPE_KEY = "landing_page_type";
    private static final String USER_ACTION_KEY = "useraction";
    private static final String DISPLAY_NAME_KEY = "brand_name";
    private static final String SHIPPING_ADDRESS_KEY = "shipping_address";
    private static final String MERCHANT_ACCOUNT_ID = "merchant_account_id";

    /**
     * Starts the Billing Agreement flow for PayPal. This will launch the PayPal app if installed or
     * fall back to a browser switch.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param request A {@link PayPalRequest} used to customize the request.
     */
    public static void requestBillingAgreement(BraintreeFragment fragment, PayPalRequest request) {
        requestBillingAgreement(fragment, request, null);
    }

    /**
     * Starts the Billing Agreement flow for PayPal with custom PayPal approval handler.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param request A {@link PayPalRequest} used to customize the request.
     * @param handler A {@link PayPalApprovalHandler} for custom approval handling.
     */
    public static void requestBillingAgreement(BraintreeFragment fragment, PayPalRequest request,
            PayPalApprovalHandler handler) {
        if (request.getAmount() == null) {
            fragment.sendAnalyticsEvent("paypal.billing-agreement.selected");
            if (request.shouldOfferCredit()) {
                fragment.sendAnalyticsEvent("paypal.billing-agreement.credit.offered");
            }

            requestOneTimePayment(fragment, request, true, handler);
        } else {
            fragment.postCallback(new BraintreeException(
                    "There must be no amount specified for the Billing Agreement flow"));
        }
    }

    /**
     * Starts the Single Payment flow for PayPal. This will launch the PayPal app if installed or
     * fall back to a browser switch.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param request A {@link PayPalRequest} used to customize the request. An amount MUST be specified.
     */
    public static void requestOneTimePayment(BraintreeFragment fragment, PayPalRequest request) {
        requestOneTimePayment(fragment, request, null);
    }

    /**
     * Starts the Single Payment flow for PayPal with custom PayPal approval handler.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param request A {@link PayPalRequest} used to customize the request. An amount MUST be specified.
     * @param handler A {@link PayPalApprovalHandler} for custom approval handling.
     */
    public static void requestOneTimePayment(BraintreeFragment fragment, PayPalRequest request,
            PayPalApprovalHandler handler) {
        if (request.getAmount() != null) {
            fragment.sendAnalyticsEvent("paypal.single-payment.selected");
            if (request.shouldOfferCredit()) {
                fragment.sendAnalyticsEvent("paypal.single-payment.credit.offered");
            }

            requestOneTimePayment(fragment, request, false, handler);
        } else {
            fragment.postCallback(new BraintreeException("An amount must be specified for the Single Payment flow."));
        }
    }

    private static void requestOneTimePayment(final BraintreeFragment fragment, final PayPalRequest paypalRequest,
            final boolean isBillingAgreement, final PayPalApprovalHandler handler) {
        final HttpResponseCallback callback = new HttpResponseCallback() {
            @Override
            public void success(String responseBody) {
                final PayPalPaymentResource paypalPaymentResource;
                try {
                    paypalPaymentResource = PayPalPaymentResource.fromJson(responseBody);
                } catch (JSONException e) {
                    fragment.postCallback(e);
                    return;
                }

                String redirectUrl = Uri.parse(paypalPaymentResource.getRedirectUrl())
                        .buildUpon()
                        .appendQueryParameter(USER_ACTION_KEY, paypalRequest.getUserAction())
                        .toString();

                Request request;
                if (isBillingAgreement) {
                    request = getBillingAgreementRequest(fragment, redirectUrl);
                } else {
                    request = getCheckoutRequest(fragment, redirectUrl);
                }

                startPayPal(fragment, request, handler);
            }

            @Override
            public void failure(Exception e) {
                fragment.postCallback(e);
            }
        };

        fragment.waitForConfiguration(new ConfigurationListener() {
            @Override
            public void onConfigurationFetched(Configuration configuration) {
                if (!configuration.isPayPalEnabled()) {
                    fragment.postCallback(new BraintreeException("PayPal is not enabled"));
                    return;
                }

                if (!isManifestValid(fragment)) {
                    fragment.sendAnalyticsEvent("paypal.invalid-manifest");
                    fragment.postCallback(new BraintreeException("BraintreeBrowserSwitchActivity missing, " +
                            "incorrectly configured in AndroidManifest.xml or another app defines the same browser " +
                            "switch url as this app. See " +
                            "https://developers.braintreepayments.com/guides/client-sdk/android/v2#browser-switch " +
                            "for the correct configuration"));
                    return;
                }

                try {
                    persistPayPalRequest(fragment.getApplicationContext(), paypalRequest);
                    createPaymentResource(fragment, paypalRequest, isBillingAgreement, callback);
                } catch (JSONException | ErrorWithResponse | BraintreeException ex) {
                    fragment.postCallback(ex);
                }
            }
        });
    }

    /**
     * Create a PayPalPaymentResource on behalf of the merchant. To be used in the PayPal Checkout
     * flows for Single Payment and Billing Agreement.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param request A {@link PayPalRequest} used to customize the request.
     * @param isBillingAgreement A boolean. If true, this will use the Billing Agreement. Otherwise,
     *        PayPal will perform a Single Payment.
     * @param callback A callback on the http request.
     */
    private static void createPaymentResource(BraintreeFragment fragment, PayPalRequest request,
            boolean isBillingAgreement, HttpResponseCallback callback)
            throws JSONException, ErrorWithResponse, BraintreeException {
        String currencyCode = request.getCurrencyCode();
        if (currencyCode == null) {
            currencyCode = fragment.getConfiguration().getPayPal().getCurrencyIsoCode();
        }

        CheckoutRequest checkoutRequest = getCheckoutRequest(fragment, null);
        JSONObject parameters = new JSONObject()
                .put(RETURN_URL_KEY, checkoutRequest.getSuccessUrl())
                .put(CANCEL_URL_KEY, checkoutRequest.getCancelUrl())
                .put(OFFER_CREDIT_KEY, request.shouldOfferCredit());

        if (fragment.getAuthorization() instanceof ClientToken) {
            parameters.put(AUTHORIZATION_FINGERPRINT_KEY, fragment.getAuthorization().getBearer());
        } else {
            parameters.put(TOKENIZATION_KEY, fragment.getAuthorization().getBearer());
        }

        if (!isBillingAgreement) {
            parameters.put(AMOUNT_KEY, request.getAmount())
                    .put(CURRENCY_ISO_CODE_KEY, currencyCode)
                    .put(INTENT_KEY, request.getIntent());
        } else {
            if (!TextUtils.isEmpty(request.getBillingAgreementDescription())) {
                parameters.put(DESCRIPTION_KEY, request.getBillingAgreementDescription());
            }
        }

        JSONObject experienceProfile = new JSONObject();
        experienceProfile.put(NO_SHIPPING_KEY, !request.isShippingAddressRequired());
        experienceProfile.put(LANDING_PAGE_TYPE_KEY, request.getLandingPageType());
        String displayName = request.getDisplayName();
        if (TextUtils.isEmpty(displayName)) {
            displayName = fragment.getConfiguration().getPayPal().getDisplayName();
        }
        experienceProfile.put(DISPLAY_NAME_KEY, displayName);

        if (request.getLocaleCode() != null) {
            experienceProfile.put(LOCALE_CODE_KEY, request.getLocaleCode());
        }

        if (request.getShippingAddressOverride() != null) {
            experienceProfile.put(ADDRESS_OVERRIDE_KEY, !request.isShippingAddressEditable());

            JSONObject shippingAddressJson;
            if (isBillingAgreement) {
                shippingAddressJson = new JSONObject();
                parameters.put(SHIPPING_ADDRESS_KEY, shippingAddressJson);
            } else {
                shippingAddressJson = parameters;
            }

            PostalAddress shippingAddress = request.getShippingAddressOverride();
            shippingAddressJson.put(PostalAddressParser.LINE_1_KEY, shippingAddress.getStreetAddress());
            shippingAddressJson.put(PostalAddressParser.LINE_2_KEY, shippingAddress.getExtendedAddress());
            shippingAddressJson.put(PostalAddressParser.LOCALITY_KEY, shippingAddress.getLocality());
            shippingAddressJson.put(PostalAddressParser.REGION_KEY, shippingAddress.getRegion());
            shippingAddressJson.put(PostalAddressParser.POSTAL_CODE_UNDERSCORE_KEY, shippingAddress.getPostalCode());
            shippingAddressJson.put(PostalAddressParser.COUNTRY_CODE_UNDERSCORE_KEY, shippingAddress.getCountryCodeAlpha2());
            shippingAddressJson.put(PostalAddressParser.RECIPIENT_NAME_UNDERSCORE_KEY, shippingAddress.getRecipientName());
        } else {
            experienceProfile.put(ADDRESS_OVERRIDE_KEY, false);
        }

        if(request.getMerchantAccountId() != null) {
            parameters.put(MERCHANT_ACCOUNT_ID, request.getMerchantAccountId());
        }

        parameters.put(EXPERIENCE_PROFILE_KEY, experienceProfile);

        String apiUrl = isBillingAgreement ? SETUP_BILLING_AGREEMENT_ENDPOINT : CREATE_SINGLE_PAYMENT_ENDPOINT;
        String versionedPath = "/v1/" + apiUrl;
        fragment.getHttpClient().post(versionedPath, parameters.toString(), callback);
    }

    private static void startPayPal(final BraintreeFragment fragment, Request request, PayPalApprovalHandler handler) {
        persistRequest(fragment.getApplicationContext(), request);
        PayPalApprovalCallback callback = null;

        if (handler == null) {
            handler = getDefaultApprovalHandler(fragment);
        } else {
            callback = new PayPalApprovalCallback() {
                @Override
                public void onComplete(Intent data) {
                    PayPal.onActivityResult(fragment, AppCompatActivity.RESULT_OK, data);
                }

                @Override
                public void onCancel() {
                    fragment.postCancelCallback(BraintreeRequestCodes.PAYPAL);
                }
            };
        }
        handler.handleApproval(request, callback);
    }

    private static PayPalApprovalHandler getDefaultApprovalHandler(final BraintreeFragment fragment) {
        return new PayPalApprovalHandler() {
            @Override
            public void handleApproval(Request request, PayPalApprovalCallback paypalApprovalCallback) {
                PendingRequest pendingRequest =
                        PayPalOneTouchCore.getStartIntent(fragment.getApplicationContext(), request);

                String paymentType = paymentTypeForRequest(request);

                if (pendingRequest.isSuccess() && pendingRequest.getRequestTarget() == RequestTarget.wallet) {
                    fragment.sendAnalyticsEvent(paymentType + ".app-switch.started");

                    fragment.startActivityForResult(pendingRequest.getIntent(), BraintreeRequestCodes.PAYPAL);
                } else if (pendingRequest.isSuccess() && pendingRequest.getRequestTarget() == RequestTarget.browser) {
                    fragment.sendAnalyticsEvent(paymentType + ".browser-switch.started");

                    fragment.browserSwitch(BraintreeRequestCodes.PAYPAL, pendingRequest.getIntent());
                } else {
                    fragment.sendAnalyticsEvent(paymentType + ".initiate.failed");
                }
            }
        };
    }

    /**
     * The result from PayPal's request.
     *
     * @param fragment A {@link BraintreeFragment} used to process the request.
     * @param data Data associated with the result.
     */
    protected static void onActivityResult(final BraintreeFragment fragment, int resultCode, Intent data) {
        Request request = getPersistedRequest(fragment.getApplicationContext());
        String paymentType = paymentTypeForRequest(request);
        String switchType = switchTypeForIntent(data);
        String eventPrefix = paymentType + "." + switchType;

        if (resultCode == AppCompatActivity.RESULT_OK && data != null && request != null) {
            Result result = PayPalOneTouchCore.parseResponse(fragment.getApplicationContext(), request, data);
            switch (result.getResultType()) {
                case Error:
                    fragment.postCallback(new BrowserSwitchException(result.getError().getMessage()));
                    fragment.sendAnalyticsEvent(eventPrefix + ".failed");
                    break;
                case Cancel:
                    fragment.postCancelCallback(BraintreeRequestCodes.PAYPAL);
                    fragment.sendAnalyticsEvent(eventPrefix + ".canceled");
                    break;
                case Success:
                    onSuccess(fragment, data, request, result);
                    fragment.sendAnalyticsEvent(eventPrefix + ".succeeded");
                    break;
            }
        } else {
            fragment.sendAnalyticsEvent(eventPrefix + ".canceled");

            if (resultCode != AppCompatActivity.RESULT_CANCELED) {
                fragment.postCancelCallback(BraintreeRequestCodes.PAYPAL);
            }
        }
    }

    private static void onSuccess(final BraintreeFragment fragment, Intent data, Request request, Result result) {
        PayPalRequest paypalRequest = getPersistedPayPalRequest(fragment.getApplicationContext());
        TokenizationClient.tokenize(fragment, parseResponse(paypalRequest, request, result, data), new PaymentMethodNonceCallback() {
            @Override
            public void success(PaymentMethodNonce paymentMethodNonce) {
                if (paymentMethodNonce instanceof PayPalAccountNonce &&
                        ((PayPalAccountNonce) paymentMethodNonce).getCreditFinancing() != null) {
                    fragment.sendAnalyticsEvent("paypal.credit.accepted");
                }

                fragment.postCallback(paymentMethodNonce);
            }

            @Override
            public void failure(Exception exception) {
                fragment.postCallback(exception);
            }
        });
    }

    /**
     * Parse the PayPal response URL using OneTouchCore.
     *
     * @param paypalRequest Original {@link PayPalRequest} that started this flow.
     * @param result Context that received the result.
     * @param intent The {@link Intent} returned in result.
     * @return A {@link PayPalAccountBuilder} or null if the intent is invalid.
     */
    private static PayPalAccountBuilder parseResponse(PayPalRequest paypalRequest, Request request, Result result,
            Intent intent) {
        PayPalAccountBuilder paypalAccountBuilder = new PayPalAccountBuilder()
                .clientMetadataId(request.getClientMetadataId());

        if (paypalRequest != null && paypalRequest.getMerchantAccountId() != null) {
            paypalAccountBuilder.merchantAccountId(paypalRequest.getMerchantAccountId());
        }

        if (request instanceof CheckoutRequest && paypalRequest != null) {
            paypalAccountBuilder.intent(paypalRequest.getIntent());
        }

        if (isAppSwitch(intent)) {
            paypalAccountBuilder.source("paypal-app");
        } else {
            paypalAccountBuilder.source("paypal-browser");
        }

        paypalAccountBuilder.oneTouchCoreData(result.getResponse());

        return paypalAccountBuilder;
    }

    @VisibleForTesting
    static CheckoutRequest getCheckoutRequest(BraintreeFragment fragment, String redirectUrl) {
        CheckoutRequest request = populateRequestData(fragment, new CheckoutRequest())
                .approvalURL(redirectUrl);

        if (redirectUrl != null) {
            Uri uri = Uri.parse(redirectUrl);
            String pairingId = uri.getQueryParameter("token");

            if (pairingId != null) {
                request.pairingId(fragment.getApplicationContext(), pairingId);
            }
        }

        return request;
    }

    @VisibleForTesting
    static BillingAgreementRequest getBillingAgreementRequest(BraintreeFragment fragment, String redirectUrl) {
        BillingAgreementRequest request = populateRequestData(fragment, new BillingAgreementRequest())
                .approvalURL(redirectUrl);

        if (redirectUrl != null) {
            Uri uri = Uri.parse(redirectUrl);
            String pairingId = uri.getQueryParameter("ba_token");

            if (pairingId != null) {
                request.pairingId(fragment.getApplicationContext(), pairingId);
            }
        }

        return request;
    }

    private static <T extends Request> T populateRequestData(BraintreeFragment fragment, T request) {
        PayPalConfiguration paypalConfiguration = fragment.getConfiguration().getPayPal();
        String environment;
        switch (paypalConfiguration.getEnvironment()) {
            case "live":
                environment = EnvironmentManager.LIVE;
                break;
            case "offline":
                environment = EnvironmentManager.MOCK;
                break;
            default:
                environment = paypalConfiguration.getEnvironment();
                break;
        }

        String clientId = paypalConfiguration.getClientId();
        if (clientId == null && EnvironmentManager.MOCK.equals(environment)) {
            clientId = "FAKE-PAYPAL-CLIENT-ID";
        }

        request.environment(environment)
                .clientId(clientId)
                .cancelUrl(fragment.getReturnUrlScheme(), "cancel")
                .successUrl(fragment.getReturnUrlScheme(), "success");

        return request;
    }

    private static boolean isAppSwitch(Intent data) {
        return data.getData() == null;
    }

    private static void persistPayPalRequest(Context context, PayPalRequest paypalRequest) {
        Parcel parcel = Parcel.obtain();
        paypalRequest.writeToParcel(parcel, 0);
        BraintreeSharedPreferences.getSharedPreferences(context).edit()
                .putString(PAYPAL_REQUEST_KEY, Base64.encodeToString(parcel.marshall(), 0))
                .apply();
    }

    private static void persistRequest(Context context, Request request) {
        Parcel parcel = Parcel.obtain();
        request.writeToParcel(parcel, 0);
        BraintreeSharedPreferences.getSharedPreferences(context).edit()
                .putString(REQUEST_KEY, Base64.encodeToString(parcel.marshall(), 0))
                .putString(REQUEST_TYPE_KEY, request.getClass().getSimpleName())
                .apply();
    }

    @Nullable
    private static PayPalRequest getPersistedPayPalRequest(Context context) {
        SharedPreferences prefs = BraintreeSharedPreferences.getSharedPreferences(context);

        try {
            byte[] requestBytes = Base64.decode(prefs.getString(PAYPAL_REQUEST_KEY, ""), 0);
            Parcel parcel = Parcel.obtain();
            parcel.unmarshall(requestBytes, 0, requestBytes.length);
            parcel.setDataPosition(0);
            return PayPalRequest.CREATOR.createFromParcel(parcel);
        } catch (Exception ignored) {
        } finally {
            prefs.edit()
                    .remove(PAYPAL_REQUEST_KEY)
                    .apply();
        }

        return null;
    }

    @Nullable
    private static Request getPersistedRequest(Context context) {
        SharedPreferences prefs = BraintreeSharedPreferences.getSharedPreferences(context);

        try {
            byte[] requestBytes = Base64.decode(prefs.getString(REQUEST_KEY, ""), 0);
            Parcel parcel = Parcel.obtain();
            parcel.unmarshall(requestBytes, 0, requestBytes.length);
            parcel.setDataPosition(0);

            String type = prefs.getString(REQUEST_TYPE_KEY, "");
            if (BillingAgreementRequest.class.getSimpleName().equals(type)) {
                return BillingAgreementRequest.CREATOR.createFromParcel(parcel);
            } else if (CheckoutRequest.class.getSimpleName().equals(type)) {
                return CheckoutRequest.CREATOR.createFromParcel(parcel);
            }
        } catch (Exception ignored) {
        } finally {
            prefs.edit()
                    .remove(REQUEST_KEY)
                    .remove(REQUEST_TYPE_KEY)
                    .apply();
        }

        return null;
    }

    private static boolean isManifestValid(BraintreeFragment fragment) {
        return ManifestValidator.isUrlSchemeDeclaredInAndroidManifest(fragment.getApplicationContext(),
                fragment.getReturnUrlScheme(), BraintreeBrowserSwitchActivity.class);
    }

    private static String switchTypeForIntent(Intent data) {
        String switchType = "unknown";

        if (data != null) {
            if (data.getData() != null || data.getBooleanExtra(BraintreeFragment.EXTRA_WAS_BROWSER_SWITCH_RESULT, false)) {
                switchType = "browser-switch";
            } else {
                switchType = "app-switch";
            }
        }

        return switchType;
    }

    private static String paymentTypeForRequest(Request request) {
        if (request instanceof BillingAgreementRequest) {
            return "paypal.billing-agreement";
        } else if (request instanceof CheckoutRequest) {
            return "paypal.single-payment";
        } else {
            return "paypal.unknown";
        }
    }
}


package com.braintreepayments.api.models;

import android.os.Parcel;
import android.os.Parcelable;

import org.json.JSONException;
import org.json.JSONObject;

/**
 * Class to parse and contain 3D Secure lookup params
 */
public class ThreeDSecureLookup implements Parcelable {

    private static final String CARD_NONCE_KEY = "paymentMethod";
    private static final String LOOKUP_KEY = "lookup";
    private static final String ACS_URL_KEY = "acsUrl";
    private static final String MD_KEY = "md";
    private static final String TERM_URL_KEY = "termUrl";
    private static final String PA_REQ_KEY = "pareq";

    private CardNonce mCardNonce;
    private String mAcsUrl;
    private String mMd;
    private String mTermUrl;
    private String mPareq;

    /**
     * Used to parse a response from the Braintree Gateway to be used for 3D Secure.
     *
     * @param jsonString The json response from the Braintree Gateway 3D Secure lookup route.
     * @return The {@link com.braintreepayments.api.models.ThreeDSecureLookup} to use when
     *         performing 3D Secure authentication.
     * @throws JSONException when parsing fails.
     */
    public static ThreeDSecureLookup fromJson(String jsonString) throws JSONException {
        JSONObject json = new JSONObject(jsonString);

        ThreeDSecureLookup lookup = new ThreeDSecureLookup();

        CardNonce cardNonce = new CardNonce();
        cardNonce.fromJson(json.getJSONObject(CARD_NONCE_KEY));
        lookup.mCardNonce = cardNonce;

        JSONObject lookupJson = json.getJSONObject(LOOKUP_KEY);

        if (lookupJson.isNull(ACS_URL_KEY)) {
            lookup.mAcsUrl = null;
        } else {
            lookup.mAcsUrl = lookupJson.getString(ACS_URL_KEY);
        }

        lookup.mMd = lookupJson.getString(MD_KEY);
        lookup.mTermUrl = lookupJson.getString(TERM_URL_KEY);
        lookup.mPareq = lookupJson.getString(PA_REQ_KEY);

        return lookup;
    }

    /**
     * @return The {@link CardNonce} from the 3D Secure lookup.
     * If {@link #getAcsUrl()} is {@code null} this card may be used immediately and will benefit
     * from 3D Secure.
     */
    public CardNonce getCardNonce() {
        return mCardNonce;
    }

    /**
     * @return The acs url from a 3D Secure lookup. May be {@code null} in which case no
     * authentication will be performed.
     */
    public String getAcsUrl() {
        return mAcsUrl;
    }

    /**
     * @return The merchant descriptor from a 3D Secure lookup
     */
    public String getMd() {
        return mMd;
    }

    /**
     * @return The term url to be used for 3D Secure authentication
     */
    public String getTermUrl() {
        return mTermUrl;
    }

    /**
     * @return The pareq to be used for 3D Secure authentication
     */
    public String getPareq() {
        return mPareq;
    }

    public ThreeDSecureLookup() {}

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(mCardNonce, flags);
        dest.writeString(mAcsUrl);
        dest.writeString(mMd);
        dest.writeString(mTermUrl);
        dest.writeString(mPareq);
    }

    private ThreeDSecureLookup(Parcel in) {
        mCardNonce = in.readParcelable(CardNonce.class.getClassLoader());
        mAcsUrl = in.readString();
        mMd = in.readString();
        mTermUrl = in.readString();
        mPareq = in.readString();
    }

    public static final Creator<ThreeDSecureLookup> CREATOR = new Creator<ThreeDSecureLookup>() {
        public ThreeDSecureLookup createFromParcel(Parcel source) {
            return new ThreeDSecureLookup(source);
        }

        public ThreeDSecureLookup[] newArray(int size) {
            return new ThreeDSecureLookup[size];
        }
    };
}


package com.braintreepayments.api.models;

import android.text.TextUtils;

import com.braintreepayments.api.Json;

import org.json.JSONObject;

/**
 * Contains the remote PayPal configuration for the Braintree SDK.
 */
public class PayPalConfiguration {

    private static final String DISPLAY_NAME_KEY = "displayName";
    private static final String CLIENT_ID_KEY = "clientId";
    private static final String PRIVACY_URL_KEY = "privacyUrl";
    private static final String USER_AGREEMENT_URL_KEY = "userAgreementUrl";
    private static final String DIRECT_BASE_URL_KEY = "directBaseUrl";
    private static final String ENVIRONMENT_KEY = "environment";
    private static final String TOUCH_DISABLED_KEY = "touchDisabled";
    private static final String CURRENCY_ISO_CODE_KEY = "currencyIsoCode";

    private String mDisplayName;
    private String mClientId;
    private String mPrivacyUrl;
    private String mUserAgreementUrl;
    private String mDirectBaseUrl;
    private String mEnvironment;
    private boolean mTouchDisabled;
    private String mCurrencyIsoCode;

    /**
     * Parse an {@link PayPalConfiguration} from json.
     *
     * @param json The {@link JSONObject} to parse.
     * @return An {@link PayPalConfiguration} instance with data that was able to be parsed from
     *         the {@link JSONObject}.
     */
    public static PayPalConfiguration fromJson(JSONObject json) {
        if (json == null) {
            json = new JSONObject();
        }

        PayPalConfiguration payPalConfiguration = new PayPalConfiguration();
        payPalConfiguration.mDisplayName = Json.optString(json, DISPLAY_NAME_KEY, null);
        payPalConfiguration.mClientId = Json.optString(json, CLIENT_ID_KEY, null);
        payPalConfiguration.mPrivacyUrl = Json.optString(json, PRIVACY_URL_KEY, null);
        payPalConfiguration.mUserAgreementUrl = Json.optString(json, USER_AGREEMENT_URL_KEY, null);
        payPalConfiguration.mDirectBaseUrl = Json.optString(json, DIRECT_BASE_URL_KEY, null);
        payPalConfiguration.mEnvironment = Json.optString(json, ENVIRONMENT_KEY, null);
        payPalConfiguration.mTouchDisabled = json.optBoolean(TOUCH_DISABLED_KEY, true);
        payPalConfiguration.mCurrencyIsoCode = Json.optString(json, CURRENCY_ISO_CODE_KEY, null);

        return payPalConfiguration;
    }

    /**
     * @return {@code true} if PayPal is enabled, {@code false} otherwise.
     */
    public boolean isEnabled() {
        boolean enabled = !TextUtils.isEmpty(mEnvironment) && !TextUtils.isEmpty(mDisplayName) &&
                !TextUtils.isEmpty(mPrivacyUrl) && !TextUtils.isEmpty(mUserAgreementUrl);

        if (!"offline".equals(mEnvironment)) {
            enabled = enabled && !TextUtils.isEmpty(mClientId);
        }

        return enabled;
    }

    /**
     * @return the PayPal app display name.
     */
    public String getDisplayName() {
        return mDisplayName;
    }

    /**
     * @return the PayPal app client id.
     */
    public String getClientId() {
        return mClientId;
    }

    /**
     * @return the PayPal app privacy url.
     */
    public String getPrivacyUrl() {
        return mPrivacyUrl;
    }

    /**
     * @return the PayPal app user agreement url.
     */
    public String getUserAgreementUrl() {
        return mUserAgreementUrl;
    }

    /**
     * @return the url for custom PayPal environments.
     */
    public String getDirectBaseUrl() {
        return (TextUtils.isEmpty(mDirectBaseUrl) ? null : mDirectBaseUrl + "/v1/");
    }

    /**
     * @return the current environment for PayPal.
     */
    public String getEnvironment() {
        return mEnvironment;
    }

    /**
     * @return {@code true} if PayPal touch is currently disabled, {@code false} otherwise.
     */
    public boolean isTouchDisabled() {
        return mTouchDisabled;
    }

    /**
     * @return the PayPal currency code.
     */
    public String getCurrencyIsoCode() {
        return mCurrencyIsoCode;
    }
}


/*
 * Open Korean Text - Scala library to process Korean text
 *
 * Copyright 2014 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.openkoreantext.processor;

import org.openkoreantext.processor.phrase_extractor.KoreanPhraseExtractor;
import org.openkoreantext.processor.tokenizer.KoreanTokenizer.KoreanToken;
import org.openkoreantext.processor.tokenizer.Sentence;
import org.openkoreantext.processor.util.KoreanPos;
import scala.collection.Iterator;
import scala.collection.JavaConverters;
import scala.collection.Seq;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Java wrapper for TwitterKoreanProcessor using Builder pattern
 */
public final class OpenKoreanTextProcessorJava {

  /**
   * Normalize Korean text
   * 그랰ㅋㅋㅋㅋㅋㅋ -> 그래ㅋㅋ
   *
   * @param text Input text.
   * @return Normalized text.
   */
  public static CharSequence normalize(CharSequence text) {
    return OpenKoreanTextProcessor.normalize(text);
  }

  /**
   * Tokenize with the builder options.
   *
   * @param text Input text.
   * @return A list of Korean Tokens (run tokensToJavaList to transform to Java List)
   */
  public static Seq<KoreanToken> tokenize(CharSequence text) {
    return OpenKoreanTextProcessor.tokenize(
        text
    );
  }

  /**
   * Add user-defined words to the noun dictionary. Spaced words are ignored.
   *
   * @param words List of user nouns.
   */
  public static void addNounsToDictionary(List<String> words) {
    OpenKoreanTextProcessor.addNounsToDictionary(JavaConverters.asScalaBufferConverter(words).asScala());
  }

  /**
   * Add user-defined word List to the dictionary for the specified KoreanPos.
   *
   * @param pos   KoreanPos of words to add.
   * @param words Sequence of words to add.
   */
  public static void addWordsToDictionary(KoreanPosJava pos, List<String> words) {
    OpenKoreanTextProcessor.addWordsToDictionary(KoreanPos.withName(pos.toString()), JavaConverters.asScalaBufferConverter(words).asScala());
  }

  /**
   * Remove user-defined word List from the dictionary for the specified KoreanPos.
   *
   * @param pos   KoreanPos of words to add.
   * @param words Sequence of words to add.
   */
  public static void removeWordFromDictionary(KoreanPosJava pos, List<String> words) {
    OpenKoreanTextProcessor.removeWordsFromDictionary(KoreanPos.withName(pos.toString()), JavaConverters.asScalaBufferConverter(words).asScala());
  }

  /**
   * Transforms the tokenization output to List<KoreanTokenJava>
   *
   * @param tokens Korean tokens (output of tokenize(CharSequence text)).
   * @return List of KoreanTokenJava.
   */
  public static List<KoreanTokenJava> tokensToJavaKoreanTokenList(Seq<KoreanToken> tokens, boolean keepSpace) {
    Iterator<KoreanToken> tokenized = tokens.iterator();
    List<KoreanTokenJava> output = new LinkedList<>();
    while (tokenized.hasNext()) {
      KoreanToken token = tokenized.next();
      String stem = "";
      if (token.stem().nonEmpty()) {
        stem += token.stem().get();
      }
      if (keepSpace || token.pos() != KoreanPos.Space()) {
        output.add(new KoreanTokenJava(
            token.text(),
            KoreanPosJava.valueOf(token.pos().toString()),
            token.offset(),
            token.length(),
            token.unknown(),
            stem
        ));
      }
    }
    return output;
  }

  // Default behavior of keepSpace is false
  public static List<KoreanTokenJava> tokensToJavaKoreanTokenList(Seq<KoreanToken> tokens) {
    return tokensToJavaKoreanTokenList(tokens, false);
  }

  /**
   * Tokenize with the builder options into a String Iterable.
   *
   * @param tokens Korean tokens (output of tokenize(CharSequence text)).
   * @return List of token strings.
   */
  public static List<String> tokensToJavaStringList(Seq<KoreanToken> tokens, boolean keepSpace) {
    Iterator<KoreanToken> tokenized = tokens.iterator();
    List<String> output = new LinkedList<>();
    while (tokenized.hasNext()) {
      final KoreanToken token = tokenized.next();

      if (keepSpace || token.pos() != KoreanPos.Space()) {
        output.add(token.text());
      }
    }
    return output;
  }

  // Default behavior of keepSpace is false
  public static List<String> tokensToJavaStringList(Seq<KoreanToken> tokens) {
    return tokensToJavaStringList(tokens, false);
  }

  /**
   * Split input text into sentences.
   *
   * @param text Input text.
   * @return List of Sentence objects.
   */
  public static List<Sentence> splitSentences(CharSequence text) {
    return toJavaList(OpenKoreanTextProcessor.splitSentences(text));
  }

  /**
   * Extract phrases from Korean input text
   *
   * @param tokens Korean tokens (output of tokenize(CharSequence text)).
   * @return List of phrase CharSequences.
   */
  public static List<KoreanPhraseExtractor.KoreanPhrase> extractPhrases(Seq<KoreanToken> tokens, boolean filterSpam, boolean includeHashtags) {
    Seq<KoreanPhraseExtractor.KoreanPhrase> seq = OpenKoreanTextProcessor.extractPhrases(tokens, filterSpam, includeHashtags);
    return toJavaList(seq);
  }

  private static <T> List<T> toJavaList(Seq<T> seq) {
    ArrayList<T> javaList = new ArrayList<>();
    javaList.addAll(JavaConverters.asJavaCollectionConverter(seq).asJavaCollection());
    return javaList;
  }

  /**
   * Detokenize the input list of words.
   *
   * @param tokens List of words.
   * @return Detokenized string.
   */
  public static String detokenize(List<String> tokens) {
    return OpenKoreanTextProcessor.detokenize(JavaConverters.asScalaBufferConverter(tokens).asScala());
  }

  /**
   * Load the dictionary resources into memory.
   * <p>
   * Dictionary resources are loaded lazily by most API methods, however in some
   * instances it is useful to be able to trigger this loading manually.
   */
  public static void loadResources() {
    OpenKoreanTextProcessor.loadResources();
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 * ===========================
 *
 * This is an adapted copy of org.apache.lucene.analysis.util.CharArraySet
 * to avoid unnecessary conflicts.
 *
 */

package org.openkoreantext.processor.util;

import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * A simple class that stores Strings as char[]'s in a
 * hash table.  Note that this is not a general purpose
 * class.  For example, it cannot remove items from the
 * set, nor does it resize its hash table to be smaller,
 * etc.  It is designed to be quick to test if a char[]
 * is in the set without the necessity of converting it
 * to a String first.
 *

 * Please note: This class implements {@link java.util.Set Set} but
 * does not behave like it should in all cases. The generic type is
 * {@code Set<Object>}, because you can add any object to it,
 * that has a string representation. The add methods will use
 * {@link Object#toString} and store the result using a {@code char[]}
 * buffer. The same behavior have the {@code contains()} methods.
 * The {@link #iterator()} returns an {@code Iterator<char[]>}.
 */
public class CharArraySet extends AbstractSet<Object> {
  private static final CharArraySet EMPTY_SET = new CharArraySet(CharArrayMap.emptyMap());
  private static final Object PLACEHOLDER = new Object();

  private final CharArrayMap<Object> map;

  /**
   * Create set with enough capacity to hold startSize terms
   *
   * @param startSize the initial capacity
   */
  public CharArraySet(int startSize) {
    this(new CharArrayMap<>(startSize));
  }

  /**
   * Creates a set from a Collection of objects.
   *
   * @param c a collection whose elements to be placed into the set
   */
  private CharArraySet(Collection<?> c) {
    this(c.size());
    addAll(c);
  }

  /**
   * Create set from the specified map (internal only), used also by {@link CharArrayMap#keySet()}
   */
  CharArraySet(final CharArrayMap<Object> map) {
    this.map = map;
  }

  /**
   * Clears all entries in this set. This method is supported for reusing, but not {@link Set#remove}.
   */
  @Override
  public void clear() {
    map.clear();
  }

  /**
   * true if the len chars of text starting at off
   * are in the set
   */
  public boolean contains(char[] text, int off, int len) {
    return map.containsKey(text, off, len);
  }

  /**
   * true if the CharSequence is in the set
   */
  public boolean contains(CharSequence cs) {
    return map.containsKey(cs);
  }

  @Override
  public boolean contains(Object o) {
    return map.containsKey(o);
  }

  @Override
  public boolean add(Object o) {
    return map.put(o, PLACEHOLDER) == null;
  }

  /**
   * Add this CharSequence into the set
   */
  public boolean add(CharSequence text) {
    return map.put(text, PLACEHOLDER) == null;
  }

  /**
   * Add this String into the set
   */
  public boolean add(String text) {
    return map.put(text, PLACEHOLDER) == null;
  }

  /**
   * Add this char[] directly to the set.
   * If ignoreCase is true for this Set, the text array will be directly modified.
   * The user should never modify this text array after calling this method.
   */
  public boolean add(char[] text) {
    return map.put(text, PLACEHOLDER) == null;
  }

  public boolean remove(String text) {
    map.remove(text);
    return !map.containsKey(text);
  }

  public boolean removeAll(List<String> words) {
    boolean removed = false;
    for (String word : words) {
      removed |= remove(word);
    }
    return removed;
  }

  @Override
  public int size() {
    return map.size();
  }

  /**
   * Returns an unmodifiable {@link CharArraySet}. This allows to provide
   * unmodifiable views of internal sets for "read-only" use.
   *
   * @param set a set for which the unmodifiable set is returned.
   * @return an new unmodifiable {@link CharArraySet}.
   * @throws NullPointerException if the given set is null.
   */
  public static CharArraySet unmodifiableSet(CharArraySet set) {
    if (set == null)
      throw new NullPointerException("Given set is null");
    if (set == EMPTY_SET)
      return EMPTY_SET;
    if (set.map instanceof CharArrayMap.UnmodifiableCharArrayMap)
      return set;
    return new CharArraySet(CharArrayMap.unmodifiableMap(set.map));
  }

  /**
   * Returns a copy of the given set as a {@link CharArraySet}. If the given set
   * is a {@link CharArraySet} the ignoreCase property will be preserved.
   *
   * @param set a set to copy
   * @return a copy of the given set as a {@link CharArraySet}. If the given set
   * is a {@link CharArraySet} the ignoreCase property as well as the
   * matchVersion will be of the given set will be preserved.
   */
  public static CharArraySet copy(final Set<?> set) {
    if (set == EMPTY_SET)
      return EMPTY_SET;
    if (set instanceof CharArraySet) {
      final CharArraySet source = (CharArraySet) set;
      return new CharArraySet(CharArrayMap.copy(source.map));
    }
    return new CharArraySet(set);
  }

  /**
   * Returns an {@link Iterator} for {@code char[]} instances in this set.
   */
  @Override
  @SuppressWarnings("unchecked")
  public Iterator<Object> iterator() {
    // use the AbstractSet#keySet()'s iterator (to not produce endless recursion)
    return map.originalKeySet().iterator();
  }

  @Override
  public String toString() {
    final StringBuilder sb = new StringBuilder("[");
    for (Object item : this) {
      if (sb.length() > 1) sb.append(", ");
      if (item instanceof char[]) {
        sb.append((char[]) item);
      } else {
        sb.append(item);
      }
    }
    return sb.append(']').toString();
  }
}


package org.jcodec.common.io;

import static java.lang.Math.min;
import static org.jcodec.platform.Platform.stringFromBytes;

import org.jcodec.common.ArrayUtil;
import org.jcodec.common.AutoFileChannelWrapper;
import org.jcodec.platform.Platform;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * This class is part of JCodec ( www.jcodec.org ) This software is distributed
 * under FreeBSD License
 *
 * @author The JCodec project
 *
 */
public class NIOUtils {

    public static ByteBuffer search(ByteBuffer buffer, int n, byte[] param) {
        ByteBuffer result = buffer.duplicate();
        int step = 0, rem = buffer.position();
        while (buffer.hasRemaining()) {
            int b = buffer.get();
            if (b == param[step]) {
                ++step;
                if (step == param.length) {
                    if (n == 0) {
                        buffer.position(rem);
                        result.limit(buffer.position());
                        break;
                    }
                    n--;
                    step = 0;
                }
            } else {
                if (step != 0) {
                    step = 0;
                    ++rem;
                    buffer.position(rem);
                } else
                    rem = buffer.position();
            }
        }
        return result;
    }

    public static final ByteBuffer read(ByteBuffer buffer, int count) {
        ByteBuffer slice = buffer.duplicate();
        int limit = buffer.position() + count;
        slice.limit(limit);
        buffer.position(limit);
        return slice;
    }

    public static ByteBuffer fetchFromFile(File file) throws IOException {
        return NIOUtils.fetchFromFileL(file, (int) file.length());
    }

    public static ByteBuffer fetchFromChannel(ReadableByteChannel ch, int size) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(size);
        NIOUtils.readFromChannel(ch, buf);
        buf.flip();
        return buf;
    }

    public static ByteBuffer fetchAllFromChannel(SeekableByteChannel ch) throws IOException {
        List<ByteBuffer> buffers = new ArrayList<ByteBuffer>();
        ByteBuffer buf;
        do {
            buf = fetchFromChannel(ch, 1 << 20);
            buffers.add(buf);
        } while(buf.hasRemaining());

        return combineBuffers(buffers);
    }

    /**
     * Reads size amount of bytes from ch into a new ByteBuffer allocated from a
     * buffer buf
     *
     * @param buf
     * @param ch
     * @param size
     * @return
     * @throws IOException
     */
    public static ByteBuffer fetchFrom(ByteBuffer buf, ReadableByteChannel ch, int size) throws IOException {
        ByteBuffer result = buf.duplicate();
        result.limit(size);
        NIOUtils.readFromChannel(ch, result);
        result.flip();
        return result;
    }

    public static ByteBuffer fetchFromFileL(File file, int length) throws IOException {
        FileChannel is = null;
        try {
            is = new FileInputStream(file).getChannel();
            return fetchFromChannel(is, length);
        } finally {
            closeQuietly(is);
        }
    }

    public static void writeTo(ByteBuffer buffer, File file) throws IOException {
        FileChannel out = null;
        try {
            out = new FileOutputStream(file).getChannel();
            out.write(buffer);
        } finally {
            closeQuietly(out);
        }
    }

    public static byte[] toArray(ByteBuffer buffer) {
        byte[] result = new byte[buffer.remaining()];
        buffer.duplicate().get(result);
        return result;
    }

    public static byte[] toArrayL(ByteBuffer buffer, int count) {
        byte[] result = new byte[Math.min(buffer.remaining(), count)];
        buffer.duplicate().get(result);
        return result;
    }

    public static int readL(ReadableByteChannel channel, ByteBuffer buffer, int length) throws IOException {
        ByteBuffer fork = buffer.duplicate();
        fork.limit(min(fork.position() + length, fork.limit()));
        while (channel.read(fork) != -1 && fork.hasRemaining())
            ;
        buffer.position(fork.position());
        return buffer.position() == 0 ? -1 : buffer.position();
    }

    public static int readFromChannel(ReadableByteChannel channel, ByteBuffer buffer) throws IOException {
        int rem = buffer.position();
        while (channel.read(buffer) != -1 && buffer.hasRemaining())
            ;
        return buffer.position() - rem;
    }

    public static void write(ByteBuffer to, ByteBuffer from) {
        if (from.hasArray()) {
            to.put(from.array(), from.arrayOffset() + from.position(), Math.min(to.remaining(), from.remaining()));
        } else {
            to.put(toArrayL(from, to.remaining()));
        }
    }

    public static void writeL(ByteBuffer to, ByteBuffer from, int count) {
        if (from.hasArray()) {
            to.put(from.array(), from.arrayOffset() + from.position(), Math.min(from.remaining(), count));
        } else {
            to.put(toArrayL(from, count));
        }
    }

    public static void fill(ByteBuffer buffer, byte val) {
        while (buffer.hasRemaining())
            buffer.put(val);
    }

    public static final MappedByteBuffer map(String fileName) throws IOException {
        return mapFile(new File(fileName));
    }

    public static final MappedByteBuffer mapFile(File file) throws IOException {
        FileInputStream is = new FileInputStream(file);
        MappedByteBuffer map = is.getChannel().map(MapMode.READ_ONLY, 0, file.length());
        is.close();
        return map;
    }

    public static int skip(ByteBuffer buffer, int count) {
        int toSkip = Math.min(buffer.remaining(), count);
        buffer.position(buffer.position() + toSkip);
        return toSkip;
    }

    public static ByteBuffer from(ByteBuffer buffer, int offset) {
        ByteBuffer dup = buffer.duplicate();
        dup.position(dup.position() + offset);
        return dup;
    }

    public static ByteBuffer combineBuffers(Iterable<ByteBuffer> picture) {
        int size = 0;
        for (ByteBuffer byteBuffer : picture) {
            size += byteBuffer.remaining();
        }
        ByteBuffer result = ByteBuffer.allocate(size);
        for (ByteBuffer byteBuffer : picture) {
            write(result, byteBuffer);
        }
        result.flip();
        return result;
    }

    public static boolean combineBuffersInto(ByteBuffer dup, List<ByteBuffer> buffers) {
        throw new RuntimeException("Stan");
    }

    public static String readString(ByteBuffer buffer, int len) {
        return stringFromBytes(toArray(read(buffer, len)));
    }

    public static String readPascalStringL(ByteBuffer buffer, int maxLen) {
        ByteBuffer sub = read(buffer, maxLen + 1);
        return stringFromBytes(toArray(NIOUtils.read(sub, Math.min(sub.get() & 0xff, maxLen))));
    }

    public static void writePascalStringL(ByteBuffer buffer, String string, int maxLen) {
        buffer.put((byte) string.length());
        buffer.put(asciiString(string));
        skip(buffer, maxLen - string.length());
    }

    public static byte[] asciiString(String fourcc) {
        return Platform.getBytes(fourcc);
    }

    public static void writePascalString(ByteBuffer buffer, String name) {
        buffer.put((byte) name.length());
        buffer.put(asciiString(name));
    }

    public static String readPascalString(ByteBuffer buffer) {
        return readString(buffer, buffer.get() & 0xff);
    }

    public static String readNullTermString(ByteBuffer buffer) {
        return readNullTermStringCharset(buffer, Platform.UTF_8);
    }

    public static String readNullTermStringCharset(ByteBuffer buffer, String charset) {
        ByteBuffer fork = buffer.duplicate();
        while (buffer.hasRemaining() && buffer.get() != 0)
            ;
        if (buffer.hasRemaining())
            fork.limit(buffer.position() - 1);
        return Platform.stringFromCharset(toArray(fork), charset);
    }

    public static ByteBuffer readBuf(ByteBuffer buffer) {
        ByteBuffer result = buffer.duplicate();
        buffer.position(buffer.limit());
        return result;
    }

    public static void copy(ReadableByteChannel _in, WritableByteChannel out, long amount) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(0x10000);
        int read;
        do {
            buf.position(0);
            buf.limit((int) Math.min(amount, buf.capacity()));
            read = _in.read(buf);
            if (read != -1) {
                buf.flip();
                out.write(buf);
                amount -= read;
            }
        } while (read != -1 && amount > 0);
    }

    public static void closeQuietly(Closeable channel) {
        if (channel == null)
            return;
        try {
            channel.close();
        } catch (IOException e) {
        }
    }

    public static byte readByte(ReadableByteChannel channel) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(1);
        channel.read(buf);
        buf.flip();
        return buf.get();
    }

    public static byte[] readNByte(ReadableByteChannel channel, int n) throws IOException {
        byte[] result = new byte[n];
        channel.read(ByteBuffer.wrap(result));
        return result;
    }

    public static int readInt(ReadableByteChannel channel) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(4);
        channel.read(buf);
        buf.flip();
        return buf.getInt();
    }

    public static int readIntOrder(ReadableByteChannel channel, ByteOrder order) throws IOException {
        ByteBuffer buf = (ByteBuffer) ByteBuffer.allocate(4).order(order);
        channel.read(buf);
        buf.flip();
        return buf.getInt();
    }

    public static void writeByte(WritableByteChannel channel, byte value) throws IOException {
        channel.write((ByteBuffer) ByteBuffer.allocate(1).put(value).flip());
    }

    public static void writeIntOrder(WritableByteChannel channel, int value, ByteOrder order) throws IOException {
        ByteBuffer order2 = (ByteBuffer) ByteBuffer.allocate(4).order(order);
        channel.write((ByteBuffer) order2.putInt(value).flip());
    }

    public static void writeIntLE(WritableByteChannel channel, int value) throws IOException {
        ByteBuffer allocate = ByteBuffer.allocate(4);
        allocate.order(ByteOrder.LITTLE_ENDIAN);
        channel.write((ByteBuffer) allocate.putInt(value).flip());
    }

    public static void writeInt(WritableByteChannel channel, int value) throws IOException {
        channel.write((ByteBuffer) ByteBuffer.allocate(4).putInt(value).flip());
    }

    public static void writeLong(WritableByteChannel channel, long value) throws IOException {
        channel.write((ByteBuffer) ByteBuffer.allocate(8).putLong(value).flip());
    }

    public static FileChannelWrapper readableChannel(File file) throws FileNotFoundException {
        return new FileChannelWrapper(new FileInputStream(file).getChannel());
    }

    public static FileChannelWrapper writableChannel(File file) throws FileNotFoundException {
        return new FileChannelWrapper(new FileOutputStream(file).getChannel());
    }

    public static FileChannelWrapper rwChannel(File file) throws FileNotFoundException {
        return new FileChannelWrapper(new RandomAccessFile(file, "rw").getChannel());
    }

    public static FileChannelWrapper readableFileChannel(String file) throws FileNotFoundException {
        return new FileChannelWrapper(new FileInputStream(file).getChannel());
    }

    public static FileChannelWrapper writableFileChannel(String file) throws FileNotFoundException {
        return new FileChannelWrapper(new FileOutputStream(file).getChannel());
    }

    public static FileChannelWrapper rwFileChannel(String file) throws FileNotFoundException {
        return new FileChannelWrapper(new RandomAccessFile(file, "rw").getChannel());
    }

    public static AutoFileChannelWrapper autoChannel(File file) throws IOException {
        return new AutoFileChannelWrapper(file);
    }

    public static ByteBuffer duplicate(ByteBuffer bb) {
        ByteBuffer out = ByteBuffer.allocate(bb.remaining());
        out.put(bb.duplicate());
        out.flip();
        return out;
    }

    public static int find(List<ByteBuffer> catalog, ByteBuffer key) {
        byte[] keyA = toArray(key);
        for (int i = 0; i < catalog.size(); i++) {
            if (Platform.arrayEqualsByte(toArray(catalog.get(i)), keyA))
                return i;
        }
        return -1;
    }

    public static interface FileReaderListener {
        void progress(int percentDone);
    }

    public static abstract class FileReader {
        private int oldPd;

        protected abstract void data(ByteBuffer data, long filePos);

        protected abstract void done();

        public void readChannel(SeekableByteChannel ch, int bufferSize, FileReaderListener listener) throws IOException {
            ByteBuffer buf = ByteBuffer.allocate(bufferSize);
            long size = ch.size();
            for (long pos = ch.position(); ch.read(buf) != -1; pos = ch.position()) {
                buf.flip();
                data(buf, pos);
                buf.flip();
                if (listener != null) {
                    int newPd = (int) (100 * pos / size);
                    if (newPd != oldPd)
                        listener.progress(newPd);
                    oldPd = newPd;
                }
            }
            done();
        }

        public void readFile(File source, int bufferSize, FileReaderListener listener) throws IOException {
            SeekableByteChannel ch = null;
            try {
                ch = NIOUtils.readableChannel(source);
                readChannel(ch, bufferSize, listener);
            } finally {
                NIOUtils.closeQuietly(ch);
            }
        }
    }

    public static byte getRel(ByteBuffer bb, int rel) {
        return bb.get(bb.position() + rel);
    }

    public static ByteBuffer cloneBuffer(ByteBuffer pesBuffer) {
        ByteBuffer res = ByteBuffer.allocate(pesBuffer.remaining());
        res.put(pesBuffer.duplicate());
        res.clear();
        return res;
    }

    public static ByteBuffer clone(ByteBuffer byteBuffer) {
        ByteBuffer result = ByteBuffer.allocate(byteBuffer.remaining());
        result.put(byteBuffer.duplicate());
        result.flip();
        return result;
    }

    public static ByteBuffer asByteBuffer(byte[] bytes) {
        return ByteBuffer.wrap(bytes);
    }

    public static ByteBuffer asByteBufferInt(int[] ints) {
        return asByteBuffer(ArrayUtil.toByteArray(ints));
    }

    public static void relocateLeftover(ByteBuffer bb) {
        int pos;
        for (pos = 0; bb.hasRemaining(); pos++) {
            bb.put(pos, bb.get());
        }
        bb.position(pos);
        bb.limit(bb.capacity());
    }
}


package net.sourceforge.jaad.aac.sbr;

/**
 * This class is part of JAAD ( jaadec.sourceforge.net ) that is distributed
 * under the Public Domain license. Code changes provided by the JCodec project
 * are distributed under FreeBSD license.
 *
 * @author in-somnia
 */
class NoiseEnvelope implements SBRConstants {

	private static final float[] E_deq_tab = {
		64.0f, 128.0f, 256.0f, 512.0f, 1024.0f, 2048.0f, 4096.0f, 8192.0f,
		16384.0f, 32768.0f, 65536.0f, 131072.0f, 262144.0f, 524288.0f, 1.04858E+006f, 2.09715E+006f,
		4.1943E+006f, 8.38861E+006f, 1.67772E+007f, 3.35544E+007f, 6.71089E+007f, 1.34218E+008f, 2.68435E+008f, 5.36871E+008f,
		1.07374E+009f, 2.14748E+009f, 4.29497E+009f, 8.58993E+009f, 1.71799E+010f, 3.43597E+010f, 6.87195E+010f, 1.37439E+011f,
		2.74878E+011f, 5.49756E+011f, 1.09951E+012f, 2.19902E+012f, 4.39805E+012f, 8.79609E+012f, 1.75922E+013f, 3.51844E+013f,
		7.03687E+013f, 1.40737E+014f, 2.81475E+014f, 5.6295E+014f, 1.1259E+015f, 2.2518E+015f, 4.5036E+015f, 9.0072E+015f,
		1.80144E+016f, 3.60288E+016f, 7.20576E+016f, 1.44115E+017f, 2.8823E+017f, 5.76461E+017f, 1.15292E+018f, 2.30584E+018f,
		4.61169E+018f, 9.22337E+018f, 1.84467E+019f, 3.68935E+019f, 7.3787E+019f, 1.47574E+020f, 2.95148E+020f, 5.90296E+020f
	};

	/* table for Q_div2 values when no coupling */
	private static final float[] Q_div2_tab = {
		0.984615f, 0.969697f,
		0.941176f, 0.888889f,
		0.8f, 0.666667f,
		0.5f, 0.333333f,
		0.2f, 0.111111f,
		0.0588235f, 0.030303f,
		0.0153846f, 0.00775194f,
		0.00389105f, 0.00194932f,
		0.00097561f, 0.000488043f,
		0.000244081f, 0.000122055f,
		6.10314E-005f, 3.05166E-005f,
		1.52586E-005f, 7.62934E-006f,
		3.81468E-006f, 1.90734E-006f,
		9.53673E-007f, 4.76837E-007f,
		2.38419E-007f, 1.19209E-007f,
		5.96046E-008f};

	private static final float[][] Q_div2_tab_left = {
		{0.0302959f, 0.111015f, 0.332468f, 0.663212f, 0.882759f, 0.962406f, 0.984615f, 0.990329f, 0.991768f, 0.992128f, 0.992218f, 0.992241f, 0.992246f},
		{0.0153809f, 0.0587695f, 0.199377f, 0.496124f, 0.790123f, 0.927536f, 0.969697f, 0.980843f, 0.98367f, 0.984379f, 0.984556f, 0.984601f, 0.984612f},
		{0.00775006f, 0.0302744f, 0.110727f, 0.329897f, 0.653061f, 0.864865f, 0.941176f, 0.962406f, 0.967864f, 0.969238f, 0.969582f, 0.969668f, 0.96969f},
		{0.0038901f, 0.0153698f, 0.0586081f, 0.197531f, 0.484848f, 0.761905f, 0.888889f, 0.927536f, 0.937729f, 0.940312f, 0.94096f, 0.941122f, 0.941163f},
		{0.00194884f, 0.00774443f, 0.0301887f, 0.109589f, 0.32f, 0.615385f, 0.8f, 0.864865f, 0.882759f, 0.887348f, 0.888503f, 0.888792f, 0.888865f},
		{0.000975372f, 0.00388727f, 0.0153257f, 0.057971f, 0.190476f, 0.444444f, 0.666667f, 0.761905f, 0.790123f, 0.797508f, 0.799375f, 0.799844f, 0.799961f},
		{0.000487924f, 0.00194742f, 0.00772201f, 0.0298507f, 0.105263f, 0.285714f, 0.5f, 0.615385f, 0.653061f, 0.663212f, 0.6658f, 0.66645f, 0.666612f},
		{0.000244021f, 0.000974659f, 0.00387597f, 0.0151515f, 0.0555556f, 0.166667f, 0.333333f, 0.444444f, 0.484848f, 0.496124f, 0.499025f, 0.499756f, 0.499939f},
		{0.000122026f, 0.000487567f, 0.00194175f, 0.00763359f, 0.0285714f, 0.0909091f, 0.2f, 0.285714f, 0.32f, 0.329897f, 0.332468f, 0.333116f, 0.333279f},
		{6.10165E-005f, 0.000243843f, 0.000971817f, 0.00383142f, 0.0144928f, 0.047619f, 0.111111f, 0.166667f, 0.190476f, 0.197531f, 0.199377f, 0.199844f, 0.199961f},
		{3.05092E-005f, 0.000121936f, 0.000486145f, 0.00191939f, 0.00729927f, 0.0243902f, 0.0588235f, 0.0909091f, 0.105263f, 0.109589f, 0.110727f, 0.111015f, 0.111087f},
		{1.52548E-005f, 6.09719E-005f, 0.000243132f, 0.000960615f, 0.003663f, 0.0123457f, 0.030303f, 0.047619f, 0.0555556f, 0.057971f, 0.0586081f, 0.0587695f, 0.05881f},
		{7.62747E-006f, 3.04869E-005f, 0.000121581f, 0.000480538f, 0.00183486f, 0.00621118f, 0.0153846f, 0.0243902f, 0.0285714f, 0.0298507f, 0.0301887f, 0.0302744f, 0.0302959f},
		{3.81375E-006f, 1.52437E-005f, 6.0794E-005f, 0.000240327f, 0.000918274f, 0.00311526f, 0.00775194f, 0.0123457f, 0.0144928f, 0.0151515f, 0.0153257f, 0.0153698f, 0.0153809f},
		{1.90688E-006f, 7.62189E-006f, 3.03979E-005f, 0.000120178f, 0.000459348f, 0.00156006f, 0.00389105f, 0.00621118f, 0.00729927f, 0.00763359f, 0.00772201f, 0.00774443f, 0.00775006f},
		{9.53441E-007f, 3.81096E-006f, 1.51992E-005f, 6.00925E-005f, 0.000229727f, 0.00078064f, 0.00194932f, 0.00311526f, 0.003663f, 0.00383142f, 0.00387597f, 0.00388727f, 0.0038901f},
		{4.76721E-007f, 1.90548E-006f, 7.59965E-006f, 3.00472E-005f, 0.000114877f, 0.000390472f, 0.00097561f, 0.00156006f, 0.00183486f, 0.00191939f, 0.00194175f, 0.00194742f, 0.00194884f},
		{2.3836E-007f, 9.52743E-007f, 3.79984E-006f, 1.50238E-005f, 5.74416E-005f, 0.000195274f, 0.000488043f, 0.00078064f, 0.000918274f, 0.000960615f, 0.000971817f, 0.000974659f, 0.000975372f},
		{1.1918E-007f, 4.76372E-007f, 1.89992E-006f, 7.51196E-006f, 2.87216E-005f, 9.76467E-005f, 0.000244081f, 0.000390472f, 0.000459348f, 0.000480538f, 0.000486145f, 0.000487567f, 0.000487924f},
		{5.95901E-008f, 2.38186E-007f, 9.49963E-007f, 3.756E-006f, 1.4361E-005f, 4.88257E-005f, 0.000122055f, 0.000195274f, 0.000229727f, 0.000240327f, 0.000243132f, 0.000243843f, 0.000244021f},
		{2.9795E-008f, 1.19093E-007f, 4.74982E-007f, 1.878E-006f, 7.18056E-006f, 2.44135E-005f, 6.10314E-005f, 9.76467E-005f, 0.000114877f, 0.000120178f, 0.000121581f, 0.000121936f, 0.000122026f},
		{1.48975E-008f, 5.95465E-008f, 2.37491E-007f, 9.39002E-007f, 3.59029E-006f, 1.22069E-005f, 3.05166E-005f, 4.88257E-005f, 5.74416E-005f, 6.00925E-005f, 6.0794E-005f, 6.09719E-005f, 6.10165E-005f},
		{7.44876E-009f, 2.97732E-008f, 1.18745E-007f, 4.69501E-007f, 1.79515E-006f, 6.10348E-006f, 1.52586E-005f, 2.44135E-005f, 2.87216E-005f, 3.00472E-005f, 3.03979E-005f, 3.04869E-005f, 3.05092E-005f},
		{3.72438E-009f, 1.48866E-008f, 5.93727E-008f, 2.34751E-007f, 8.97575E-007f, 3.05175E-006f, 7.62934E-006f, 1.22069E-005f, 1.4361E-005f, 1.50238E-005f, 1.51992E-005f, 1.52437E-005f, 1.52548E-005f},
		{1.86219E-009f, 7.44331E-009f, 2.96864E-008f, 1.17375E-007f, 4.48788E-007f, 1.52588E-006f, 3.81468E-006f, 6.10348E-006f, 7.18056E-006f, 7.51196E-006f, 7.59965E-006f, 7.62189E-006f, 7.62747E-006f},
		{9.31095E-010f, 3.72166E-009f, 1.48432E-008f, 5.86876E-008f, 2.24394E-007f, 7.62939E-007f, 1.90734E-006f, 3.05175E-006f, 3.59029E-006f, 3.756E-006f, 3.79984E-006f, 3.81096E-006f, 3.81375E-006f},
		{4.65548E-010f, 1.86083E-009f, 7.42159E-009f, 2.93438E-008f, 1.12197E-007f, 3.8147E-007f, 9.53673E-007f, 1.52588E-006f, 1.79515E-006f, 1.878E-006f, 1.89992E-006f, 1.90548E-006f, 1.90688E-006f},
		{2.32774E-010f, 9.30414E-010f, 3.71079E-009f, 1.46719E-008f, 5.60985E-008f, 1.90735E-007f, 4.76837E-007f, 7.62939E-007f, 8.97575E-007f, 9.39002E-007f, 9.49963E-007f, 9.52743E-007f, 9.53441E-007f},
		{1.16387E-010f, 4.65207E-010f, 1.8554E-009f, 7.33596E-009f, 2.80492E-008f, 9.53674E-008f, 2.38419E-007f, 3.8147E-007f, 4.48788E-007f, 4.69501E-007f, 4.74982E-007f, 4.76372E-007f, 4.76721E-007f},
		{5.81935E-011f, 2.32603E-010f, 9.27699E-010f, 3.66798E-009f, 1.40246E-008f, 4.76837E-008f, 1.19209E-007f, 1.90735E-007f, 2.24394E-007f, 2.34751E-007f, 2.37491E-007f, 2.38186E-007f, 2.3836E-007f},
		{2.90967E-011f, 1.16302E-010f, 4.63849E-010f, 1.83399E-009f, 7.01231E-009f, 2.38419E-008f, 5.96046E-008f, 9.53674E-008f, 1.12197E-007f, 1.17375E-007f, 1.18745E-007f, 1.19093E-007f, 1.1918E-007f}
	};

	private static final float[][] Q_div2_tab_right = {
		{0.992246f, 0.992241f, 0.992218f, 0.992128f, 0.991768f, 0.990329f, 0.984615f, 0.962406f, 0.882759f, 0.663212f, 0.332468f, 0.111015f, 0.0302959f},
		{0.984612f, 0.984601f, 0.984556f, 0.984379f, 0.98367f, 0.980843f, 0.969697f, 0.927536f, 0.790123f, 0.496124f, 0.199377f, 0.0587695f, 0.0153809f},
		{0.96969f, 0.969668f, 0.969582f, 0.969238f, 0.967864f, 0.962406f, 0.941176f, 0.864865f, 0.653061f, 0.329897f, 0.110727f, 0.0302744f, 0.00775006f},
		{0.941163f, 0.941122f, 0.94096f, 0.940312f, 0.937729f, 0.927536f, 0.888889f, 0.761905f, 0.484848f, 0.197531f, 0.0586081f, 0.0153698f, 0.0038901f},
		{0.888865f, 0.888792f, 0.888503f, 0.887348f, 0.882759f, 0.864865f, 0.8f, 0.615385f, 0.32f, 0.109589f, 0.0301887f, 0.00774443f, 0.00194884f},
		{0.799961f, 0.799844f, 0.799375f, 0.797508f, 0.790123f, 0.761905f, 0.666667f, 0.444444f, 0.190476f, 0.057971f, 0.0153257f, 0.00388727f, 0.000975372f},
		{0.666612f, 0.66645f, 0.6658f, 0.663212f, 0.653061f, 0.615385f, 0.5f, 0.285714f, 0.105263f, 0.0298507f, 0.00772201f, 0.00194742f, 0.000487924f},
		{0.499939f, 0.499756f, 0.499025f, 0.496124f, 0.484848f, 0.444444f, 0.333333f, 0.166667f, 0.0555556f, 0.0151515f, 0.00387597f, 0.000974659f, 0.000244021f},
		{0.333279f, 0.333116f, 0.332468f, 0.329897f, 0.32f, 0.285714f, 0.2f, 0.0909091f, 0.0285714f, 0.00763359f, 0.00194175f, 0.000487567f, 0.000122026f},
		{0.199961f, 0.199844f, 0.199377f, 0.197531f, 0.190476f, 0.166667f, 0.111111f, 0.047619f, 0.0144928f, 0.00383142f, 0.000971817f, 0.000243843f, 6.10165E-005f},
		{0.111087f, 0.111015f, 0.110727f, 0.109589f, 0.105263f, 0.0909091f, 0.0588235f, 0.0243902f, 0.00729927f, 0.00191939f, 0.000486145f, 0.000121936f, 3.05092E-005f},
		{0.05881f, 0.0587695f, 0.0586081f, 0.057971f, 0.0555556f, 0.047619f, 0.030303f, 0.0123457f, 0.003663f, 0.000960615f, 0.000243132f, 6.09719E-005f, 1.52548E-005f},
		{0.0302959f, 0.0302744f, 0.0301887f, 0.0298507f, 0.0285714f, 0.0243902f, 0.0153846f, 0.00621118f, 0.00183486f, 0.000480538f, 0.000121581f, 3.04869E-005f, 7.62747E-006f},
		{0.0153809f, 0.0153698f, 0.0153257f, 0.0151515f, 0.0144928f, 0.0123457f, 0.00775194f, 0.00311526f, 0.000918274f, 0.000240327f, 6.0794E-005f, 1.52437E-005f, 3.81375E-006f},
		{0.00775006f, 0.00774443f, 0.00772201f, 0.00763359f, 0.00729927f, 0.00621118f, 0.00389105f, 0.00156006f, 0.000459348f, 0.000120178f, 3.03979E-005f, 7.62189E-006f, 1.90688E-006f},
		{0.0038901f, 0.00388727f, 0.00387597f, 0.00383142f, 0.003663f, 0.00311526f, 0.00194932f, 0.00078064f, 0.000229727f, 6.00925E-005f, 1.51992E-005f, 3.81096E-006f, 9.53441E-007f},
		{0.00194884f, 0.00194742f, 0.00194175f, 0.00191939f, 0.00183486f, 0.00156006f, 0.00097561f, 0.000390472f, 0.000114877f, 3.00472E-005f, 7.59965E-006f, 1.90548E-006f, 4.76721E-007f},
		{0.000975372f, 0.000974659f, 0.000971817f, 0.000960615f, 0.000918274f, 0.00078064f, 0.000488043f, 0.000195274f, 5.74416E-005f, 1.50238E-005f, 3.79984E-006f, 9.52743E-007f, 2.3836E-007f},
		{0.000487924f, 0.000487567f, 0.000486145f, 0.000480538f, 0.000459348f, 0.000390472f, 0.000244081f, 9.76467E-005f, 2.87216E-005f, 7.51196E-006f, 1.89992E-006f, 4.76372E-007f, 1.1918E-007f},
		{0.000244021f, 0.000243843f, 0.000243132f, 0.000240327f, 0.000229727f, 0.000195274f, 0.000122055f, 4.88257E-005f, 1.4361E-005f, 3.756E-006f, 9.49963E-007f, 2.38186E-007f, 5.95901E-008f},
		{0.000122026f, 0.000121936f, 0.000121581f, 0.000120178f, 0.000114877f, 9.76467E-005f, 6.10314E-005f, 2.44135E-005f, 7.18056E-006f, 1.878E-006f, 4.74982E-007f, 1.19093E-007f, 2.9795E-008f},
		{6.10165E-005f, 6.09719E-005f, 6.0794E-005f, 6.00925E-005f, 5.74416E-005f, 4.88257E-005f, 3.05166E-005f, 1.22069E-005f, 3.59029E-006f, 9.39002E-007f, 2.37491E-007f, 5.95465E-008f, 1.48975E-008f},
		{3.05092E-005f, 3.04869E-005f, 3.03979E-005f, 3.00472E-005f, 2.87216E-005f, 2.44135E-005f, 1.52586E-005f, 6.10348E-006f, 1.79515E-006f, 4.69501E-007f, 1.18745E-007f, 2.97732E-008f, 7.44876E-009f},
		{1.52548E-005f, 1.52437E-005f, 1.51992E-005f, 1.50238E-005f, 1.4361E-005f, 1.22069E-005f, 7.62934E-006f, 3.05175E-006f, 8.97575E-007f, 2.34751E-007f, 5.93727E-008f, 1.48866E-008f, 3.72438E-009f},
		{7.62747E-006f, 7.62189E-006f, 7.59965E-006f, 7.51196E-006f, 7.18056E-006f, 6.10348E-006f, 3.81468E-006f, 1.52588E-006f, 4.48788E-007f, 1.17375E-007f, 2.96864E-008f, 7.44331E-009f, 1.86219E-009f},
		{3.81375E-006f, 3.81096E-006f, 3.79984E-006f, 3.756E-006f, 3.59029E-006f, 3.05175E-006f, 1.90734E-006f, 7.62939E-007f, 2.24394E-007f, 5.86876E-008f, 1.48432E-008f, 3.72166E-009f, 9.31095E-010f},
		{1.90688E-006f, 1.90548E-006f, 1.89992E-006f, 1.878E-006f, 1.79515E-006f, 1.52588E-006f, 9.53673E-007f, 3.8147E-007f, 1.12197E-007f, 2.93438E-008f, 7.42159E-009f, 1.86083E-009f, 4.65548E-010f},
		{9.53441E-007f, 9.52743E-007f, 9.49963E-007f, 9.39002E-007f, 8.97575E-007f, 7.62939E-007f, 4.76837E-007f, 1.90735E-007f, 5.60985E-008f, 1.46719E-008f, 3.71079E-009f, 9.30414E-010f, 2.32774E-010f},
		{4.76721E-007f, 4.76372E-007f, 4.74982E-007f, 4.69501E-007f, 4.48788E-007f, 3.8147E-007f, 2.38419E-007f, 9.53674E-008f, 2.80492E-008f, 7.33596E-009f, 1.8554E-009f, 4.65207E-010f, 1.16387E-010f},
		{2.3836E-007f, 2.38186E-007f, 2.37491E-007f, 2.34751E-007f, 2.24394E-007f, 1.90735E-007f, 1.19209E-007f, 4.76837E-008f, 1.40246E-008f, 3.66798E-009f, 9.27699E-010f, 2.32603E-010f, 5.81935E-011f},
		{1.1918E-007f, 1.19093E-007f, 1.18745E-007f, 1.17375E-007f, 1.12197E-007f, 9.53674E-008f, 5.96046E-008f, 2.38419E-008f, 7.01231E-009f, 1.83399E-009f, 4.63849E-010f, 1.16302E-010f, 2.90967E-011f}
	};
	/* table for Q_div values when no coupling */
	private static final float[] Q_div_tab = {
		0.0153846f, 0.030303f,
		0.0588235f, 0.111111f,
		0.2f, 0.333333f,
		0.5f, 0.666667f,
		0.8f, 0.888889f,
		0.941176f, 0.969697f,
		0.984615f, 0.992248f,
		0.996109f, 0.998051f,
		0.999024f, 0.999512f,
		0.999756f, 0.999878f,
		0.999939f, 0.999969f,
		0.999985f, 0.999992f,
		0.999996f, 0.999998f,
		0.999999f, 1f,
		1f, 1f,
		1f
	};

	private static final float[][] Q_div_tab_left = {
		{0.969704f, 0.888985f, 0.667532f, 0.336788f, 0.117241f, 0.037594f, 0.0153846f, 0.00967118f, 0.00823245f, 0.00787211f, 0.00778198f, 0.00775945f, 0.00775382f},
		{0.984619f, 0.94123f, 0.800623f, 0.503876f, 0.209877f, 0.0724638f, 0.030303f, 0.0191571f, 0.0163305f, 0.0156212f, 0.0154438f, 0.0153994f, 0.0153883f},
		{0.99225f, 0.969726f, 0.889273f, 0.670103f, 0.346939f, 0.135135f, 0.0588235f, 0.037594f, 0.0321361f, 0.0307619f, 0.0304178f, 0.0303317f, 0.0303102f},
		{0.99611f, 0.98463f, 0.941392f, 0.802469f, 0.515152f, 0.238095f, 0.111111f, 0.0724638f, 0.0622711f, 0.0596878f, 0.0590397f, 0.0588776f, 0.058837f},
		{0.998051f, 0.992256f, 0.969811f, 0.890411f, 0.68f, 0.384615f, 0.2f, 0.135135f, 0.117241f, 0.112652f, 0.111497f, 0.111208f, 0.111135f},
		{0.999025f, 0.996113f, 0.984674f, 0.942029f, 0.809524f, 0.555556f, 0.333333f, 0.238095f, 0.209877f, 0.202492f, 0.200625f, 0.200156f, 0.200039f},
		{0.999512f, 0.998053f, 0.992278f, 0.970149f, 0.894737f, 0.714286f, 0.5f, 0.384615f, 0.346939f, 0.336788f, 0.3342f, 0.33355f, 0.333388f},
		{0.999756f, 0.999025f, 0.996124f, 0.984848f, 0.944444f, 0.833333f, 0.666667f, 0.555556f, 0.515152f, 0.503876f, 0.500975f, 0.500244f, 0.500061f},
		{0.999878f, 0.999512f, 0.998058f, 0.992366f, 0.971429f, 0.909091f, 0.8f, 0.714286f, 0.68f, 0.670103f, 0.667532f, 0.666884f, 0.666721f},
		{0.999939f, 0.999756f, 0.999028f, 0.996169f, 0.985507f, 0.952381f, 0.888889f, 0.833333f, 0.809524f, 0.802469f, 0.800623f, 0.800156f, 0.800039f},
		{0.999969f, 0.999878f, 0.999514f, 0.998081f, 0.992701f, 0.97561f, 0.941176f, 0.909091f, 0.894737f, 0.890411f, 0.889273f, 0.888985f, 0.888913f},
		{0.999985f, 0.999939f, 0.999757f, 0.999039f, 0.996337f, 0.987654f, 0.969697f, 0.952381f, 0.944444f, 0.942029f, 0.941392f, 0.94123f, 0.94119f},
		{0.999992f, 0.99997f, 0.999878f, 0.999519f, 0.998165f, 0.993789f, 0.984615f, 0.97561f, 0.971429f, 0.970149f, 0.969811f, 0.969726f, 0.969704f},
		{0.999996f, 0.999985f, 0.999939f, 0.99976f, 0.999082f, 0.996885f, 0.992248f, 0.987654f, 0.985507f, 0.984848f, 0.984674f, 0.98463f, 0.984619f},
		{0.999998f, 0.999992f, 0.99997f, 0.99988f, 0.999541f, 0.99844f, 0.996109f, 0.993789f, 0.992701f, 0.992366f, 0.992278f, 0.992256f, 0.99225f},
		{0.999999f, 0.999996f, 0.999985f, 0.99994f, 0.99977f, 0.999219f, 0.998051f, 0.996885f, 0.996337f, 0.996169f, 0.996124f, 0.996113f, 0.99611f},
		{1f, 0.999998f, 0.999992f, 0.99997f, 0.999885f, 0.99961f, 0.999024f, 0.99844f, 0.998165f, 0.998081f, 0.998058f, 0.998053f, 0.998051f},
		{1f, 0.999999f, 0.999996f, 0.999985f, 0.999943f, 0.999805f, 0.999512f, 0.999219f, 0.999082f, 0.999039f, 0.999028f, 0.999025f, 0.999025f},
		{1f, 1f, 0.999998f, 0.999992f, 0.999971f, 0.999902f, 0.999756f, 0.99961f, 0.999541f, 0.999519f, 0.999514f, 0.999512f, 0.999512f},
		{1f, 1f, 0.999999f, 0.999996f, 0.999986f, 0.999951f, 0.999878f, 0.999805f, 0.99977f, 0.99976f, 0.999757f, 0.999756f, 0.999756f},
		{1f, 1f, 1f, 0.999998f, 0.999993f, 0.999976f, 0.999939f, 0.999902f, 0.999885f, 0.99988f, 0.999878f, 0.999878f, 0.999878f},
		{1f, 1f, 1f, 0.999999f, 0.999996f, 0.999988f, 0.999969f, 0.999951f, 0.999943f, 0.99994f, 0.999939f, 0.999939f, 0.999939f},
		{1f, 1f, 1f, 1f, 0.999998f, 0.999994f, 0.999985f, 0.999976f, 0.999971f, 0.99997f, 0.99997f, 0.99997f, 0.999969f},
		{1f, 1f, 1f, 1f, 0.999999f, 0.999997f, 0.999992f, 0.999988f, 0.999986f, 0.999985f, 0.999985f, 0.999985f, 0.999985f},
		{1f, 1f, 1f, 1f, 1f, 0.999998f, 0.999996f, 0.999994f, 0.999993f, 0.999992f, 0.999992f, 0.999992f, 0.999992f},
		{1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999998f, 0.999997f, 0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999996f},
		{1f, 1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}
	};

	private static final float[][] Q_div_tab_right = {
		{0.00775382f, 0.00775945f, 0.00778198f, 0.00787211f, 0.00823245f, 0.00967118f, 0.0153846f, 0.037594f, 0.117241f, 0.336788f, 0.667532f, 0.888985f, 0.969704f},
		{0.0153883f, 0.0153994f, 0.0154438f, 0.0156212f, 0.0163305f, 0.0191571f, 0.030303f, 0.0724638f, 0.209877f, 0.503876f, 0.800623f, 0.94123f, 0.984619f},
		{0.0303102f, 0.0303317f, 0.0304178f, 0.0307619f, 0.0321361f, 0.037594f, 0.0588235f, 0.135135f, 0.346939f, 0.670103f, 0.889273f, 0.969726f, 0.99225f},
		{0.058837f, 0.0588776f, 0.0590397f, 0.0596878f, 0.0622711f, 0.0724638f, 0.111111f, 0.238095f, 0.515152f, 0.802469f, 0.941392f, 0.98463f, 0.99611f},
		{0.111135f, 0.111208f, 0.111497f, 0.112652f, 0.117241f, 0.135135f, 0.2f, 0.384615f, 0.68f, 0.890411f, 0.969811f, 0.992256f, 0.998051f},
		{0.200039f, 0.200156f, 0.200625f, 0.202492f, 0.209877f, 0.238095f, 0.333333f, 0.555556f, 0.809524f, 0.942029f, 0.984674f, 0.996113f, 0.999025f},
		{0.333388f, 0.33355f, 0.3342f, 0.336788f, 0.346939f, 0.384615f, 0.5f, 0.714286f, 0.894737f, 0.970149f, 0.992278f, 0.998053f, 0.999512f},
		{0.500061f, 0.500244f, 0.500975f, 0.503876f, 0.515152f, 0.555556f, 0.666667f, 0.833333f, 0.944444f, 0.984848f, 0.996124f, 0.999025f, 0.999756f},
		{0.666721f, 0.666884f, 0.667532f, 0.670103f, 0.68f, 0.714286f, 0.8f, 0.909091f, 0.971429f, 0.992366f, 0.998058f, 0.999512f, 0.999878f},
		{0.800039f, 0.800156f, 0.800623f, 0.802469f, 0.809524f, 0.833333f, 0.888889f, 0.952381f, 0.985507f, 0.996169f, 0.999028f, 0.999756f, 0.999939f},
		{0.888913f, 0.888985f, 0.889273f, 0.890411f, 0.894737f, 0.909091f, 0.941176f, 0.97561f, 0.992701f, 0.998081f, 0.999514f, 0.999878f, 0.999969f},
		{0.94119f, 0.94123f, 0.941392f, 0.942029f, 0.944444f, 0.952381f, 0.969697f, 0.987654f, 0.996337f, 0.999039f, 0.999757f, 0.999939f, 0.999985f},
		{0.969704f, 0.969726f, 0.969811f, 0.970149f, 0.971429f, 0.97561f, 0.984615f, 0.993789f, 0.998165f, 0.999519f, 0.999878f, 0.99997f, 0.999992f},
		{0.984619f, 0.98463f, 0.984674f, 0.984848f, 0.985507f, 0.987654f, 0.992248f, 0.996885f, 0.999082f, 0.99976f, 0.999939f, 0.999985f, 0.999996f},
		{0.99225f, 0.992256f, 0.992278f, 0.992366f, 0.992701f, 0.993789f, 0.996109f, 0.99844f, 0.999541f, 0.99988f, 0.99997f, 0.999992f, 0.999998f},
		{0.99611f, 0.996113f, 0.996124f, 0.996169f, 0.996337f, 0.996885f, 0.998051f, 0.999219f, 0.99977f, 0.99994f, 0.999985f, 0.999996f, 0.999999f},
		{0.998051f, 0.998053f, 0.998058f, 0.998081f, 0.998165f, 0.99844f, 0.999024f, 0.99961f, 0.999885f, 0.99997f, 0.999992f, 0.999998f, 1f},
		{0.999025f, 0.999025f, 0.999028f, 0.999039f, 0.999082f, 0.999219f, 0.999512f, 0.999805f, 0.999943f, 0.999985f, 0.999996f, 0.999999f, 1f},
		{0.999512f, 0.999512f, 0.999514f, 0.999519f, 0.999541f, 0.99961f, 0.999756f, 0.999902f, 0.999971f, 0.999992f, 0.999998f, 1f, 1f},
		{0.999756f, 0.999756f, 0.999757f, 0.99976f, 0.99977f, 0.999805f, 0.999878f, 0.999951f, 0.999986f, 0.999996f, 0.999999f, 1f, 1f},
		{0.999878f, 0.999878f, 0.999878f, 0.99988f, 0.999885f, 0.999902f, 0.999939f, 0.999976f, 0.999993f, 0.999998f, 1f, 1f, 1f},
		{0.999939f, 0.999939f, 0.999939f, 0.99994f, 0.999943f, 0.999951f, 0.999969f, 0.999988f, 0.999996f, 0.999999f, 1f, 1f, 1f},
		{0.999969f, 0.99997f, 0.99997f, 0.99997f, 0.999971f, 0.999976f, 0.999985f, 0.999994f, 0.999998f, 1f, 1f, 1f, 1f},
		{0.999985f, 0.999985f, 0.999985f, 0.999985f, 0.999986f, 0.999988f, 0.999992f, 0.999997f, 0.999999f, 1f, 1f, 1f, 1f},
		{0.999992f, 0.999992f, 0.999992f, 0.999992f, 0.999993f, 0.999994f, 0.999996f, 0.999998f, 1f, 1f, 1f, 1f, 1f},
		{0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999996f, 0.999997f, 0.999998f, 0.999999f, 1f, 1f, 1f, 1f, 1f},
		{0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999998f, 0.999999f, 1f, 1f, 1f, 1f, 1f, 1f},
		{0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 0.999999f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f},
		{1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f}
	};

	public static void extract_envelope_data(SBR sbr, int ch) {
		int l, k;

		for(l = 0; l<sbr.L_E[ch]; l++) {
			if(sbr.bs_df_env[ch][l]==0) {
				for(k = 1; k<sbr.n[sbr.f[ch][l]]; k++) {
					sbr.E[ch][k][l] = sbr.E[ch][k-1][l]+sbr.E[ch][k][l];
					if(sbr.E[ch][k][l]<0)
						sbr.E[ch][k][l] = 0;
				}

			}
			else { /* bs_df_env == 1 */

				int g = (l==0) ? sbr.f_prev[ch] : sbr.f[ch][l-1];
				int E_prev;

				if(sbr.f[ch][l]==g) {
					for(k = 0; k<sbr.n[sbr.f[ch][l]]; k++) {
						if(l==0)
							E_prev = sbr.E_prev[ch][k];
						else
							E_prev = sbr.E[ch][k][l-1];

						sbr.E[ch][k][l] = E_prev+sbr.E[ch][k][l];
					}

				}
				else if((g==1)&&(sbr.f[ch][l]==0)) {
					int i;

					for(k = 0; k<sbr.n[sbr.f[ch][l]]; k++) {
						for(i = 0; i<sbr.N_high; i++) {
							if(sbr.f_table_res[HI_RES][i]==sbr.f_table_res[LO_RES][k]) {
								if(l==0)
									E_prev = sbr.E_prev[ch][i];
								else
									E_prev = sbr.E[ch][i][l-1];

								sbr.E[ch][k][l] = E_prev+sbr.E[ch][k][l];
							}
						}
					}

				}
				else if((g==0)&&(sbr.f[ch][l]==1)) {
					int i;

					for(k = 0; k<sbr.n[sbr.f[ch][l]]; k++) {
						for(i = 0; i<sbr.N_low; i++) {
							if((sbr.f_table_res[LO_RES][i]<=sbr.f_table_res[HI_RES][k])
								&&(sbr.f_table_res[HI_RES][k]<sbr.f_table_res[LO_RES][i+1])) {
								if(l==0)
									E_prev = sbr.E_prev[ch][i];
								else
									E_prev = sbr.E[ch][i][l-1];

								sbr.E[ch][k][l] = E_prev+sbr.E[ch][k][l];
							}
						}
					}
				}
			}
		}
	}

	public static void extract_noise_floor_data(SBR sbr, int ch) {
		int l, k;

		for(l = 0; l<sbr.L_Q[ch]; l++) {
			if(sbr.bs_df_noise[ch][l]==0) {
				for(k = 1; k<sbr.N_Q; k++) {
					sbr.Q[ch][k][l] = sbr.Q[ch][k][l]+sbr.Q[ch][k-1][l];
				}
			}
			else {
				if(l==0) {
					for(k = 0; k<sbr.N_Q; k++) {
						sbr.Q[ch][k][l] = sbr.Q_prev[ch][k]+sbr.Q[ch][k][0];
					}
				}
				else {
					for(k = 0; k<sbr.N_Q; k++) {
						sbr.Q[ch][k][l] = sbr.Q[ch][k][l-1]+sbr.Q[ch][k][l];
					}
				}
			}
		}
	}

	/* calculates 1/(1+Q) */
	/* [0..1] */
	public static float calc_Q_div(SBR sbr, int ch, int m, int l) {
		if(sbr.bs_coupling) {
			/* left channel */
			if((sbr.Q[0][m][l]<0||sbr.Q[0][m][l]>30)
				||(sbr.Q[1][m][l]<0||sbr.Q[1][m][l]>24 /* 2*panOffset(1) */)) {
				return 0;
			}
			else {
				/* the pan parameter is always even */
				if(ch==0) {
					return Q_div_tab_left[sbr.Q[0][m][l]][sbr.Q[1][m][l]>>1];
				}
				else {
					return Q_div_tab_right[sbr.Q[0][m][l]][sbr.Q[1][m][l]>>1];
				}
			}
		}
		else {
			/* no coupling */
			if(sbr.Q[ch][m][l]<0||sbr.Q[ch][m][l]>30) {
				return 0;
			}
			else {
				return Q_div_tab[sbr.Q[ch][m][l]];
			}
		}
	}

	/* calculates Q/(1+Q) */
	/* [0..1] */
	public static float calc_Q_div2(SBR sbr, int ch, int m, int l) {
		if(sbr.bs_coupling) {
			if((sbr.Q[0][m][l]<0||sbr.Q[0][m][l]>30)
				||(sbr.Q[1][m][l]<0||sbr.Q[1][m][l]>24 /* 2*panOffset(1) */)) {
				return 0;
			}
			else {
				/* the pan parameter is always even */
				if(ch==0) {
					return Q_div2_tab_left[sbr.Q[0][m][l]][sbr.Q[1][m][l]>>1];
				}
				else {
					return Q_div2_tab_right[sbr.Q[0][m][l]][sbr.Q[1][m][l]>>1];
				}
			}
		}
		else {
			/* no coupling */
			if(sbr.Q[ch][m][l]<0||sbr.Q[ch][m][l]>30) {
				return 0;
			}
			else {
				return Q_div2_tab[sbr.Q[ch][m][l]];
			}
		}
	}

	public static void dequantChannel(SBR sbr, int ch) {
		if(!sbr.bs_coupling) {
			int exp;
			int l, k;
			int amp = (sbr.amp_res[ch]) ? 0 : 1;

			for(l = 0; l<sbr.L_E[ch]; l++) {
				for(k = 0; k<sbr.n[sbr.f[ch][l]]; k++) {
					/* +6 for the *64 and -10 for the /32 in the synthesis QMF (fixed)
					 * since this is a energy value: (x/32)^2 = (x^2)/1024
					 */
					/* exp = (sbr.E[ch][k][l] >> amp) + 6; */
					exp = (sbr.E[ch][k][l]>>amp);

					if((exp<0)||(exp>=64)) {
						sbr.E_orig[ch][k][l] = 0;
					}
					else {
						sbr.E_orig[ch][k][l] = E_deq_tab[exp];

						/* save half the table size at the cost of 1 multiply */
						if(amp!=0&&(sbr.E[ch][k][l]&1)!=0) {
							sbr.E_orig[ch][k][l] = (sbr.E_orig[ch][k][l]*1.414213562f);
						}
					}
				}
			}

			for(l = 0; l<sbr.L_Q[ch]; l++) {
				for(k = 0; k<sbr.N_Q; k++) {
					sbr.Q_div[ch][k][l] = calc_Q_div(sbr, ch, k, l);
					sbr.Q_div2[ch][k][l] = calc_Q_div2(sbr, ch, k, l);
				}
			}
		}
	}

	private static final float[] E_pan_tab = {
		0.000244081f, 0.000488043f,
		0.00097561f, 0.00194932f,
		0.00389105f, 0.00775194f,
		0.0153846f, 0.030303f,
		0.0588235f, 0.111111f,
		0.2f, 0.333333f,
		0.5f, 0.666667f,
		0.8f, 0.888889f,
		0.941176f, 0.969697f,
		0.984615f, 0.992248f,
		0.996109f, 0.998051f,
		0.999024f, 0.999512f,
		0.999756f
	};

	public static void unmap(SBR sbr) {
		float tmp;
		int exp0, exp1;
		int l, k;
		int amp0 = (sbr.amp_res[0]) ? 0 : 1;
		int amp1 = (sbr.amp_res[1]) ? 0 : 1;

		for(l = 0; l<sbr.L_E[0]; l++) {
			for(k = 0; k<sbr.n[sbr.f[0][l]]; k++) {
				/* +6: * 64 ; +1: * 2 ; */
				exp0 = (sbr.E[0][k][l]>>amp0)+1;

				/* UN_MAP removed: (x / 4096) same as (x >> 12) */
				/* E[1] is always even so no need for compensating the divide by 2 with
				 * an extra multiplication
				 */
				/* exp1 = (sbr.E[1][k][l] >> amp1) - 12; */
				exp1 = (sbr.E[1][k][l]>>amp1);

				if((exp0<0)||(exp0>=64)
					||(exp1<0)||(exp1>24)) {
					sbr.E_orig[1][k][l] = 0;
					sbr.E_orig[0][k][l] = 0;
				}
				else {
					tmp = E_deq_tab[exp0];
					if(amp0!=0&&(sbr.E[0][k][l]&1)!=0) {
						tmp *= 1.414213562;
					}

					/* panning */
					sbr.E_orig[0][k][l] = (tmp*E_pan_tab[exp1]);
					sbr.E_orig[1][k][l] = (tmp*E_pan_tab[24-exp1]);
				}
			}
		}

		for(l = 0; l<sbr.L_Q[0]; l++) {
			for(k = 0; k<sbr.N_Q; k++) {
				sbr.Q_div[0][k][l] = calc_Q_div(sbr, 0, k, l);
				sbr.Q_div[1][k][l] = calc_Q_div(sbr, 1, k, l);
				sbr.Q_div2[0][k][l] = calc_Q_div2(sbr, 0, k, l);
				sbr.Q_div2[1][k][l] = calc_Q_div2(sbr, 1, k, l);
			}
		}
	}
}