package com.foxx.androidcast;

/*********************************************************************
 * CastSettingsBinder.java
 * Created at: Sun 17 May 2026 18:17:14 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class CastSettingsBinder
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (8 commits, 650 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	b42fba669987d4b76f8eef0405456807674d0790194862a921dc1aab8380b1d2
**********************************************************************/
import android.os.Build;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.foxx.androidcast.sender.CodecCatalog;
import com.foxx.androidcast.ui.SettingsEnumSpinner;
import com.foxx.androidcast.ui.SettingsStringArrayAdapter;

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

/** Binds settings UI widgets to {@link AppPreferences} and {@link CastSettings}. */
public final class CastSettingsBinder {
    private CastSettingsBinder() {}

    public static void bindGlobal(AppCompatActivity activity, EditText username, EditText pin,
            CheckBox trayIcon) {
        username.setText(AppPreferences.getUsername(activity));
        pin.setText(AppPreferences.getPin(activity));
        trayIcon.setChecked(AppPreferences.isShowTrayIcon(activity));
    }

    public static void saveGlobal(AppCompatActivity activity, EditText username, EditText pin,
            CheckBox trayIcon) {
        String name = username.getText().toString().trim();
        if (name.isEmpty()) {
            name = AppPreferences.defaultDeviceName(activity);
            username.setText(name);
        }
        AppPreferences.setUsername(activity, name);
        AppPreferences.setPin(activity, pin.getText().toString().trim());
        AppPreferences.setShowTrayIcon(activity, trayIcon.isChecked());
    }

    public static void bindTheme(AppCompatActivity activity, Spinner spinner) {
        CastThemeHelper.AppThemeMode[] modes = CastThemeHelper.AppThemeMode.values();
        String[] labels = {
                activity.getString(R.string.theme_system),
                activity.getString(R.string.theme_dark),
                activity.getString(R.string.theme_light),
                activity.getString(R.string.theme_inverted)
        };
        bindEnumSpinner(spinner, modes, labels, AppPreferences.getThemeMode(activity),
                mode -> AppPreferences.setThemeMode(activity, mode));
    }

    public static void saveTheme(AppCompatActivity activity, Spinner spinner) {
        readEnumSpinner(spinner, (CastThemeHelper.AppThemeMode mode) ->
                AppPreferences.setThemeMode(activity, mode));
    }

    public static CastThemeHelper.AppThemeMode themeModeAt(int position) {
        CastThemeHelper.AppThemeMode[] modes = CastThemeHelper.AppThemeMode.values();
        if (position >= 0 && position < modes.length) {
            return modes[position];
        }
        return CastThemeHelper.AppThemeMode.SYSTEM;
    }

    public static void bindLanguage(AppCompatActivity activity, Spinner spinner) {
        CastLocaleHelper.AppLocaleMode[] modes = CastLocaleHelper.AppLocaleMode.values();
        String[] labels = {
                activity.getString(R.string.language_system),
                activity.getString(R.string.language_english),
                activity.getString(R.string.language_russian)
        };
        bindEnumSpinner(spinner, modes, labels, AppPreferences.getLocaleMode(activity),
                mode -> AppPreferences.setLocaleMode(activity, mode));
    }

    public static void saveLanguage(AppCompatActivity activity, Spinner spinner) {
        readEnumSpinner(spinner, (CastLocaleHelper.AppLocaleMode mode) ->
                AppPreferences.setLocaleMode(activity, mode));
    }

    public static CastLocaleHelper.AppLocaleMode localeModeAt(int position) {
        CastLocaleHelper.AppLocaleMode[] modes = CastLocaleHelper.AppLocaleMode.values();
        if (position >= 0 && position < modes.length) {
            return modes[position];
        }
        return CastLocaleHelper.AppLocaleMode.SYSTEM;
    }

    public static void bindSender(AppCompatActivity activity, CastSettings settings) {
        bindTransport(activity, settings, R.id.spinner_sender_transport, true);
        bindVideoCodec(activity, settings, R.id.spinner_sender_video_codec);
        bindBitrate(activity, settings);
        bindNetworkAdoption(activity, settings, R.id.spinner_sender_network);
        com.foxx.androidcast.sender.SenderResolutionUi.bindSenderResolution(activity, settings);
        bindCaptureMode(activity, settings);
        bindStreamProtection(activity, settings);
        bindAudioSpinner(activity, settings, R.id.spinner_sender_audio);
        bindAudioCodec(activity, settings);
    }

