package com.greenaddress.greenbits.ui.preferences;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;

import androidx.core.content.ContextCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreference;
import io.reactivex.Observable;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

import com.afollestad.materialdialogs.MaterialDialog;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.greenaddress.gdk.GDKTwoFactorCall;
import com.greenaddress.greenapi.data.BalanceData;
import com.greenaddress.greenapi.data.NetworkData;
import com.greenaddress.greenapi.data.NotificationsData;
import com.greenaddress.greenapi.data.PricingData;
import com.greenaddress.greenapi.data.SettingsData;
import com.greenaddress.greenapi.data.TwoFactorConfigData;
import com.greenaddress.greenapi.model.Conversion;
import com.greenaddress.greenbits.AuthenticationHandler;
import com.greenaddress.greenbits.ui.BuildConfig;
import com.greenaddress.greenbits.ui.R;
import com.greenaddress.greenbits.ui.UI;
import com.greenaddress.greenbits.ui.accounts.SweepSelectActivity;
import com.greenaddress.greenbits.ui.onboarding.SecurityActivity;
import com.greenaddress.greenbits.ui.twofactor.PopupCodeResolver;
import com.greenaddress.greenbits.ui.twofactor.PopupMethodResolver;
import com.greenaddress.greenbits.ui.twofactor.TwoFactorActivity;

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

import static android.app.Activity.RESULT_OK;
import static com.greenaddress.greenapi.Session.getSession;
import static com.greenaddress.greenbits.ui.authentication.FirstScreenActivity.NETWORK_SELECTOR_REQUEST;

public class GeneralPreferenceFragment extends GAPreferenceFragment {
    private static final String TAG = GeneralPreferenceFragment.class.getSimpleName();

    public static final int REQUEST_ENABLE_2FA = 2031;
    private static final int REQUEST_2FA = 101;
    private static final ObjectMapper mObjectMapper = new ObjectMapper();

    private Preference mPinPref;
    private Preference mWatchOnlyLogin;
    private ListPreference mUnitPref;
    private ListPreference mPriceSourcePref;
    private ListPreference mTxPriorityPref;
    private Preference mCustomRatePref;
    private Preference mTwoFactorPref;
    private Preference mLimitsPref;
    private SwitchPreference mLocktimePref;
    private Preference mSetEmail;
    private Preference mSendLocktimePref;
    private Preference mTwoFactorRequestResetPref;
    private Preference mMemonicPref;
    private Preference mSweepPref;
    private ListPreference mTimeoutPref;
    private PreferenceCategory mAccountTitle;
    private Preference mSPV;
    private NetworkData mNetworkData;
    private Disposable mUpdateDisposable;

