package de.danoeh.antennapod.storage.preferences;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
import androidx.preference.PreferenceManager;

import de.danoeh.antennapod.model.feed.FeedOrder;
import org.json.JSONArray;
import org.json.JSONException;

import java.io.File;
import java.io.IOException;
import java.net.Proxy;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import de.danoeh.antennapod.model.download.ProxyConfig;
import de.danoeh.antennapod.model.feed.FeedCounter;
import de.danoeh.antennapod.model.feed.FeedPreferences;
import de.danoeh.antennapod.model.feed.SortOrder;
import de.danoeh.antennapod.model.feed.SubscriptionsFilter;

/**
 * Provides access to preferences set by the user in the settings screen. A
 * private instance of this class must first be instantiated via
 * init() or otherwise every public method will throw an Exception
 * when called.
 */
public abstract class UserPreferences {
    private static final String TAG = "UserPreferences";

    // User Interface
    public static final String PREF_THEME = "prefTheme";
    public static final String PREF_THEME_BLACK = "prefThemeBlack";
    public static final String PREF_TINTED_COLORS = "prefTintedColors";
    public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems";
    public static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder";
    public static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator";
    public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify";
    public static final String PREF_USE_EPISODE_COVER = "prefEpisodeCover";
    public static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
    private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify";
    public static final String PREF_FULL_NOTIFICATION_BUTTONS = "prefFullNotificationButtons";
    private static final String PREF_SHOW_DOWNLOAD_REPORT = "prefShowDownloadReport";
    public static final String PREF_DEFAULT_PAGE = "prefDefaultPage";
    public static final String PREF_FILTER_FEED = "prefSubscriptionsFilter";
    public static final String PREF_SUBSCRIPTION_TITLE = "prefSubscriptionTitle";
    public static final String PREF_BACK_OPENS_DRAWER = "prefBackButtonOpensDrawer";

    public static final String PREF_QUEUE_KEEP_SORTED = "prefQueueKeepSorted";
    public static final String PREF_QUEUE_KEEP_SORTED_ORDER = "prefQueueKeepSortedOrder";
    public static final String PREF_NEW_EPISODES_ACTION = "prefNewEpisodesAction";
    private static final String PREF_DOWNLOADS_SORTED_ORDER = "prefDownloadSortedOrder";
    private static final String PREF_INBOX_SORTED_ORDER = "prefInboxSortedOrder";

    // Episode
    public static final String PREF_SORT_ALL_EPISODES = "prefEpisodesSort";
    public static final String PREF_FILTER_ALL_EPISODES = "prefEpisodesFilter";

    // Playback
    public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect";
    public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect";
    public static final String PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT = "prefUnpauseOnBluetoothReconnect";
    public static final String PREF_HARDWARE_FORWARD_BUTTON = "prefHardwareForwardButton";
    public static final String PREF_HARDWARE_PREVIOUS_BUTTON = "prefHardwarePreviousButton";
    public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue";
    public static final String PREF_SKIP_KEEPS_EPISODE = "prefSkipKeepsEpisode";
    private static final String PREF_FAVORITE_KEEPS_EPISODE = "prefFavoriteKeepsEpisode";
    private static final String PREF_AUTO_DELETE = "prefAutoDelete";
    private static final String PREF_AUTO_DELETE_LOCAL = "prefAutoDeleteLocal";
    public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs";
    private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
    public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
    private static final String PREF_TIME_RESPECTS_SPEED = "prefPlaybackTimeRespectsSpeed";
    public static final String PREF_STREAM_OVER_DOWNLOAD = "prefStreamOverDownload";

    // Network
    private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
    public static final String PREF_ENQUEUE_LOCATION = "prefEnqueueLocation";
    public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
    private static final String PREF_MOBILE_UPDATE = "prefMobileUpdateTypes";
    public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
    public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
    public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
    public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery";
    public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
    private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
    private static final String PREF_PROXY_TYPE = "prefProxyType";
    private static final String PREF_PROXY_HOST = "prefProxyHost";
    private static final String PREF_PROXY_PORT = "prefProxyPort";
    private static final String PREF_PROXY_USER = "prefProxyUser";
    private static final String PREF_PROXY_PASSWORD = "prefProxyPassword";