    public static void bindReceiver(AppCompatActivity activity, CastSettings settings) {
        bindReceiverPip(activity);
        bindTransport(activity, settings, R.id.spinner_receiver_transport, true);
        bindVideoCodec(activity, settings, R.id.spinner_receiver_video_codec);
        bindNetworkAdoption(activity, settings, R.id.spinner_receiver_network);
        Spinner audioPlay = activity.findViewById(R.id.spinner_receiver_play_audio);
        if (audioPlay != null) {
            bindOnOffSpinner(activity, audioPlay, AppPreferences.isPlayIncomingAudio(activity),
                    on -> AppPreferences.setPlayIncomingAudio(activity, on));
        }
    }

    public static void saveSender(AppCompatActivity activity, CastSettings settings) {
        readTransport(activity, settings, R.id.spinner_sender_transport);
        readVideoCodec(activity, settings, R.id.spinner_sender_video_codec);
        readBitrate(activity, settings);
        readNetworkAdoption(activity, settings, R.id.spinner_sender_network);
        readSenderResolution(activity, settings);
        readCaptureMode(activity, settings);
        readStreamProtection(activity, settings);
        readAudioSpinner(activity, settings, R.id.spinner_sender_audio);
        readAudioCodec(activity, settings);
        settings.setAdjustmentPreset(CastSettings.AdjustmentPreset.AUTO);
        AppPreferences.saveSenderDefaults(activity, settings);
    }

    public static void saveReceiver(AppCompatActivity activity, CastSettings settings) {
        saveReceiverPip(activity);
        readTransport(activity, settings, R.id.spinner_receiver_transport);
        readVideoCodec(activity, settings, R.id.spinner_receiver_video_codec);
        readNetworkAdoption(activity, settings, R.id.spinner_receiver_network);
        Spinner audioPlay = activity.findViewById(R.id.spinner_receiver_play_audio);
        if (audioPlay != null) {
            AppPreferences.setPlayIncomingAudio(activity, audioPlay.getSelectedItemPosition() == 0);
        }
        settings.setAdjustmentPreset(CastSettings.AdjustmentPreset.AUTO);
        AppPreferences.saveReceiverDefaults(activity, settings);
    }

    private static void bindTransport(AppCompatActivity activity, CastSettings settings, int spinnerId,
            boolean unused) {
        Spinner spinner = activity.findViewById(spinnerId);
        java.util.ArrayList<String> labelList = new java.util.ArrayList<>();
        java.util.ArrayList<String> valueList = new java.util.ArrayList<>();
        labelList.add(activity.getString(R.string.transport_udp));
        valueList.add(CastConfig.TRANSPORT_UDP);
        labelList.add(activity.getString(R.string.transport_tcp));
        valueList.add(CastConfig.TRANSPORT_TCP);
        if (!CastConfig.ALPHA_FEATURE_FREEZE) {
            labelList.add(activity.getString(R.string.transport_quic));
            valueList.add(CastConfig.TRANSPORT_QUIC);
        }
        if (AppPreferences.isDevUsbTetherTransportEnabled(activity)) {
            labelList.add(activity.getString(R.string.transport_usb_tether));
            valueList.add(CastConfig.TRANSPORT_USB_TETHER);
        }
        String[] labels = labelList.toArray(new String[0]);
        String[] values = valueList.toArray(new String[0]);
        bindStringSpinner(spinner, labels, values, settings.getTransport(), v -> {
            settings.setTransport(v);
            refreshStreamProtectionUi(activity, settings);
        });
        refreshStreamProtectionUi(activity, settings);
    }

    private static void refreshStreamProtectionUi(AppCompatActivity activity, CastSettings settings) {
        updateStreamProtectionSpinnerEnabled(activity, settings, R.id.spinner_sender_stream_protection,
                R.id.text_stream_protection_hint, R.id.spinner_sender_transport);
    }

    private static void readTransport(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        Object tag = spinner.getTag(R.id.tag_spinner_values);
        if (tag instanceof String[]) {
            int pos = spinner.getSelectedItemPosition();
            String[] values = (String[]) tag;
            if (pos >= 0 && pos < values.length) {
                settings.setTransport(values[pos]);
            }
        }
    }