    @Override
    public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) {
        super.onCreatePreferences(savedInstanceState, rootKey);
        addPreferencesFromResource(R.xml.preference_general);
        setHasOptionsMenu(true);

        if (getSession() == null || getSession().getSettings() == null) {
            logout();
            return;
        }

        mNetworkData = getNetwork();
        final SettingsData settings = getSession().getSettings();
        final boolean isLiquid = mNetworkData.getLiquid();
        TwoFactorConfigData twoFaData = null;
        try {
            twoFaData = getSession().getTwoFactorConfig();
        } catch (final Exception e) { }
        final boolean anyEnabled = twoFaData != null ? twoFaData.isAnyEnabled() : false;
        final boolean emailConfirmed = twoFaData != null ? twoFaData.getEmail().isConfirmed() : false;

        // Pin submenu
        mPinPref = find(PrefKeys.DELETE_OR_CONFIGURE_PIN);
        if (!AuthenticationHandler.hasPin(getActivity()) && !getSession().isPinJustSaved()) {
            mPinPref.setEnabled(false);
            mPinPref.setSummary(getString(R.string.id_green_only_supports_one_pin_per));
        }
        mPinPref.setVisible(getSession().getHWWallet() == null);
        mPinPref.setOnPreferenceClickListener(preference -> {
            final Intent intent = new Intent(getActivity(), SettingsActivity.class);
            intent.putExtra( PreferenceActivity.EXTRA_SHOW_FRAGMENT, PinPreferenceFragment.class.getName() );
            startActivity(intent);
            return false;
        });

        // Watch-Only Login
        mWatchOnlyLogin = find(PrefKeys.WATCH_ONLY_LOGIN);
        mWatchOnlyLogin.setVisible(!isLiquid);
        mWatchOnlyLogin.setOnPreferenceClickListener((preference) -> onWatchOnlyLoginClicked());
        setupWatchOnlySummary();

        mAccountTitle = find("account_title");
        mAccountTitle.setVisible(!isLiquid);

        // Network & Logout
        final Preference logout = find(PrefKeys.LOGOUT);
        logout.setTitle(getString(R.string.id_s_network, mNetworkData.getName()));
        logout.setSummary(UI.getColoredString(
                              getString(R.string.id_log_out), ContextCompat.getColor(getContext(), R.color.red)));
        logout.setOnPreferenceClickListener(preference -> {
            logout.setEnabled(false);
            logout();
            return false;
        });

        // Bitcoin denomination
        mUnitPref = find(PrefKeys.UNIT);
        mUnitPref.setEntries(mNetworkData.getLiquid() ? UI.LIQUID_UNITS : UI.UNITS);
        mUnitPref.setEntryValues(UI.UNITS);
        mUnitPref.setOnPreferenceChangeListener((preference, newValue) -> {
            if (!newValue.equals(settings.getUnit())) {
                settings.setUnit(newValue.toString());
                mUpdateDisposable = updateSettings(settings).subscribe( (s) -> {
                    setUnitSummary(newValue.toString());
                }, (e) -> {
                    UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
                });
                return true;
            }
            return false;
        });
        setUnitSummary(Conversion.getBitcoinOrLiquidUnit());

        // Reference exchange rate
        mPriceSourcePref = find(PrefKeys.PRICING);
        mPriceSourcePref.setSingleLineTitle(false);
        mPriceSourcePref.setVisible(!isLiquid);
        mPriceSourcePref.setOnPreferenceChangeListener((preference, o) -> onPriceSourceChanged(o));
        setPricingSummary(settings.getPricing());
        try {
            final Map<String, Object> availableCurrencies = getSession().getAvailableCurrencies();
            setPricingEntries(availableCurrencies);
        } catch (final Exception e) {
            e.printStackTrace();
        }

        // Transaction priority, i.e. default fees
        mTxPriorityPref = find(PrefKeys.REQUIRED_NUM_BLOCKS);
        mTxPriorityPref.setSingleLineTitle(false);
        mTxPriorityPref.setVisible(!isLiquid);
        setRequiredNumBlocksSummary(settings.getRequiredNumBlocks());
        final String[] priorityValues = getResources().getStringArray(R.array.fee_target_values);
        mTxPriorityPref.setOnPreferenceChangeListener((preference, newValue) -> {
            if (warnIfOffline(getActivity())) {
                return false;
            }
            final int index = mTxPriorityPref.findIndexOfValue(newValue.toString());
            settings.setRequiredNumBlocks(Integer.parseInt(priorityValues[index]));
            setRequiredNumBlocksSummary(null);
            mUpdateDisposable = updateSettings(settings).subscribe((s) -> {
                setRequiredNumBlocksSummary(Integer.parseInt(priorityValues[index]));
            }, (e) -> {
                UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
            });
            return true;
        });

        // Default custom feerate
        mCustomRatePref = find(PrefKeys.DEFAULT_FEERATE_SATBYTE);
        mCustomRatePref.setVisible(!isLiquid);
        mCustomRatePref.setOnPreferenceClickListener(this::onFeeRatePreferenceClicked);
        setFeeRateSummary();

        // Two-factor Authentication Submenu
        mTwoFactorPref = find(PrefKeys.TWO_FACTOR);
        mTwoFactorPref.setOnPreferenceClickListener(preference -> {
            final Intent intent = new Intent(getActivity(), SecurityActivity.class);
            startActivity(intent);
            return false;
        });

        // Set two-factor threshold
        mLimitsPref = find(PrefKeys.TWO_FAC_LIMITS);
        mLimitsPref.setOnPreferenceClickListener(this::onLimitsPreferenceClicked);
        mLimitsPref.setVisible(anyEnabled && !isLiquid);
        setLimitsText(twoFaData.getLimits());

        // Enable nlocktime recovery emails
        mLocktimePref = find(PrefKeys.TWO_FAC_N_LOCKTIME_EMAILS);
        mLocktimePref.setVisible(emailConfirmed && !isLiquid);
        mLocktimePref.setOnPreferenceChangeListener((preference, o) -> {
            if (warnIfOffline(getActivity())) {
                return false;
            }
            final boolean value = (Boolean) o;
            if (settings.getNotifications() == null)
                settings.setNotifications(new NotificationsData());
            settings.getNotifications().setEmailOutgoing(value);
            settings.getNotifications().setEmailIncoming(value);
            mUpdateDisposable = updateSettings(settings).subscribe((s) -> { }, (e) -> {
                UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
            });
            return true;
        });

        // Set nlocktime email
        mSetEmail = find(PrefKeys.SET_EMAIL);
        mSetEmail.setVisible(!emailConfirmed && !isLiquid);
        mSetEmail.setOnPreferenceClickListener((preference) -> onSetEmailClicked());

        // Send nlocktime recovery emails
        mSendLocktimePref = find(PrefKeys.SEND_NLOCKTIME);
        mSendLocktimePref.setVisible(emailConfirmed && !isLiquid);
        mSendLocktimePref.setOnPreferenceClickListener(this::onSendNLocktimeClicked);

        // Cancel two factor reset
        mTwoFactorRequestResetPref = find(PrefKeys.RESET_TWOFACTOR);
        mTwoFactorRequestResetPref.setOnPreferenceClickListener(preference -> prompt2FAChange("reset", true));
        mTwoFactorRequestResetPref.setVisible(anyEnabled && !isLiquid);

        // Mnemonic
        mMemonicPref = find(PrefKeys.MNEMONIC_PASSPHRASE);
        mMemonicPref.setVisible(getSession().getHWWallet() == null);
        final String touchToDisplay = getString(R.string.id_touch_to_display);
        mMemonicPref.setSummary(touchToDisplay);
        mMemonicPref.setOnPreferenceClickListener(preference -> {
            final Intent intent = new Intent(getActivity(), DisplayMnemonicActivity.class);
            startActivity(intent);
            return false;
        });

        // Auto logout timeout
        final int timeout = settings.getAltimeout();
        mTimeoutPref = find(PrefKeys.ALTIMEOUT);
        mTimeoutPref.setEntryValues(getResources().getStringArray(R.array.auto_logout_values));
        setTimeoutValues(mTimeoutPref);
        setTimeoutSummary(timeout);
        mTimeoutPref.setOnPreferenceChangeListener((preference, newValue) -> {
            final Integer altimeout = Integer.parseInt(newValue.toString());
            settings.setAltimeout(altimeout);
            setTimeoutSummary(null);
            mUpdateDisposable = updateSettings(settings).subscribe((s) -> {
                setTimeoutSummary(altimeout);
            }, (e) -> {
                UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
            });
            return true;
        });

        findPreference("network_monitor").setVisible(false);

        // SPV_SYNCRONIZATION Syncronization Submenu
        mSPV = findPreference(PrefKeys.SPV_SYNCRONIZATION);
        mSPV.setVisible(!isLiquid);
        mSPV.setOnPreferenceClickListener(preference -> {
            final Intent intent = new Intent(getActivity(), SettingsActivity.class);
            intent.putExtra( PreferenceActivity.EXTRA_SHOW_FRAGMENT, SPVPreferenceFragment.class.getName() );
            startActivity(intent);
            return false;
        });

        // sweep from paper wallet
        mSweepPref = find(PrefKeys.SWEEP);
        mSweepPref.setVisible(!isLiquid);
        mSweepPref.setOnPreferenceClickListener(preference -> {
            final Intent intent = new Intent(getActivity(), SweepSelectActivity.class);
            startActivity(intent);
            return false;
        });

        findPreference(PrefKeys.PGP_KEY).setOnPreferenceClickListener(this::onPGPKeyClicked);


        // Terms of service
        final Preference termsOfUse = find(PrefKeys.TERMS_OF_USE);
        termsOfUse.setOnPreferenceClickListener(preference -> openURI("https://blockstream.com/green/terms/"));

        // Privacy policy
        final Preference privacyPolicy = find(PrefKeys.PRIVACY_POLICY);
        privacyPolicy.setOnPreferenceClickListener(preference -> openURI("https://blockstream.com/green/privacy/"));

        // Version
        final Preference version = find(PrefKeys.VERSION);
        version.setSummary(String.format("%s %s",
                                         getString(R.string.app_name),
                                         getString(R.string.id_version_1s_2s,
                                                   BuildConfig.VERSION_NAME,
                                                   BuildConfig.BUILD_TYPE)));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mUpdateDisposable != null)
            mUpdateDisposable.dispose();
    }

    private void setupWatchOnlySummary() {
        mWatchOnlyLogin.setSummary("");
        mUpdateDisposable = Observable.just(getSession())
                            .observeOn(Schedulers.computation())
                            .map((session) -> {
            return session.getWatchOnlyUsername();
        })
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe((username) -> {
            if (username.isEmpty())
                mWatchOnlyLogin.setSummary(R.string.id_set_up_watchonly_credentials);
            else
                mWatchOnlyLogin.setSummary(getString(R.string.id_enabled_1s, username));
        }, (e) -> {
            e.printStackTrace();
            UI.toast(getActivity(), e.getMessage(), Toast.LENGTH_LONG);
        });
    }

    private Observable<SettingsData> updateSettings(final SettingsData settings) {
        final Activity activity = getActivity();
        return Observable.just(getSession())
               .observeOn(Schedulers.computation())
               .map((session) -> {
            session.changeSettings(settings.toObjectNode()).resolve(new PopupMethodResolver(activity),
                                                                    new PopupCodeResolver(activity));
            return session.refreshSettings();
        }).observeOn(AndroidSchedulers.mainThread())
               .map((settingsData) -> {
            final ObjectNode details = mObjectMapper.createObjectNode();
            details.put("event", "settings");
            details.set("settings", mObjectMapper.valueToTree(settingsData));
            getSession().getNotificationModel().onNewNotification(getSession(), details);
            return settingsData;
        }).map((settingsData) -> {
            UI.toast(getActivity(), R.string.id_setting_updated, Toast.LENGTH_LONG);
            return settingsData;
        });
    }

    private String getDefaultFeeRate() {
        Long minFeeRateKB = 1000L;
        try {
            minFeeRateKB = getSession().getFees().get(0);
        } catch (final Exception e) {
            e.printStackTrace();
        }
        final String minFeeRateText = String.valueOf(minFeeRateKB / 1000.0);
        return cfg().getString( PrefKeys.DEFAULT_FEERATE_SATBYTE, minFeeRateText);
    }

    private boolean onPriceSourceChanged(final Object o) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        final String[] split = o.toString().split(" ");
        final String currency = split[0];
        final String exchange = split[1];
        final SettingsData settings = getSession().getSettings();

        settings.getPricing().setCurrency(currency);
        settings.getPricing().setExchange(exchange);
        setPricingSummary(null);

        mUpdateDisposable = updateSettings(settings)
                            .observeOn(Schedulers.computation())
                            .map((s) -> {
            final TwoFactorConfigData twoFaData = getSession().getTwoFactorConfig();
            final ObjectNode limitsData = twoFaData.getLimits();
            return limitsData;
        }).observeOn(AndroidSchedulers.mainThread())
                            .subscribe((limitsData) -> {
            setLimitsText(limitsData);
            setPricingSummary(settings.getPricing());
            final Integer satoshi = limitsData.get("satoshi").asInt(0);
            if (satoshi > 0) {
                UI.popup(
                    getActivity(),
                    "Changing reference exchange rate will reset your 2FA threshold to 0. Remember to top-up the 2FA threshold after continuing.")
                .show();
            }
        }, (final Throwable e) -> {
            UI.toast(getActivity(), e.getMessage(), Toast.LENGTH_LONG);
        });
        return true;
    }

    private boolean onWatchOnlyLoginClicked() {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        final View v = UI.inflateDialog(getActivity(), R.layout.dialog_set_watchonly);
        final EditText inputUser = UI.find(v, R.id.input_user);
        try {
            // refetch username
            inputUser.setText(getSession().getWatchOnlyUsername());
        } catch (final Exception e) {}
        final EditText inputPassword = UI.find(v, R.id.input_password);
        final MaterialDialog dialog = UI.popup(getActivity(), R.string.id_watchonly_login)
                                      .customView(v, true)
                                      .backgroundColor(getResources().getColor(R.color.buttonJungleGreen))
                                      .onPositive((dlg, which) -> {
            final String username = UI.getText(inputUser);
            final String password = UI.getText(inputPassword);
            if (username.isEmpty() || password.isEmpty()) {
                UI.toast(getActivity(), R.string.id_the_password_cant_be_empty, Toast.LENGTH_LONG);
                return;
            }
            mUpdateDisposable = Observable.just(getSession())
                                .observeOn(Schedulers.computation())
                                .map((session) -> {
                session.setWatchOnly(username, password);
                return session;
            }).observeOn(AndroidSchedulers.mainThread())
                                .subscribe((session) -> {
                setupWatchOnlySummary();
            }, (e) -> {
                UI.toast(getActivity(), R.string.id_username_not_available, Toast.LENGTH_LONG);
            });
        }).build();
        UI.showDialog(dialog);
        return false;
    }

    private boolean onPGPKeyClicked(final Preference pgpKey) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        final View v = UI.inflateDialog(getActivity(), R.layout.dialog_set_pgp_key);
        final EditText inputPGPKey = UI.find(v, R.id.input_pgp_key);
        final SettingsData settings = getSession().getSettings();
        final String oldValue = settings.getPgp() == null ? "" : settings.getPgp();
        try {
            inputPGPKey.setText(oldValue);
        } catch (final Exception e) {}

        final MaterialDialog dialog = UI.popup(getActivity(), R.string.id_pgp_key)
                                      .customView(v, true)
                                      .backgroundColor(getResources().getColor(R.color.buttonJungleGreen))
                                      .onPositive((dlg, which) -> {
            final String newValue = UI.getText(inputPGPKey);
            if (!newValue.equals(oldValue)) {
                settings.setPgp(newValue);
                mUpdateDisposable = updateSettings(settings).subscribe((s) -> {
                    inputPGPKey.setText(settings.getPgp());
                }, (e) -> {
                    UI.toast(getActivity(), R.string.id_invalid_pgp_key, Toast.LENGTH_LONG);
                });
            }
        }).build();
        UI.showDialog(dialog);
        return false;
    }

    private boolean onFeeRatePreferenceClicked(final Preference preference) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        final View v = UI.inflateDialog(getActivity(), R.layout.dialog_set_custom_feerate);
        final EditText rateEdit = UI.find(v, R.id.set_custom_feerate_amount);
        UI.localeDecimalInput(rateEdit);

        final Double aDouble = Double.valueOf(getDefaultFeeRate());

        rateEdit.setText(Conversion.getNumberFormat(2).format(aDouble));
        rateEdit.selectAll();

        final MaterialDialog dialog;
        dialog = UI.popup(getActivity(), R.string.id_set_custom_fee_rate)
                 .customView(v, true)
                 .backgroundColor(getResources().getColor(R.color.buttonJungleGreen))
                 .onPositive((dlg, which) -> {
            try {
                final Long minFeeRateKB = getSession().getFees().get(0);
                final String enteredFeeRate = UI.getText(rateEdit);
                final Number parsed = Conversion.getNumberFormat(2).parse(enteredFeeRate);
                final Double enteredFeeRateKB = parsed.doubleValue();

                if (enteredFeeRateKB * 1000 < minFeeRateKB) {
                    UI.toast(getActivity(), getString(R.string.id_fee_rate_must_be_at_least_s,
                                                      String.format("%.2f",(minFeeRateKB/1000.0) )), Toast.LENGTH_LONG);
                } else {
                    cfg().edit().putString(PrefKeys.DEFAULT_FEERATE_SATBYTE, String.valueOf(enteredFeeRateKB)).apply();
                    setFeeRateSummary();
                }
            } catch (final Exception e) {
                UI.toast(getActivity(), "Error setting Fee Rate", Toast.LENGTH_LONG);
            }
        }).build();
        UI.showDialog(dialog);
        return false;
    }

    private void setRequiredNumBlocksSummary(final Integer currentPriority) {
        if (currentPriority == null)
            mTxPriorityPref.setSummary("");
        else {
            final String[] prioritySummaries = {prioritySummary(3), prioritySummary(12), prioritySummary(24)};
            final String[] priorityValues = getResources().getStringArray(R.array.fee_target_values);
            for (int index = 0; index < priorityValues.length; index++)
                if (currentPriority.equals(Integer.valueOf(priorityValues[index])))
                    mTxPriorityPref.setSummary(prioritySummaries[index]);
        }
    }

    private String prioritySummary(final int blocks) {
        final int blocksPerHour = mNetworkData.getLiquid() ? 60 : 6;
        final int n = blocks % blocksPerHour == 0 ? blocks / blocksPerHour : blocks * (60 / blocksPerHour);
        final String confirmationInBlocks = getResources().getString(R.string.id_confirmation_in_d_blocks, blocks);
        final int idTime = blocks % blocksPerHour ==
                           0 ? (blocks == blocksPerHour ? R.string.id_hour : R.string.id_hours) : R.string.id_minutes;
        return String.format("%s, %d %s %s", confirmationInBlocks, n, getResources().getString(idTime),
                             getResources().getString(R.string.id_on_average));
    }

    private void setTimeoutSummary(final Integer altimeout) {
        if (altimeout == null)
            mTimeoutPref.setSummary("");
        else {
            final String minutesText = altimeout == 1 ?
                                       "1 " + getString(R.string.id_minute) :
                                       getString(R.string.id_1d_minutes, altimeout);
            mTimeoutPref.setSummary(minutesText);
        }
    }

    private void setTimeoutValues(final ListPreference preference) {
        final CharSequence[] entries = preference.getEntryValues();
        final int length = entries.length;
        final String[] entryValues = new String[length];
        for (int i = 0; i < length; i++) {
            final int currentMinutes = Integer.valueOf(entries[i].toString());
            final String minutesText = currentMinutes == 1 ?
                                       "1 " + getString(R.string.id_minute) :
                                       getString(R.string.id_1d_minutes, currentMinutes);
            entryValues[i] = minutesText;
        }
        preference.setEntries(entryValues);
    }

    private void setPricingEntries(final Map<String, Object> currencies) {
        final List<String> values = getAvailableCurrenciesAsList(currencies);
        final List<String> formatted =
            getAvailableCurrenciesAsFormattedList(currencies, getString(R.string.id_s_from_s));
        final String[] valuesArr = values.toArray(new String[0]);
        final String[] formattedArr = formatted.toArray(new String[0]);
        mPriceSourcePref.setEntries(formattedArr);
        mPriceSourcePref.setEntryValues(valuesArr);
    }

    public List<String> getAvailableCurrenciesAsFormattedList(final Map<String, Object> currencies,
                                                              final String format) {
        final List<String> list = new ArrayList<>();
        for (Pair<String,String> pair : getAvailableCurrenciesAsPairs(currencies)) {
            list.add(String.format(format, pair.first, pair.second));
        }
        return list;
    }

    public List<String> getAvailableCurrenciesAsList(final Map<String, Object> currencies) {
        if (getAvailableCurrenciesAsPairs(currencies) == null)
            return null;
        final List<String> list = new ArrayList<>();
        for (Pair<String,String> pair : getAvailableCurrenciesAsPairs(currencies)) {
            list.add(String.format("%s %s", pair.first, pair.second));
        }
        return list;
    }

    private List<Pair<String, String>> getAvailableCurrenciesAsPairs(final Map<String, Object> currencies) {
        final List<Pair<String, String>> ret = new LinkedList<>();
        final Map<String, ArrayList<String>> perExchange = (Map) currencies.get("per_exchange");

        for (final String exchange : perExchange.keySet())
            for (final String currency : perExchange.get(exchange))
                ret.add(new Pair<>(currency, exchange));

        Collections.sort(ret, (lhs, rhs) -> lhs.first.compareTo(rhs.first));
        return ret;
    }

    private void setPricingSummary(final PricingData pricing) {
        final String summary = pricing == null ? "" : String.format(getString(
                                                                        R.string.id_s_from_s),
                                                                    pricing.getCurrency(), pricing.getExchange());
        mPriceSourcePref.setSummary(summary);
    }

    private void setUnitSummary(final String value) {
        final String summary = value == null ? "" : value;
        mUnitPref.setSummary(summary);
    }

    private void setFeeRateSummary() {
        final Double aDouble = Double.valueOf(getDefaultFeeRate());
        final String feeRateString = UI.getFeeRateString(Double.valueOf(aDouble * 1000).longValue());
        mCustomRatePref.setSummary(feeRateString);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (isZombie())
            return;
        if (getSession() == null || getSession().getSettings() == null) {
            logout();
            return;
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (isZombie())
            return;
    }

    private boolean prompt2FAChange(final String method, final Boolean newValue) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        // TODO spending limits in two TwoFactorConfigData
        final Intent intent = new Intent(getActivity(), TwoFactorActivity.class);
        intent.putExtra("method", method);
        intent.putExtra("enable", newValue);
        startActivityForResult(intent, REQUEST_ENABLE_2FA);
        return true;
    }

    private void setLimitsText(final ObjectNode limitsData) {
        getActivity().runOnUiThread(() -> {
            try {
                final boolean isFiat = limitsData.get("is_fiat").asBoolean();
                final BalanceData balance = mObjectMapper.treeToValue(limitsData, BalanceData.class);
                if (!isFiat && balance.getSatoshi() == 0) {
                    mLimitsPref.setSummary(R.string.id_set_twofactor_threshold);
                } else if (isFiat) {
                    mLimitsPref.setSummary(Conversion.getFiat(balance, true));
                } else {
                    mLimitsPref.setSummary(Conversion.getBtc(balance, true));
                }
            } catch (final Exception e) {
                // We can throw because we have been logged out here, e.g. when
                // requesting a two-factor reset and unwinding the activity stack.
                // Since this is harmless, ignore the error here.
            }
        });
    }

    private boolean onLimitsPreferenceClicked(final Preference preference) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        final View v = UI.inflateDialog(getActivity(), R.layout.dialog_set_limits);
        final Spinner unitSpinner = UI.find(v, R.id.set_limits_currency);
        final EditText amountEdit = UI.find(v, R.id.set_limits_amount);
        UI.localeDecimalInput(amountEdit);

        final String[] currencies;
        currencies = new String[] {Conversion.getBitcoinOrLiquidUnit(), Conversion.getFiatCurrency()};

        final ArrayAdapter<String> adapter;
        adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item, currencies);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        unitSpinner.setAdapter(adapter);

        try {
            final ObjectNode limitsData = getSession().getTwoFactorConfig().getLimits();
            final boolean isFiat = limitsData.get("is_fiat").asBoolean();
            unitSpinner.setSelection(isFiat ? 1 : 0);
            amountEdit.selectAll();
            final BalanceData balance;
            balance = mObjectMapper.treeToValue(limitsData, BalanceData.class);
            if (isFiat) {
                amountEdit.setText(Conversion.getFiat(balance, false));
            } else {
                amountEdit.setText(Conversion.getBtc(balance, false));
            }
        } catch (final Exception e) {
            Log.e(TAG, "Conversion error: " + e.getLocalizedMessage());
        }

        final MaterialDialog dialog;
        dialog = UI.popup(getActivity(), R.string.id_set_twofactor_threshold)
                 .cancelable(false)
                 .customView(v, true)
                 .backgroundColor(getResources().getColor(R.color.buttonJungleGreen))
                 .onPositive((dlg, which) -> {
            try {
                final String unit = unitSpinner.getSelectedItem().toString();
                final String value = UI.getText(amountEdit);
                final Double doubleValue = Conversion.getNumberFormat().parse(value).doubleValue();
                setSpendingLimits(unit, doubleValue.toString());
            } catch (final Exception e) {
                UI.toast(getActivity(), "Error setting limits", Toast.LENGTH_LONG);
            }
        }).build();
        UI.showDialog(dialog);
        return false;
    }

    private boolean onSendNLocktimeClicked(final Preference preference) {
        if (warnIfOffline(getActivity())) {
            return false;
        }
        mUpdateDisposable = Observable.just(getSession())
                            .observeOn(Schedulers.computation())
                            .map((session) -> {
            session.sendNlocktimes();
            return session;
        })
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe((username) -> {
            UI.toast(getActivity(), R.string.id_recovery_transaction_request, Toast.LENGTH_SHORT);
        }, (e) -> {
            e.printStackTrace();
            UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
        });
        return false;
    }

    private boolean onSetEmailClicked() {
        final Intent intent = new Intent(getActivity(), TwoFactorActivity.class);
        intent.putExtra("method", "email");
        intent.putExtra("settingEmail", true);
        startActivityForResult(intent, REQUEST_2FA);
        return false;
    }

    private void setSpendingLimits(final String unit, final String amount) {
        final Activity activity = getActivity();

        final boolean isFiat = unit.equals(Conversion.getFiatCurrency());
        final String amountStr = TextUtils.isEmpty(amount) ? "0" : amount;

        final ObjectNode limitsData = new ObjectMapper().createObjectNode();
        limitsData.set("is_fiat", isFiat ? BooleanNode.TRUE : BooleanNode.FALSE);
        limitsData.set(isFiat ? "fiat" : Conversion.getUnitKey(), new TextNode(amountStr));
        mUpdateDisposable = Observable.just(getSession())
                            .observeOn(Schedulers.computation())
                            .map((session) -> {
            final GDKTwoFactorCall call = getSession().twoFactorChangeLimits(limitsData);
            final ObjectNode newLimits =
                call.resolve(new PopupMethodResolver(activity), new PopupCodeResolver(activity));
            getSession().getTwoFactorConfig().setLimits(newLimits);
            return newLimits;
        })
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe((newLimits) -> {
            setLimitsText(newLimits);
            UI.toast(getActivity(), R.string.id_setting_updated, Toast.LENGTH_LONG);
        }, (e) -> {
            e.printStackTrace();
            UI.toast(getActivity(), R.string.id_operation_failure, Toast.LENGTH_LONG);
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == GeneralPreferenceFragment.REQUEST_ENABLE_2FA && resultCode == RESULT_OK
            && data != null && "reset".equals(data.getStringExtra("method"))) {
            logout();
        } else if (requestCode == NETWORK_SELECTOR_REQUEST && resultCode == RESULT_OK) {
            logout();
        }
    }
}