    // Services
    private static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications";

    // Other
    private static final String PREF_DATA_FOLDER = "prefDataFolder";
    public static final String PREF_DELETE_REMOVES_FROM_QUEUE = "prefDeleteRemovesFromQueue";
    private static final String PREF_AUTOMATIC_EXPORT_FOLDER = "prefAutomaticExportFolder";

    // Mediaplayer
    private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
    public static final String PREF_PLAYBACK_SKIP_SILENCE = "prefSkipSilence";
    private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs";
    private static final String PREF_REWIND_SECS = "prefRewindSecs";
    private static final String PREF_QUEUE_LOCKED = "prefQueueLocked";

    // Experimental
    public static final int EPISODE_CLEANUP_QUEUE = -1;
    public static final int EPISODE_CLEANUP_NULL = -2;
    public static final int EPISODE_CLEANUP_EXCEPT_FAVORITE = -3;
    public static final int EPISODE_CLEANUP_DEFAULT = 0;

    // Constants
    public static final int NOTIFICATION_BUTTON_SKIP = 2;
    public static final int NOTIFICATION_BUTTON_NEXT_CHAPTER = 3;
    public static final int NOTIFICATION_BUTTON_PLAYBACK_SPEED = 4;
    public static final int NOTIFICATION_BUTTON_SLEEP_TIMER = 5;
    public static final int EPISODE_CACHE_SIZE_UNLIMITED = -1;
    public static final String DEFAULT_PAGE_REMEMBER = "remember";

    private static Context context;
    private static SharedPreferences prefs;

    /**
     * Sets up the UserPreferences class.
     *
     * @throws IllegalArgumentException if context is null
     */
    public static void init(@NonNull Context context) {
        Log.d(TAG, "Creating new instance of UserPreferences");

        UserPreferences.context = context.getApplicationContext();
        UserPreferences.prefs = PreferenceManager.getDefaultSharedPreferences(context);

        createNoMediaFile();
    }

    public enum ThemePreference {
        LIGHT, DARK, BLACK, SYSTEM
    }

    public static void setTheme(ThemePreference theme) {
        switch (theme) {
            case LIGHT:
                prefs.edit().putString(PREF_THEME, "0").apply();
                break;
            case DARK:
                prefs.edit().putString(PREF_THEME, "1").apply();
                break;
            default:
                prefs.edit().putString(PREF_THEME, "system").apply();
                break;
        }
    }

    public static ThemePreference getTheme() {
        switch (prefs.getString(PREF_THEME, "system")) {
            case "0":
                return ThemePreference.LIGHT;
            case "1":
                return ThemePreference.DARK;
            default:
                return ThemePreference.SYSTEM;
        }
    }

    public static boolean getIsBlackTheme() {
        return prefs.getBoolean(PREF_THEME_BLACK, false);
    }

    public static boolean getIsThemeColorTinted() {
        return Build.VERSION.SDK_INT >= 31 && prefs.getBoolean(PREF_TINTED_COLORS, false);
    }