    private static void bindVideoCodec(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        List<CodecCatalog.VideoCodecChoice> choices = CodecCatalog.videoCodecChoicesForUi(settings);
        CastSettings.VideoCodec[] values = new CastSettings.VideoCodec[choices.size()];
        String[] labels = new String[choices.size()];
        int[] scores = new int[choices.size()];
        boolean[] selectable = new boolean[choices.size()];
        boolean showScores = AppPreferences.isShowCodecPriorityScores(activity);
        for (int i = 0; i < choices.size(); i++) {
            values[i] = choices.get(i).codec;
            labels[i] = labelVideoCodec(activity, choices.get(i).codec);
            scores[i] = codecSpinnerScore(showScores, choices.get(i).codec, choices.get(i).priorityScore);
            selectable[i] = choices.get(i).selectable;
        }
        CastSettings.VideoCodec current = settings.getVideoCodec();
        if (!isVideoCodecSelectable(choices, current)) {
            current = CastSettings.VideoCodec.AUTO;
            settings.setVideoCodec(current);
        }
        SettingsEnumSpinner.bind(spinner, values, labels, scores, selectable, current, settings::setVideoCodec);
    }

    /** Rebind codec spinners after {@code codecs.json} reload or debug score toggle. */
    public static void refreshCodecSpinners(AppCompatActivity activity, CastSettings senderSettings,
            CastSettings receiverSettings) {
        bindVideoCodec(activity, senderSettings, R.id.spinner_sender_video_codec);
        bindVideoCodec(activity, receiverSettings, R.id.spinner_receiver_video_codec);
        bindAudioCodec(activity, senderSettings);
    }

    private static boolean isVideoCodecSelectable(List<CodecCatalog.VideoCodecChoice> choices,
            CastSettings.VideoCodec codec) {
        for (CodecCatalog.VideoCodecChoice c : choices) {
            if (c.codec == codec) {
                return c.selectable;
            }
        }
        return false;
    }

    private static void readVideoCodec(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        CastSettings.VideoCodec v = SettingsEnumSpinner.read(spinner, CastSettings.VideoCodec.AUTO);
        settings.setVideoCodec(v);
    }

    private static void bindAudioCodec(AppCompatActivity activity, CastSettings settings) {
        Spinner spinner = activity.findViewById(R.id.spinner_sender_audio_codec);
        if (spinner == null) {
            return;
        }
        List<CodecCatalog.AudioCodecChoice> choices = CodecCatalog.audioCodecChoicesForUi(settings);
        CastSettings.AudioCodec[] values = new CastSettings.AudioCodec[choices.size()];
        String[] labels = new String[choices.size()];
        int[] scores = new int[choices.size()];
        boolean[] selectable = new boolean[choices.size()];
        boolean showScores = AppPreferences.isShowCodecPriorityScores(activity);
        for (int i = 0; i < choices.size(); i++) {
            values[i] = choices.get(i).codec;
            labels[i] = labelAudioCodec(activity, choices.get(i).codec);
            scores[i] = codecSpinnerScore(showScores, choices.get(i).codec, choices.get(i).priorityScore);
            selectable[i] = choices.get(i).selectable;
        }
        CastSettings.AudioCodec current = settings.getAudioCodec();
        for (CodecCatalog.AudioCodecChoice c : choices) {
            if (c.codec == current && !c.selectable) {
                current = CastSettings.AudioCodec.AUTO;
                settings.setAudioCodec(current);
                break;
            }
        }
        SettingsEnumSpinner.bind(spinner, values, labels, scores, selectable, current, settings::setAudioCodec);
    }

    private static void readAudioCodec(AppCompatActivity activity, CastSettings settings) {
        Spinner spinner = activity.findViewById(R.id.spinner_sender_audio_codec);
        if (spinner == null) {
            return;
        }
        settings.setAudioCodec(SettingsEnumSpinner.read(spinner, CastSettings.AudioCodec.AUTO));
    }

    private static void bindBitrate(AppCompatActivity activity, CastSettings settings) {
        Spinner spinner = activity.findViewById(R.id.spinner_sender_bitrate);
        CastSettings.BitrateMode[] modes = {
                CastSettings.BitrateMode.ESTIMATED,
                CastSettings.BitrateMode.QUALITY,
                CastSettings.BitrateMode.SPEED,
                CastSettings.BitrateMode.BALANCED
        };
        String[] labels = {
                activity.getString(R.string.bitrate_estimated),
                activity.getString(R.string.option_quality),
                activity.getString(R.string.option_speed),
                activity.getString(R.string.option_balanced)
        };
        CastSettings.BitrateMode current = settings.getBitrateMode();
        if (current == CastSettings.BitrateMode.AUTO) {
            current = CastSettings.BitrateMode.ESTIMATED;
        }
        bindEnumSpinner(spinner, modes, labels, current, settings::setBitrateMode);
    }

