package app.fedilab.android.peertube.helper;

import android.app.Dialog;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackSelectionOverride;
import androidx.media3.common.TrackSelectionParameters;
import androidx.media3.common.Tracks;
import androidx.media3.ui.TrackSelectionView;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.ImmutableList;

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

import app.fedilab.android.R;
import app.fedilab.android.databinding.TrackSelectionDialogBinding;

/**
 * Dialog to select tracks.
 */
@androidx.annotation.OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public final class TrackSelectionDialog extends DialogFragment {

    public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
            ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
    private final SparseArray<TrackSelectionViewFragment> tabFragments;
    private final ArrayList<Integer> tabTrackTypes;
    private int titleId;
    private DialogInterface.OnClickListener onClickListener;
    private DialogInterface.OnDismissListener onDismissListener;
    private TrackSelectionDialogBinding binding;

    public TrackSelectionDialog() {
        tabFragments = new SparseArray<>();
        tabTrackTypes = new ArrayList<>();
        // Retain instance across activity re-creation to prevent losing access to init data.
        setRetainInstance(true);
    }

    /**
     * Returns whether a track selection dialog will have content to display if initialized with the
     * specified {@link Player}.
     */
    public static boolean willHaveContent(Player player) {
        return willHaveContent(player.getCurrentTracks());
    }

    /**
     * Returns whether a track selection dialog will have content to display if initialized with the
     * specified {@link Tracks}.
     */
    public static boolean willHaveContent(Tracks tracks) {
        for (Tracks.Group trackGroup : tracks.getGroups()) {
            if (SUPPORTED_TRACK_TYPES.contains(trackGroup.getType())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
     * when tracks are selected.
     *
     * @param player            The {@link Player}.
     * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
     *                          dismissed.
     */
    public static TrackSelectionDialog createForPlayer(
            Player player, DialogInterface.OnDismissListener onDismissListener) {
        return createForTracksAndParameters(
                R.string.track_selection_title,
                player.getCurrentTracks(),
                player.getTrackSelectionParameters(),
                /* allowAdaptiveSelections= */ true,
                /* allowMultipleOverrides= */ false,
                player::setTrackSelectionParameters,
                onDismissListener);
    }

    /**
     * Creates a dialog for given {@link Tracks} and {@link TrackSelectionParameters}.
     *
     * @param titleId                  The resource id of the dialog title.
     * @param tracks                   The {@link Tracks} describing the tracks to display.
     * @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
     * @param allowAdaptiveSelections  Whether adaptive selections (consisting of more than one track)
     *                                 can be made.
     * @param allowMultipleOverrides   Whether tracks from multiple track groups can be selected.
     * @param trackSelectionListener   Called when tracks are selected.
     * @param onDismissListener        {@link DialogInterface.OnDismissListener} called when the dialog is
     *                                 dismissed.
     */
    public static TrackSelectionDialog createForTracksAndParameters(
            int titleId,
            Tracks tracks,
            TrackSelectionParameters trackSelectionParameters,
            boolean allowAdaptiveSelections,
            boolean allowMultipleOverrides,
            TrackSelectionListener trackSelectionListener,
            DialogInterface.OnDismissListener onDismissListener) {
        TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
        trackSelectionDialog.init(
                tracks,
                trackSelectionParameters,
                titleId,
                allowAdaptiveSelections,
                allowMultipleOverrides,
                /* onClickListener= */ (dialog, which) -> {
                    TrackSelectionParameters.Builder builder = trackSelectionParameters.buildUpon();
                    for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
                        int trackType = SUPPORTED_TRACK_TYPES.get(i);
                        builder.setTrackTypeDisabled(trackType, trackSelectionDialog.getIsDisabled(trackType));
                        builder.clearOverridesOfType(trackType);
                        Map<TrackGroup, TrackSelectionOverride> overrides =
                                trackSelectionDialog.getOverrides(trackType);
                        for (TrackSelectionOverride override : overrides.values()) {
                            builder.addOverride(override);
                        }
                    }
                    trackSelectionListener.onTracksSelected(builder.build());
                },
                onDismissListener);
        return trackSelectionDialog;
    }

    private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
        return switch (trackType) {
            case C.TRACK_TYPE_VIDEO ->
                    resources.getString(R.string.video);
            case C.TRACK_TYPE_AUDIO ->
                    resources.getString(R.string.audio);
            case C.TRACK_TYPE_TEXT -> resources.getString(R.string.text);
            default -> throw new IllegalArgumentException();
        };
    }

    private void init(
            Tracks tracks,
            TrackSelectionParameters trackSelectionParameters,
            int titleId,
            boolean allowAdaptiveSelections,
            boolean allowMultipleOverrides,
            DialogInterface.OnClickListener onClickListener,
            DialogInterface.OnDismissListener onDismissListener) {
        this.titleId = titleId;
        this.onClickListener = onClickListener;
        this.onDismissListener = onDismissListener;

        for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
            @C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i);
            ArrayList<Tracks.Group> trackGroups = new ArrayList<>();
            for (Tracks.Group trackGroup : tracks.getGroups()) {
                if (trackGroup.getType() == trackType) {
                    trackGroups.add(trackGroup);
                }
            }
            if (!trackGroups.isEmpty()) {
                TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
                tabFragment.init(
                        trackGroups,
                        trackSelectionParameters.disabledTrackTypes.contains(trackType),
                        trackSelectionParameters.overrides,
                        allowAdaptiveSelections,
                        allowMultipleOverrides);
                tabFragments.put(trackType, tabFragment);
                tabTrackTypes.add(trackType);
            }
        }
    }

    /**
     * Returns whether the disabled option is selected for the specified track type.
     *
     * @param trackType The track type.
     * @return Whether the disabled option is selected for the track type.
     */
    public boolean getIsDisabled(int trackType) {
        TrackSelectionViewFragment trackView = tabFragments.get(trackType);
        return trackView != null && trackView.isDisabled;
    }

    /**
     * Returns the selected track overrides for the specified track type.
     *
     * @param trackType The track type.
     * @return The track overrides for the track type.
     */
    public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
        TrackSelectionViewFragment trackView = tabFragments.get(trackType);
        return trackView == null ? Collections.emptyMap() : trackView.overrides;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        MaterialAlertDialogBuilder materialAlertDialogBuilder = new MaterialAlertDialogBuilder(requireContext());
        binding = TrackSelectionDialogBinding.inflate(getLayoutInflater());
        materialAlertDialogBuilder.setView(binding.getRoot());
        Dialog dialog = materialAlertDialogBuilder.create();
        binding.trackSelectionDialogViewPager.setAdapter(new FragmentAdapter(getChildFragmentManager()));
        binding.trackSelectionDialogTabLayout.setupWithViewPager(binding.trackSelectionDialogViewPager);
        binding.trackSelectionDialogTabLayout.setVisibility(tabFragments.size() > 1 ? View.VISIBLE : View.GONE);
        binding.trackSelectionDialogCancelButton.setOnClickListener(view -> dismiss());
        binding.trackSelectionDialogOkButton.setOnClickListener(
                view -> {
                    onClickListener.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE);
                    dismiss();
                });
        dialog.setTitle(titleId);
        return dialog;
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        onDismissListener.onDismiss(dialog);
    }


    /**
     * Called when tracks are selected.
     */
    public interface TrackSelectionListener {

        /**
         * Called when tracks are selected.
         *
         * @param trackSelectionParameters A {@link TrackSelectionParameters} representing the selected
         *                                 tracks. Any manual selections are defined by {@link
         *                                 TrackSelectionParameters#disabledTrackTypes} and {@link
         *                                 TrackSelectionParameters#overrides}.
         */
        void onTracksSelected(TrackSelectionParameters trackSelectionParameters);
    }

    /**
     * Fragment to show a track selection in tab of the track selection dialog.
     */
    public static final class TrackSelectionViewFragment extends Fragment
            implements TrackSelectionView.TrackSelectionListener {

        /* package */ boolean isDisabled;
        /* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
        private List<Tracks.Group> trackGroups;
        private boolean allowAdaptiveSelections;
        private boolean allowMultipleOverrides;

        public TrackSelectionViewFragment() {
            // Retain instance across activity re-creation to prevent losing access to init data.
            setRetainInstance(true);
        }

        public void init(
                List<Tracks.Group> trackGroups,
                boolean isDisabled,
                Map<TrackGroup, TrackSelectionOverride> overrides,
                boolean allowAdaptiveSelections,
                boolean allowMultipleOverrides) {
            this.trackGroups = trackGroups;
            this.isDisabled = isDisabled;
            this.allowAdaptiveSelections = allowAdaptiveSelections;
            this.allowMultipleOverrides = allowMultipleOverrides;
            // TrackSelectionView does this filtering internally, but we need to do it here as well to
            // handle the case where the TrackSelectionView is never created.
            this.overrides =
                    new HashMap<>(
                            TrackSelectionView.filterOverrides(overrides, trackGroups, allowMultipleOverrides));
        }

        @Override
        public View onCreateView(
                LayoutInflater inflater,
                @Nullable ViewGroup container,
                @Nullable Bundle savedInstanceState) {
            View rootView =
                    inflater.inflate(
                            R.layout.exo_track_selection_dialog, container, /* attachToRoot= */ false);
            TrackSelectionView trackSelectionView = rootView.findViewById(R.id.exo_track_selection_view);
            trackSelectionView.setShowDisableOption(true);
            trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
            trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
            trackSelectionView.init(
                    trackGroups,
                    isDisabled,
                    overrides,
                    /* trackFormatComparator= */ null,
                    /* listener= */ this);
            return rootView;
        }

        @Override
        public void onTrackSelectionChanged(
                boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
            this.isDisabled = isDisabled;
            this.overrides = overrides;
        }
    }

    private final class FragmentAdapter extends FragmentPagerAdapter {

        public FragmentAdapter(FragmentManager fragmentManager) {
            super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        }

        @NonNull
        @Override
        public Fragment getItem(int position) {
            return tabFragments.get(tabTrackTypes.get(position));
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            return getTrackTypeString(getResources(), tabTrackTypes.get(position));
        }
    }
}