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

package com.android.systemui.tv.media;

import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.media.MediaRoute2Info;
import android.net.Uri;
import android.os.Bundle;
import android.text.Annotation;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaDevice.MediaDeviceType;
import com.android.systemui.media.dialog.MediaItem;
import com.android.systemui.tv.media.settings.CenteredImageSpan;
import com.android.systemui.tv.media.settings.ControlWidget;
import com.android.systemui.tv.res.R;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Adapter for showing the {@link MediaItem}s in the {@link TvMediaOutputDialogActivity}.
 */
public class TvMediaOutputAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final String TAG = TvMediaOutputAdapter.class.getSimpleName();
    private static final boolean DEBUG = false;

    private final TvMediaOutputController mMediaOutputController;
    private final PanelCallback mCallback;
    private final Context mContext;
    protected List<MediaItem> mMediaItemList = new CopyOnWriteArrayList<>();

    private final AccessibilityManager mA11yManager;

    private final CharSequence mTooltipText;
    private String mSavedDeviceId;

    private final boolean mIsRtl;

    TvMediaOutputAdapter(Context context, TvMediaOutputController mediaOutputController,
            PanelCallback callback) {
        mContext = context;
        mMediaOutputController = mediaOutputController;
        mCallback = callback;

        mA11yManager = context.getSystemService(AccessibilityManager.class);

        Resources res = mContext.getResources();
        mIsRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
        mTooltipText = createTooltipText();

        setHasStableIds(true);
    }

    @Override
    public int getItemViewType(int position) {
        if (position >= mMediaItemList.size()) {
            Log.e(TAG, "Incorrect position for item type: " + position);
            return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;
        }
        return mMediaItemList.get(position).getMediaItemType();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View mHolderView = LayoutInflater.from(mContext)
                .inflate(MediaItem.getMediaLayoutId(viewType), parent, false);

        switch (viewType) {
            case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER:
                return new DividerViewHolder(mHolderView);
            case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE:
            case MediaItem.MediaItemType.TYPE_DEVICE:
                return new DeviceViewHolder(mHolderView);
            default:
                Log.e(TAG, "unknown viewType: " + viewType);
                return new DeviceViewHolder(mHolderView);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if (position >= getItemCount()) {
            Log.e(TAG, "Tried to bind at position > list size (" + getItemCount() + ")");
        }

        MediaItem currentMediaItem = mMediaItemList.get(position);
        switch (currentMediaItem.getMediaItemType()) {
            case MediaItem.MediaItemType.TYPE_GROUP_DIVIDER ->
                    ((DividerViewHolder) viewHolder).onBind(currentMediaItem.getTitle());
            case MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE ->
                    ((DeviceViewHolder) viewHolder).onBindNewDevice();
            case MediaItem.MediaItemType.TYPE_DEVICE -> ((DeviceViewHolder) viewHolder).onBind(
                    currentMediaItem.getMediaDevice().get(), position);
            default -> Log.d(TAG, "Incorrect position: " + position);
        }
    }

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

    /**
     * Returns position of the MediaDevice with the saved device id.
     */
    protected int getFocusPosition() {
        if (DEBUG) Log.d(TAG, "getFocusPosition, deviceId: " + mSavedDeviceId);
        if (mSavedDeviceId == null) {
            return 0;
        }
        for (int i = 0; i < mMediaItemList.size(); i++) {
            MediaItem item = mMediaItemList.get(i);
            if (item.getMediaDevice().isPresent()) {
                if (item.getMediaDevice().get().getId().equals(mSavedDeviceId)) {
                    mSavedDeviceId = null;
                    return i;
                }
            }
        }
        return 0;
    }

    /**
     * Replaces the dpad action with an icon.
     */
    private CharSequence createTooltipText() {
        Resources res = mContext.getResources();
        final SpannedString tooltipText = (SpannedString) res.getText(
                R.string.audio_device_settings_tooltip);
        final SpannableString spannableString = new SpannableString(tooltipText);
        Arrays.stream(tooltipText.getSpans(0, tooltipText.length(), Annotation.class)).findFirst()
                .ifPresent(annotation -> {
                    final Drawable icon =
                            res.getDrawable(R.drawable.dpad_right, mContext.getTheme());
                    icon.setLayoutDirection(
                            mContext.getResources().getConfiguration().getLayoutDirection());
                    icon.mutate();
                    icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
                    spannableString.setSpan(new CenteredImageSpan(icon),
                            tooltipText.getSpanStart(annotation),
                            tooltipText.getSpanEnd(annotation),
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                });

        return spannableString;
    }

    @Override
    public long getItemId(int position) {
        MediaItem item = mMediaItemList.get(position);
        if (item.getMediaDevice().isPresent()) {
            return item.getMediaDevice().get().getId().hashCode();
        }
        if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
            if (item.getTitle() == null || item.getTitle().isEmpty()) {
                return MediaItem.MediaItemType.TYPE_GROUP_DIVIDER;
            }
            return item.getTitle().hashCode();
        }
        return item.getMediaItemType();
    }

    public void updateItems() {
        mMediaItemList.clear();
        mMediaItemList.addAll(mMediaOutputController.getMediaItemList());
        if (DEBUG) {
            Log.d(TAG, "updateItems");
            for (MediaItem mediaItem : mMediaItemList) {
                Log.d(TAG, mediaItem.toString());
            }
        }
        notifyDataSetChanged();
    }

    private class DeviceViewHolder extends RecyclerView.ViewHolder {
        final ImageView mIcon;
        final TextView mTitle;
        final TextView mSubtitle;
        final RadioButton mRadioButton;
        final ImageButton mA11ySettingsButton;
        final OutputDeviceControlWidget mWidget;
        MediaDevice mMediaDevice;

        DeviceViewHolder(View itemView) {
            super(itemView);
            mIcon = itemView.requireViewById(R.id.media_output_item_icon);
            mTitle = itemView.requireViewById(R.id.media_dialog_item_title);
            mSubtitle = itemView.requireViewById(R.id.media_dialog_item_subtitle);
            mRadioButton = itemView.requireViewById(R.id.media_dialog_radio_button);

            mWidget = itemView.requireViewById(R.id.media_dialog_device_widget);
            mA11ySettingsButton = itemView.requireViewById(R.id.media_dialog_item_a11y_settings);
        }

        void onBind(MediaDevice mediaDevice, int position) {
            mMediaDevice = mediaDevice;
            // Title
            mTitle.setText(mediaDevice.getName());

            // Subtitle
            setSummary(mediaDevice);

            // Icon
            Drawable icon;
            if (mediaDevice.getState()
                    == LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED) {
                icon =
                        mContext.getDrawable(
                                com.android.systemui.res.R.drawable.media_output_status_failed);
            } else {
                icon = mediaDevice.getIconWithoutBackground();
            }
            if (icon == null) {
                if (DEBUG) Log.d(TAG, "Using default icon for " + mediaDevice);
                icon = mContext.getDrawable(
                        com.android.settingslib.R.drawable.ic_media_speaker_device);
            }
            mIcon.setImageDrawable(icon);

            mRadioButton.setVisibility(mediaDevice.isConnected() ? View.VISIBLE : View.GONE);
            mRadioButton.setChecked(isCurrentlyConnected(mediaDevice));

            mWidget.setOnFocusChangeListener((view, focused) -> {
                setSummary(mediaDevice);
                mTitle.setSelected(focused);
                mSubtitle.setSelected(focused);
            });

            mWidget.setOnClickListener(v -> transferOutput(mediaDevice));

            String baseUri = getBaseUriForDevice(mContext, mMediaDevice);
            boolean hasSettings = baseUri != null && !baseUri.isEmpty();

            if (hasSettings) {
                if (mA11yManager.isEnabled()) {
                    mA11ySettingsButton.setVisibility(View.VISIBLE);
                    mA11ySettingsButton.setContentDescription(
                            mContext.getString(R.string.audio_device_settings_content_description,
                            mediaDevice.getName()));
                    mA11ySettingsButton.setOnClickListener((view) -> {
                        openDeviceSettings(baseUri);
                    });
                } else {
                    ControlWidget.TooltipConfig toolTipConfig = new ControlWidget.TooltipConfig();
                    toolTipConfig.setShouldShowTooltip(true);
                    toolTipConfig.setTooltipText(mTooltipText);
                    mWidget.setTooltipConfig(toolTipConfig);

                    mWidget.setOnKeyListener(
                            (v, keyCode, event) -> {
                                if (event.getAction() != KeyEvent.ACTION_UP) {
                                    return false;
                                }
                                int dpadArrow = mIsRtl ?
                                        KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT;
                                if (mMediaDevice != null
                                        && (keyCode == dpadArrow
                                        || (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
                                        && event.isLongPress()))) {

                                    return openDeviceSettings(baseUri);
                                }
                                return false;
                            });

                    mA11ySettingsButton.setVisibility(View.GONE);
                }
            } else {
                mA11ySettingsButton.setVisibility(View.GONE);
            }
        }

        private boolean openDeviceSettings(@NonNull String baseUri) {
            Uri uri = Uri.parse(baseUri);
            if (mMediaDevice.getDeviceType()
                    == MediaDeviceType.TYPE_BLUETOOTH_DEVICE) {
                uri =
                        Uri.withAppendedPath(
                                uri,
                                ((BluetoothMediaDevice) mMediaDevice)
                                        .getCachedDevice()
                                        .getAddress());
            }

            mSavedDeviceId = mMediaDevice.getId();
            mCallback.openDeviceSettings(
                    uri.toString(),
                    mTitle.getText(),
                    getSummary(mMediaDevice, /* focused= */ false),
                    mMediaDevice.getId());

            return true;
        }

        private void setSummary(MediaDevice mediaDevice) {
            CharSequence summary = getSummary(mediaDevice, mWidget.hasFocus());
            if (mediaDevice.getDeviceType() == MediaDeviceType.TYPE_PHONE_DEVICE
                    && mContext.getResources().getBoolean(
                    com.android.systemui.tv.res.R.bool.
                            config_audioOutputInternalSpeakerGroupedWithSpdif)) {
                mSubtitle.setText(mContext.getResources().getString(
                        R.string.media_output_internal_speaker_spdif_subtitle));
            } else {
                mSubtitle.setText(summary);
            }
            mSubtitle.setVisibility(summary == null || summary.isEmpty()
                    ? View.GONE : View.VISIBLE);
        }

        private CharSequence getSummary(MediaDevice mediaDevice, boolean focused) {
            if (mediaDevice.getState()
                    == LocalMediaManager.MediaDeviceState.STATE_CONNECTING_FAILED) {
                return mContext.getString(
                        com.android.systemui.res.R.string.media_output_dialog_connect_failed);
            } else {
                return mediaDevice.getSummaryForTv(focused
                        ? R.color.media_dialog_low_battery_focused
                        : R.color.media_dialog_low_battery_unfocused);
            }
        }

        private void transferOutput(MediaDevice mediaDevice) {
            if (mMediaOutputController.isAnyDeviceTransferring()) {
                // Don't interrupt ongoing transfer
                return;
            }
            if (isCurrentlyConnected(mediaDevice)) {
                if (DEBUG) Log.d(TAG, "Device is already selected as the active output");
                return;
            }
            mMediaOutputController.setTemporaryAllowListExceptionIfNeeded(mediaDevice);
            mMediaOutputController.connectDevice(mediaDevice);
            mediaDevice.setState(LocalMediaManager.MediaDeviceState.STATE_CONNECTING);
            notifyDataSetChanged();
        }

        /**
         * The single currentConnected device or the only selected device
         */
        boolean isCurrentlyConnected(MediaDevice device) {
            return TextUtils.equals(device.getId(),
                    mMediaOutputController.getCurrentConnectedMediaDevice().getId())
                    || (mMediaOutputController.getSelectedMediaDevice().size() == 1
                    && isDeviceIncluded(mMediaOutputController.getSelectedMediaDevice(), device));
        }

        void onBindNewDevice() {
            mIcon.setImageResource(com.android.systemui.res.R.drawable.ic_add);
            mTitle.setText(R.string.media_output_dialog_pairing_new);
            mSubtitle.setVisibility(View.GONE);
            mRadioButton.setVisibility(View.GONE);

            mWidget.setOnClickListener(v -> launchBluetoothSettings());
        }

        private void launchBluetoothSettings() {
            mCallback.dismissDialog();

            String uri = mMediaOutputController.getBluetoothSettingsSliceUri();
            if (uri == null) {
                return;
            }

            Intent bluetoothIntent = new Intent("android.settings.SLICE_SETTINGS");
            Bundle extra = new Bundle();
            extra.putString("slice_uri", uri);
            bluetoothIntent.putExtras(extra);
            bluetoothIntent.addFlags(
                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
            mContext.startActivity(bluetoothIntent);
        }

        private boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
            for (MediaDevice device : deviceList) {
                if (TextUtils.equals(device.getId(), targetDevice.getId())) {
                    return true;
                }
            }
            return false;
        }

        static String getBaseUriForDevice(Context context, MediaDevice device) {
            int resourceId;

            int deviceType = device.getDeviceType();

            if (deviceType == MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) {
                int routeType = device.getRouteType();
                switch (routeType) {
                    case MediaRoute2Info.TYPE_HDMI:
                        resourceId = R.string.audio_output_hdmi_slice_uri;
                        break;
                    case MediaRoute2Info.TYPE_HDMI_ARC:
                    case MediaRoute2Info.TYPE_HDMI_EARC:
                        resourceId = R.string.audio_output_hdmi_e_arc_slice_uri;
                        break;
                    case MediaRoute2Info.TYPE_USB_HEADSET:
                    case MediaRoute2Info.TYPE_USB_DEVICE:
                    case MediaRoute2Info.TYPE_USB_ACCESSORY:
                        resourceId = R.string.audio_output_usb_slice_uri;
                        break;
                    default:
                        return null;
                }
            } else {
                switch (deviceType) {
                    case MediaDeviceType.TYPE_PHONE_DEVICE:
                        resourceId = R.string.audio_output_builtin_speaker_slice_uri;
                        break;
                    case MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
                        resourceId = R.string.audio_output_bluetooth_slice_uri;
                        break;
                    case MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
                        resourceId = R.string.audio_output_wired_headphone_slice_uri;
                        break;
                    case MediaDeviceType.TYPE_CAST_DEVICE:
                        resourceId = R.string.audio_output_cast_device_slice_uri;
                        break;
                    case MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
                        resourceId = R.string.audio_output_cast_group_slice_uri;
                        break;
                    case MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
                        resourceId = R.string.audio_output_remote_avr_slice_uri;
                        break;
                    default:
                        return null;
                }
            }

            return context.getString(resourceId);
        }
    }

    private static class DividerViewHolder extends RecyclerView.ViewHolder {
        final TextView mHeaderText;
        final View mDividerLine;

        DividerViewHolder(@NonNull View itemView) {
            super(itemView);
            mHeaderText = itemView.requireViewById(R.id.media_output_group_header);
            mDividerLine = itemView.requireViewById(R.id.media_output_divider_line);
        }

        void onBind(String groupDividerTitle) {
            boolean hasText = groupDividerTitle != null && !groupDividerTitle.isEmpty();
            mHeaderText.setVisibility(hasText ? View.VISIBLE : View.GONE);
            mDividerLine.setVisibility(hasText ? View.GONE : View.VISIBLE);
            if (hasText) {
                mHeaderText.setText(groupDividerTitle);
            }
        }

    }

    interface PanelCallback {
        void openDeviceSettings(String uri, CharSequence title, CharSequence subtitle, String id);

        void dismissDialog();
    }
}