    private static void readBitrate(AppCompatActivity activity, CastSettings settings) {
        readEnumSpinner(activity.findViewById(R.id.spinner_sender_bitrate), settings::setBitrateMode);
    }

    private static void bindNetworkAdoption(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        CastSettings.NetworkAdoption[] modes = {
                CastSettings.NetworkAdoption.ESTIMATED,
                CastSettings.NetworkAdoption.QUALITY,
                CastSettings.NetworkAdoption.SPEED
        };
        String[] labels = {
                activity.getString(R.string.network_estimated_auto),
                activity.getString(R.string.option_quality),
                activity.getString(R.string.option_speed)
        };
        CastSettings.NetworkAdoption current = settings.getNetworkAdoption();
        if (current == CastSettings.NetworkAdoption.AUTO) {
            current = CastSettings.NetworkAdoption.ESTIMATED;
        }
        bindEnumSpinner(spinner, modes, labels, current, settings::setNetworkAdoption);
    }

    private static void readNetworkAdoption(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        readEnumSpinner(activity.findViewById(spinnerId), settings::setNetworkAdoption);
    }

    private static void readSenderResolution(AppCompatActivity activity, CastSettings settings) {
        com.foxx.androidcast.sender.SenderResolutionUi.readSenderResolution(activity, settings);
    }

    public static void bindReceiverPip(AppCompatActivity activity) {
        CheckBox pip = activity.findViewById(R.id.check_receiver_pip);
        if (pip == null) {
            return;
        }
        boolean supported = PictureInPictureHelper.isSupported();
        pip.setEnabled(supported);
        pip.setOnCheckedChangeListener(null);
        pip.setChecked(AppPreferences.getReceiverPipEffective(activity));
        pip.setOnCheckedChangeListener((btn, checked) ->
                AppPreferences.setReceiverPipUserChoice(activity, checked));
        if (!supported) {
            pip.setChecked(false);
        }
    }

    /** Re-apply permission-derived default when the user has not chosen explicitly. */
    public static void refreshReceiverPipFromPermissions(AppCompatActivity activity) {
        if (AppPreferences.isReceiverPipUserDefined(activity)) {
            return;
        }
        bindReceiverPip(activity);
    }

    private static void saveReceiverPip(AppCompatActivity activity) {
        CheckBox pip = activity.findViewById(R.id.check_receiver_pip);
        if (pip != null && pip.isEnabled()) {
            AppPreferences.setReceiverPipUserChoice(activity, pip.isChecked());
        }
    }

    private static void bindCaptureMode(AppCompatActivity activity, CastSettings settings) {
        Spinner spinner = activity.findViewById(R.id.spinner_sender_capture_mode);
        List<String> labels = new ArrayList<>();
        List<CastSettings.CaptureMode> values = new ArrayList<>();
        labels.add(activity.getString(R.string.capture_full_screen));
        values.add(CastSettings.CaptureMode.FULL_SCREEN);
        if (PlatformCastSupport.isCaptureUserChoiceSupported()) {
            labels.add(activity.getString(R.string.capture_user_choice));
            values.add(CastSettings.CaptureMode.USER_CHOICE);
        }
        if (PlatformCastSupport.isCameraCaptureModeSupported()) {
            labels.add(activity.getString(R.string.capture_camera_rear));
            values.add(CastSettings.CaptureMode.CAMERA);
            if (PlatformCastSupport.hasFrontCamera(activity)) {
                labels.add(activity.getString(R.string.capture_camera_front));
                values.add(CastSettings.CaptureMode.CAMERA_FRONT);
            }
        }
        labels.add(activity.getString(R.string.capture_calibration_test));
        values.add(CastSettings.CaptureMode.CALIBRATION_TEST);
        bindEnumSpinner(spinner, values.toArray(new CastSettings.CaptureMode[0]),
                labels.toArray(new String[0]), settings.getCaptureMode(), settings::setCaptureMode);
    }

    private static void readCaptureMode(AppCompatActivity activity, CastSettings settings) {
        readEnumSpinner(activity.findViewById(R.id.spinner_sender_capture_mode), settings::setCaptureMode);
    }