    public static List<String> getHiddenDrawerItems() {
        String hiddenItems = prefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "");
        return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenItems, ",")));
    }

    public static List<Integer> getFullNotificationButtons() {
        String[] buttons = TextUtils.split(
            prefs.getString(PREF_FULL_NOTIFICATION_BUTTONS,
                NOTIFICATION_BUTTON_SKIP + "," + NOTIFICATION_BUTTON_PLAYBACK_SPEED), ",");

        List<Integer> notificationButtons = new ArrayList<>();
        for (String button : buttons) {
            notificationButtons.add(Integer.parseInt(button));
        }
        return notificationButtons;
    }

    /**
     * Helper function to return whether the specified button should be shown on full
     * notifications.
     *
     * @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD,
     *                 NOTIFICATION_BUTTON_SKIP, NOTIFICATION_BUTTON_PLAYBACK_SPEED
     *                 or NOTIFICATION_BUTTON_NEXT_CHAPTER.
     * @return {@code true} if button should be shown, {@code false}  otherwise
     */
    private static boolean showButtonOnFullNotification(int buttonId) {
        return getFullNotificationButtons().contains(buttonId);
    }

    public static boolean showSkipOnFullNotification() {
        return showButtonOnFullNotification(NOTIFICATION_BUTTON_SKIP);
    }

    public static boolean showNextChapterOnFullNotification() {
        return showButtonOnFullNotification(NOTIFICATION_BUTTON_NEXT_CHAPTER);
    }

    public static boolean showPlaybackSpeedOnFullNotification() {
        return showButtonOnFullNotification(NOTIFICATION_BUTTON_PLAYBACK_SPEED);
    }

    public static boolean showSleepTimerOnFullNotification() {
        return showButtonOnFullNotification(NOTIFICATION_BUTTON_SLEEP_TIMER);
    }

    public static FeedOrder getFeedOrder() {
        String value = prefs.getString(PREF_DRAWER_FEED_ORDER, "" + FeedOrder.COUNTER.id);
        return FeedOrder.fromOrdinal(Integer.parseInt(value));
    }

    public static void setFeedOrder(FeedOrder feedOrder) {
        prefs.edit().putString(PREF_DRAWER_FEED_ORDER, "" + feedOrder.id).apply();
    }

    public static FeedCounter getFeedCounterSetting() {
        String value = prefs.getString(PREF_DRAWER_FEED_COUNTER, "" + FeedCounter.SHOW_NEW.id);
        return FeedCounter.fromOrdinal(Integer.parseInt(value));
    }

    /**
     * @return {@code true} if episodes should use their own cover, {@code false}  otherwise
     */
    public static boolean getUseEpisodeCoverSetting() {
        return prefs.getBoolean(PREF_USE_EPISODE_COVER, true);
    }

    /**
     * @return {@code true} if we should show remaining time or the duration
     */
    public static boolean shouldShowRemainingTime() {
        return prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
    }

    /**
     * Sets the preference for whether we show the remain time, if not show the duration. This will
     * send out events so the current playing screen, queue and the episode list would refresh
     *
     * @return {@code true} if we should show remaining time or the duration
     */
    public static void setShowRemainTimeSetting(Boolean showRemain) {
        prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showRemain).apply();
    }

    @Nullable
    public static String getAutomaticExportFolder() {
        return prefs.getString(PREF_AUTOMATIC_EXPORT_FOLDER, null);
    }

    public static void setAutomaticExportFolder(@Nullable String folder) {
        prefs.edit().putString(PREF_AUTOMATIC_EXPORT_FOLDER, folder).apply();
    }

    /**
     * Returns notification priority.
     *
     * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT
     */
    public static int getNotifyPriority() {
        if (prefs.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) {
            return NotificationCompat.PRIORITY_MAX;
        } else {
            return NotificationCompat.PRIORITY_DEFAULT;
        }
    }

    /**
     * Returns true if notifications are persistent
     *
     * @return {@code true} if notifications are persistent, {@code false}  otherwise
     */
    public static boolean isPersistNotify() {
        return prefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true);
    }

    /**
     * Used for migration of the preference to system notification channels.
     */
    public static boolean getShowDownloadReportRaw() {
        return prefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true);
    }

    public static boolean enqueueDownloadedEpisodes() {
        return prefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true);
    }

    public enum EnqueueLocation {
        BACK, FRONT, AFTER_CURRENTLY_PLAYING, RANDOM
    }

    @NonNull
    public static EnqueueLocation getEnqueueLocation() {
        String valStr = prefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name());
        try {
            return EnqueueLocation.valueOf(valStr);
        } catch (Throwable t) {
            // should never happen but just in case
            Log.e(TAG, "getEnqueueLocation: invalid value '" + valStr + "' Use default.", t);
            return EnqueueLocation.BACK;
        }
    }

    public static void setEnqueueLocation(@NonNull EnqueueLocation location) {
        prefs.edit()
                .putString(PREF_ENQUEUE_LOCATION, location.name())
                .apply();
    }

    public static boolean isPauseOnHeadsetDisconnect() {
        return prefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true);
    }

    public static boolean isUnpauseOnHeadsetReconnect() {
        return prefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true);
    }

    public static boolean isUnpauseOnBluetoothReconnect() {
        return prefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false);
    }

    public static int getHardwareForwardButton() {
        return Integer.parseInt(prefs.getString(PREF_HARDWARE_FORWARD_BUTTON,
                String.valueOf(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD)));
    }

    public static int getHardwarePreviousButton() {
        return Integer.parseInt(prefs.getString(PREF_HARDWARE_PREVIOUS_BUTTON,
                String.valueOf(KeyEvent.KEYCODE_MEDIA_REWIND)));
    }


    public static boolean isFollowQueue() {
        return prefs.getBoolean(PREF_FOLLOW_QUEUE, true);
    }

    /**
     * Set to true to enable Continuous Playback
     */
    @VisibleForTesting
    public static void setFollowQueue(boolean value) {
        prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).apply();
    }

    public static boolean shouldSkipKeepEpisode() {
        return prefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true);
    }

    public static boolean shouldFavoriteKeepEpisode() {
        return prefs.getBoolean(PREF_FAVORITE_KEEPS_EPISODE, true);
    }

    public static boolean isAutoDelete() {
        return prefs.getBoolean(PREF_AUTO_DELETE, false);
    }

    public static boolean isAutoDeleteLocal() {
        return prefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false);
    }

    public static int getSmartMarkAsPlayedSecs() {
        return Integer.parseInt(prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30"));
    }

    public static boolean shouldDeleteRemoveFromQueue() {
        return prefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false);
    }

    public static float getPlaybackSpeed() {
        try {
            return Float.parseFloat(prefs.getString(PREF_PLAYBACK_SPEED, "1.00"));
        } catch (NumberFormatException e) {
            Log.e(TAG, Log.getStackTraceString(e));
            UserPreferences.setPlaybackSpeed(1.0f);
            return 1.0f;
        }
    }

    public static boolean isSkipSilence() {
        return prefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false);
    }

    public static List<Float> getPlaybackSpeedArray() {
        return readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null));
    }

    public static boolean shouldPauseForFocusLoss() {
        return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true);
    }

    public static long getUpdateInterval() {
        return Integer.parseInt(prefs.getString(PREF_UPDATE_INTERVAL, "12"));
    }

    public static boolean isAutoUpdateDisabled() {
        return getUpdateInterval() == 0;
    }

    private static boolean isAllowMobileFor(String type) {
        HashSet<String> defaultValue = new HashSet<>();
        defaultValue.add("images");
        Set<String> allowed = prefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue);
        return allowed.contains(type);
    }

    public static boolean isAllowMobileFeedRefresh() {
        return isAllowMobileFor("feed_refresh");
    }

    public static boolean isAllowMobileSync() {
        return isAllowMobileFor("sync");
    }

    public static boolean isAllowMobileEpisodeDownload() {
        return isAllowMobileFor("episode_download");
    }

    public static boolean isAllowMobileAutoDownload() {
        return isAllowMobileFor("auto_download");
    }

    public static boolean isAllowMobileStreaming() {
        return isAllowMobileFor("streaming");
    }

    public static boolean isAllowMobileImages() {
        return isAllowMobileFor("images");
    }

    private static void setAllowMobileFor(String type, boolean allow) {
        HashSet<String> defaultValue = new HashSet<>();
        defaultValue.add("images");
        final Set<String> getValueStringSet = prefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue);
        final Set<String> allowed = new HashSet<>(getValueStringSet);
        if (allow) {
            allowed.add(type);
        } else {
            allowed.remove(type);
        }
        prefs.edit().putStringSet(PREF_MOBILE_UPDATE, allowed).apply();
    }

    public static void setAllowMobileFeedRefresh(boolean allow) {
        setAllowMobileFor("feed_refresh", allow);
    }

    public static void setAllowMobileEpisodeDownload(boolean allow) {
        setAllowMobileFor("episode_download", allow);
    }

    public static void setAllowMobileAutoDownload(boolean allow) {
        setAllowMobileFor("auto_download", allow);
    }

    public static void setAllowMobileStreaming(boolean allow) {
        setAllowMobileFor("streaming", allow);
    }

    public static void setAllowMobileImages(boolean allow) {
        setAllowMobileFor("images", allow);
    }

    public static void setAllowMobileSync(boolean allow) {
        setAllowMobileFor("sync", allow);
    }

    /**
     * Returns the capacity of the episode cache. This method will return the
     * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
     * 'unlimited'.
     */
    public static int getEpisodeCacheSize() {
        return Integer.parseInt(prefs.getString(PREF_EPISODE_CACHE_SIZE, "20"));
    }

    public static boolean isEnableAutodownload() {
        return prefs.getBoolean(PREF_ENABLE_AUTODL, false);
    }

    @VisibleForTesting
    public static void setEnableAutodownload(boolean enabled) {
        prefs.edit().putBoolean(PREF_ENABLE_AUTODL, enabled).apply();
    }

    public static boolean isEnableAutodownloadOnBattery() {
        return prefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true);
    }

    public static boolean isEnableAutodownloadWifiFilter() {
        return Build.VERSION.SDK_INT < 29 && prefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false);
    }

    public static int getFastForwardSecs() {
        return prefs.getInt(PREF_FAST_FORWARD_SECS, 30);
    }

    public static int getRewindSecs() {
        return prefs.getInt(PREF_REWIND_SECS, 10);
    }

    public static String[] getAutodownloadSelectedNetworks() {
        String selectedNetWorks = prefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "");
        return TextUtils.split(selectedNetWorks, ",");
    }

    public static void setProxyConfig(ProxyConfig config) {
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(PREF_PROXY_TYPE, config.type.name());
        if (TextUtils.isEmpty(config.host)) {
            editor.remove(PREF_PROXY_HOST);
        } else {
            editor.putString(PREF_PROXY_HOST, config.host);
        }
        if (config.port <= 0 || config.port > 65535) {
            editor.remove(PREF_PROXY_PORT);
        } else {
            editor.putInt(PREF_PROXY_PORT, config.port);
        }
        if (TextUtils.isEmpty(config.username)) {
            editor.remove(PREF_PROXY_USER);
        } else {
            editor.putString(PREF_PROXY_USER, config.username);
        }
        if (TextUtils.isEmpty(config.password)) {
            editor.remove(PREF_PROXY_PASSWORD);
        } else {
            editor.putString(PREF_PROXY_PASSWORD, config.password);
        }
        editor.apply();
    }

    public static ProxyConfig getProxyConfig() {
        Proxy.Type type = Proxy.Type.valueOf(prefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name()));
        String host = prefs.getString(PREF_PROXY_HOST, null);
        int port = prefs.getInt(PREF_PROXY_PORT, 0);
        String username = prefs.getString(PREF_PROXY_USER, null);
        String password = prefs.getString(PREF_PROXY_PASSWORD, null);
        return new ProxyConfig(type, host, port, username, password);
    }

    public static boolean isQueueLocked() {
        return prefs.getBoolean(PREF_QUEUE_LOCKED, false);
    }

    public static void setFastForwardSecs(int secs) {
        prefs.edit().putInt(PREF_FAST_FORWARD_SECS, secs).apply();
    }

    public static void setRewindSecs(int secs) {
        prefs.edit().putInt(PREF_REWIND_SECS, secs).apply();
    }

    public static void setPlaybackSpeed(float speed) {
        prefs.edit().putString(PREF_PLAYBACK_SPEED, String.valueOf(speed)).apply();
    }

    public static void setSkipSilence(boolean skipSilence) {
        prefs.edit().putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence).apply();
    }

    public static void setPlaybackSpeedArray(List<Float> speeds) {
        DecimalFormatSymbols format = new DecimalFormatSymbols(Locale.US);
        format.setDecimalSeparator('.');
        DecimalFormat speedFormat = new DecimalFormat("0.00", format);
        JSONArray jsonArray = new JSONArray();
        for (float speed : speeds) {
            jsonArray.put(speedFormat.format(speed));
        }
        prefs.edit().putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()).apply();
    }

    public static void setAutodownloadSelectedNetworks(String[] value) {
        prefs.edit().putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value)).apply();
    }

    public static boolean gpodnetNotificationsEnabled() {
        if (Build.VERSION.SDK_INT >= 26) {
            return true; // System handles notification preferences
        }
        return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true);
    }

    /**
     * Used for migration of the preference to system notification channels.
     */
    public static boolean getGpodnetNotificationsEnabledRaw() {
        return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true);
    }

    public static void setGpodnetNotificationsEnabled() {
        prefs.edit().putBoolean(PREF_GPODNET_NOTIFICATIONS, true).apply();
    }

    public static void setHiddenDrawerItems(List<String> items) {
        String str = TextUtils.join(",", items);
        prefs.edit().putString(PREF_HIDDEN_DRAWER_ITEMS, str).apply();
    }

    public static void setFullNotificationButtons(List<Integer> items) {
        String str = TextUtils.join(",", items);
        prefs.edit().putString(PREF_FULL_NOTIFICATION_BUTTONS, str).apply();
    }

    public static void setQueueLocked(boolean locked) {
        prefs.edit().putBoolean(PREF_QUEUE_LOCKED, locked).apply();
    }

    private static List<Float> readPlaybackSpeedArray(String valueFromPrefs) {
        if (valueFromPrefs != null) {
            try {
                JSONArray jsonArray = new JSONArray(valueFromPrefs);
                List<Float> selectedSpeeds = new ArrayList<>();
                for (int i = 0; i < jsonArray.length(); i++) {
                    selectedSpeeds.add((float) jsonArray.getDouble(i));
                }
                return selectedSpeeds;
            } catch (JSONException e) {
                Log.e(TAG, "Got JSON error when trying to get speeds from JSONArray");
                e.printStackTrace();
            }
        }
        // If this preference hasn't been set yet, return the default options
        return Arrays.asList(1.0f, 1.25f, 1.5f);
    }

    public static int getEpisodeCleanupValue() {
        return Integer.parseInt(prefs.getString(PREF_EPISODE_CLEANUP, "" + EPISODE_CLEANUP_NULL));
    }

    public static void setEpisodeCleanupValue(int episodeCleanupValue) {
        prefs.edit().putString(PREF_EPISODE_CLEANUP, Integer.toString(episodeCleanupValue)).apply();
    }

    /**
     * Return the folder where the app stores all of its data. This method will
     * return the standard data folder if none has been set by the user.
     *
     * @param type The name of the folder inside the data folder. May be null
     *             when accessing the root of the data folder.
     * @return The data folder that has been requested or null if the folder could not be created.
     */
    public static File getDataFolder(@Nullable String type) {
        File dataFolder = getTypeDir(prefs.getString(PREF_DATA_FOLDER, null), type);
        if (dataFolder == null || !dataFolder.canWrite()) {
            Log.d(TAG, "User data folder not writable or not set. Trying default.");
            dataFolder = context.getExternalFilesDir(type);
        }
        if (dataFolder == null || !dataFolder.canWrite()) {
            Log.d(TAG, "Default data folder not available or not writable. Falling back to internal memory.");
            dataFolder = getTypeDir(context.getFilesDir().getAbsolutePath(), type);
        }
        return dataFolder;
    }

    @Nullable
    private static File getTypeDir(@Nullable String baseDirPath, @Nullable String type) {
        if (baseDirPath == null) {
            return null;
        }
        File baseDir = new File(baseDirPath);
        File typeDir = type == null ? baseDir : new File(baseDir, type);
        if (!typeDir.exists()) {
            if (!baseDir.canWrite()) {
                Log.e(TAG, "Base dir is not writable " + baseDir.getAbsolutePath());
                return null;
            }
            if (!typeDir.mkdirs()) {
                Log.e(TAG, "Could not create type dir " + typeDir.getAbsolutePath());
                return null;
            }
        }
        return typeDir;
    }

    public static void setDataFolder(String dir) {
        Log.d(TAG, "setDataFolder(dir: " + dir + ")");
        prefs.edit().putString(PREF_DATA_FOLDER, dir).apply();
    }

    /**
     * Create a .nomedia file to prevent scanning by the media scanner.
     */
    private static void createNoMediaFile() {
        File f = new File(context.getExternalFilesDir(null), ".nomedia");
        if (!f.exists()) {
            try {
                f.createNewFile();
            } catch (IOException e) {
                Log.e(TAG, "Could not create .nomedia file");
                e.printStackTrace();
            }
            Log.d(TAG, ".nomedia file created");
        }
    }

    public static String getDefaultPage() {
        return prefs.getString(PREF_DEFAULT_PAGE, "HomeFragment");
    }

    public static void setDefaultPage(String defaultPage) {
        prefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply();
    }

    public static boolean backButtonOpensDrawer() {
        return prefs.getBoolean(PREF_BACK_OPENS_DRAWER, false);
    }

    public static boolean timeRespectsSpeed() {
        return prefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false);
    }

    public static boolean isStreamOverDownload() {
        return prefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false);
    }

    public static void setStreamOverDownload(boolean stream) {
        prefs.edit().putBoolean(PREF_STREAM_OVER_DOWNLOAD, stream).apply();
    }

    /**
     * Returns if the queue is in keep sorted mode.
     *
     * @see #getQueueKeepSortedOrder()
     */
    public static boolean isQueueKeepSorted() {
        return prefs.getBoolean(PREF_QUEUE_KEEP_SORTED, false);
    }

    /**
     * Enables/disables the keep sorted mode of the queue.
     *
     * @see #setQueueKeepSortedOrder(SortOrder)
     */
    public static void setQueueKeepSorted(boolean keepSorted) {
        prefs.edit().putBoolean(PREF_QUEUE_KEEP_SORTED, keepSorted).apply();
    }

    /**
     * Returns the sort order for the queue keep sorted mode.
     * Note: This value is stored independently from the keep sorted state.
     *
     * @see #isQueueKeepSorted()
     */
    public static SortOrder getQueueKeepSortedOrder() {
        String sortOrderStr = prefs.getString(PREF_QUEUE_KEEP_SORTED_ORDER, "use-default");
        return SortOrder.parseWithDefault(sortOrderStr, SortOrder.DATE_NEW_OLD);
    }

    /**
     * Sets the sort order for the queue keep sorted mode.
     *
     * @see #setQueueKeepSorted(boolean)
     */
    public static void setQueueKeepSortedOrder(SortOrder sortOrder) {
        if (sortOrder == null) {
            return;
        }
        prefs.edit().putString(PREF_QUEUE_KEEP_SORTED_ORDER, sortOrder.name()).apply();
    }

    public static FeedPreferences.NewEpisodesAction getNewEpisodesAction() {
        String str = prefs.getString(PREF_NEW_EPISODES_ACTION,
                "" + FeedPreferences.NewEpisodesAction.ADD_TO_INBOX.code);
        return FeedPreferences.NewEpisodesAction.fromCode(Integer.parseInt(str));
    }

    /**
     * Returns the sort order for the downloads.
     */
    public static SortOrder getDownloadsSortedOrder() {
        String sortOrderStr = prefs.getString(PREF_DOWNLOADS_SORTED_ORDER, "" + SortOrder.DATE_NEW_OLD.code);
        return SortOrder.fromCodeString(sortOrderStr);
    }

    /**
     * Sets the sort order for the downloads.
     */
    public static void setDownloadsSortedOrder(SortOrder sortOrder) {
        prefs.edit().putString(PREF_DOWNLOADS_SORTED_ORDER, "" + sortOrder.code).apply();
    }

    public static SortOrder getInboxSortedOrder() {
        String sortOrderStr = prefs.getString(PREF_INBOX_SORTED_ORDER, "" + SortOrder.DATE_NEW_OLD.code);
        return SortOrder.fromCodeString(sortOrderStr);
    }

    public static void setInboxSortedOrder(SortOrder sortOrder) {
        prefs.edit().putString(PREF_INBOX_SORTED_ORDER, "" + sortOrder.code).apply();
    }

    public static SubscriptionsFilter getSubscriptionsFilter() {
        String value = prefs.getString(PREF_FILTER_FEED, "");
        return new SubscriptionsFilter(value);
    }

    public static void setSubscriptionsFilter(SubscriptionsFilter value) {
        prefs.edit().putString(PREF_FILTER_FEED, value.serialize()).apply();
    }

    public static boolean shouldShowSubscriptionTitle() {
        return prefs.getBoolean(PREF_SUBSCRIPTION_TITLE, false);
    }

    public static void setAllEpisodesSortOrder(SortOrder s) {
        prefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s.code).apply();
    }

    public static SortOrder getAllEpisodesSortOrder() {
        return SortOrder.fromCodeString(prefs.getString(PREF_SORT_ALL_EPISODES,
                "" + SortOrder.DATE_NEW_OLD.code));
    }

    public static String getPrefFilterAllEpisodes() {
        return prefs.getString(PREF_FILTER_ALL_EPISODES, "");
    }

    public static void setPrefFilterAllEpisodes(String filter) {
        prefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply();
    }
}