    private static void bindStreamProtection(AppCompatActivity activity, CastSettings settings) {
        bindStreamProtectionSpinner(activity, settings, R.id.spinner_sender_stream_protection,
                R.id.text_stream_protection_hint, R.id.spinner_sender_transport);
    }

    private static void bindStreamProtectionSpinner(AppCompatActivity activity, CastSettings settings,
            int spinnerId, int hintId, int transportSpinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        if (spinner == null) {
            return;
        }
        CastSettings.StreamProtection[] modes = CastSettings.StreamProtection.values();
        String[] labels = {
                activity.getString(R.string.option_auto),
                activity.getString(R.string.protection_fec_34),
                activity.getString(R.string.protection_fec_rs),
                activity.getString(R.string.protection_nack),
                activity.getString(R.string.protection_fec_nack),
                activity.getString(R.string.protection_none)
        };
        boolean[] selectable = new boolean[modes.length];
        java.util.Arrays.fill(selectable, true);
        CastSettings.StreamProtection current = settings.getStreamProtection();
        if (current == CastSettings.StreamProtection.AUTO) {
            current = CastSettings.StreamProtection.NONE;
        }
        SettingsEnumSpinner.bind(spinner, modes, labels, selectable, current,
                settings::setStreamProtection);
        Spinner transportSpinner = activity.findViewById(transportSpinnerId);
        if (transportSpinner != null) {
            transportSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
                @Override
                public void onItemSelected(android.widget.AdapterView<?> parent, android.view.View view,
                        int position, long id) {
                    readTransport(activity, settings, transportSpinnerId);
                    updateStreamProtectionSpinnerEnabled(activity, settings, spinnerId, hintId,
                            transportSpinnerId);
                }

                @Override
                public void onNothingSelected(android.widget.AdapterView<?> parent) {
                }
            });
        }
        updateStreamProtectionSpinnerEnabled(activity, settings, spinnerId, hintId, transportSpinnerId);
    }

    private static void readStreamProtection(AppCompatActivity activity, CastSettings settings) {
        readEnumSpinner(activity.findViewById(R.id.spinner_sender_stream_protection),
                settings::setStreamProtection);
    }

    private static void updateStreamProtectionSpinnerEnabled(AppCompatActivity activity,
            CastSettings settings, int spinnerId, int hintId, int transportSpinnerId) {
        Spinner protection = activity.findViewById(spinnerId);
        if (protection == null) {
            return;
        }
        readTransport(activity, settings, transportSpinnerId);
        boolean udp = CastConfig.TRANSPORT_UDP.equals(settings.getTransport());
        protection.setEnabled(udp);
        TextView hint = activity.findViewById(hintId);
        if (hint != null) {
            hint.setVisibility(udp ? View.VISIBLE : View.GONE);
        }
    }

    private static void bindAudioSpinner(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        TextView hint = activity.findViewById(R.id.text_sender_audio_hint);
        if (!PlatformCastSupport.isAudioCaptureSupported()) {
            spinner.setEnabled(false);
            if (hint != null) {
                hint.setVisibility(View.VISIBLE);
                hint.setText(R.string.label_audio_unavailable);
            }
            settings.setAudioEnabled(false);
            return;
        }
        if (hint != null) {
            hint.setVisibility(View.GONE);
        }
        bindOnOffSpinner(activity, spinner, settings.isAudioRequested(),
                settings::setAudioEnabled);
    }

    private static void readAudioSpinner(AppCompatActivity activity, CastSettings settings, int spinnerId) {
        Spinner spinner = activity.findViewById(spinnerId);
        if (!PlatformCastSupport.isAudioCaptureSupported()) {
            settings.setAudioEnabled(false);
            return;
        }
        settings.setAudioEnabled(spinner.getSelectedItemPosition() == 0);
    }

    private static void bindOnOffSpinner(AppCompatActivity activity, Spinner spinner, boolean on,
            java.util.function.Consumer<Boolean> consumer) {
        String[] labels = {activity.getString(R.string.option_on), activity.getString(R.string.option_off)};
        SettingsStringArrayAdapter adapter = new SettingsStringArrayAdapter(activity, labels);
        spinner.setBackgroundResource(R.drawable.bg_settings_spinner);
        spinner.setPopupBackgroundResource(R.drawable.bg_settings_spinner_popup);
        spinner.setAdapter(adapter);
        spinner.setSelection(on ? 0 : 1);
        adapter.setSelectedPosition(on ? 0 : 1);
        SettingsStringArrayAdapter.wireSelectionRefresh(spinner, adapter);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                consumer.accept(position == 0);
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {}
        });
    }

    private static void bindStringSpinner(Spinner spinner, String[] labels, String[] values,
            String current, java.util.function.Consumer<String> onSelect) {
        spinner.setTag(R.id.tag_spinner_values, values);
        SettingsStringArrayAdapter adapter = new SettingsStringArrayAdapter(spinner.getContext(), labels);
        spinner.setBackgroundResource(R.drawable.bg_settings_spinner);
        spinner.setPopupBackgroundResource(R.drawable.bg_settings_spinner_popup);
        spinner.setAdapter(adapter);
        int sel = 0;
        for (int i = 0; i < values.length; i++) {
            if (values[i].equals(current)) {
                sel = i;
                break;
            }
        }
        spinner.setSelection(sel);
        adapter.setSelectedPosition(sel);
        SettingsStringArrayAdapter.wireSelectionRefresh(spinner, adapter);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (position >= 0 && position < values.length) {
                    onSelect.accept(values[position]);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {}
        });
    }

    public static <T extends Enum<T>> void bindEnumSpinner(Spinner spinner, T[] values, String[] labels,
            T current, java.util.function.Consumer<T> onSelect) {
        spinner.setTag(R.id.tag_spinner_enum, values);
        SettingsStringArrayAdapter adapter = new SettingsStringArrayAdapter(spinner.getContext(), labels);
        spinner.setBackgroundResource(R.drawable.bg_settings_spinner);
        spinner.setPopupBackgroundResource(R.drawable.bg_settings_spinner_popup);
        spinner.setAdapter(adapter);
        int sel = 0;
        for (int i = 0; i < values.length; i++) {
            if (values[i] == current) {
                sel = i;
                break;
            }
        }
        spinner.setSelection(sel);
        adapter.setSelectedPosition(sel);
        SettingsStringArrayAdapter.wireSelectionRefresh(spinner, adapter);
        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                if (position >= 0 && position < values.length) {
                    onSelect.accept(values[position]);
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {}
        });
    }

    public static <T extends Enum<T>> void readEnumSpinner(Spinner spinner,
            java.util.function.Consumer<T> consumer) {
        Object tag = spinner.getTag(R.id.tag_spinner_enum);
        if (tag instanceof Enum[]) {
            Enum<?>[] values = (Enum<?>[]) tag;
            int pos = spinner.getSelectedItemPosition();
            if (pos >= 0 && pos < values.length) {
                @SuppressWarnings("unchecked")
                T v = (T) values[pos];
                consumer.accept(v);
            }
        }
    }

    private static String labelVideoCodec(AppCompatActivity activity, CastSettings.VideoCodec codec) {
        switch (codec) {
            case H264:
                return activity.getString(R.string.codec_h264);
            case H265:
                return activity.getString(R.string.codec_h265);
            case LIBVPX_VP8:
                return activity.getString(R.string.codec_libvpx_vp8);
            case LIBVPX_VP9:
                return activity.getString(R.string.codec_libvpx_vp9);
            case PASSTHROUGH:
                return activity.getString(R.string.codec_passthrough);
            case AUTO:
            default:
                return activity.getString(R.string.option_auto);
        }
    }

    private static String labelAudioCodec(AppCompatActivity activity, CastSettings.AudioCodec codec) {
        switch (codec) {
            case AAC:
                return activity.getString(R.string.codec_aac);
            case SPEEX:
                return activity.getString(R.string.codec_speex);
            case OPUS:
                return activity.getString(R.string.codec_opus);
            case PASSTHROUGH_DEBUG:
                return activity.getString(R.string.codec_audio_passthrough_debug);
            case AUTO:
            default:
                return activity.getString(R.string.codec_audio_auto);
        }
    }

    private static int codecSpinnerScore(boolean showScores, Enum<?> codec, int priorityScore) {
        if (!showScores || codec == null) {
            return com.foxx.androidcast.ui.SettingsScoredArrayAdapter.NO_SCORE;
        }
        String name = codec.name();
        if ("AUTO".equals(name)) {
            return com.foxx.androidcast.ui.SettingsScoredArrayAdapter.NO_SCORE;
        }
        return priorityScore;
    }
}
