/*
 * Copyright (C) 2024 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.accessibility.hearingaid;

import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;

import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;

import android.content.Context;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.settingslib.bluetooth.hearingdevices.ui.AmbientVolumeUi;
import com.android.systemui.res.R;

import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.primitives.Ints;

import java.util.Map;
import java.util.Set;

/**
 * A view of ambient volume controls.
 *
 * <p> It consists of a header with an expand icon and volume sliders for unified control and
 * separated control for devices in the same set. Toggle the expand icon will make the UI switch
 * between unified and separated control.
 */
public class AmbientVolumeLayout extends LinearLayout implements AmbientVolumeUi {

    @Nullable
    private AmbientVolumeUiListener mListener;
    private ImageView mExpandIcon;
    private ImageView mVolumeIcon;
    private final BiMap<Integer, AmbientVolumeSlider> mSideToSliderMap = HashBiMap.create();
    private final Map<Integer, Integer> mSideToMuteStateMap = new ArrayMap<>();

    private boolean mExpandable = true;
    private boolean mExpanded = false;
    private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;

    private HearingDevicesUiEventLogger mUiEventLogger;
    private int mLaunchSourceId;

    private final AmbientVolumeSlider.OnChangeListener mSliderOnChangeListener =
            (slider, value) -> {
                final Integer side = mSideToSliderMap.inverse().get(slider);
                if (side != null) {
                    if (mUiEventLogger != null) {
                        HearingDevicesUiEvent uiEvent = side == SIDE_UNIFIED
                                ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_UNIFIED
                                : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_CHANGE_SEPARATED;
                        mUiEventLogger.log(uiEvent, mLaunchSourceId);
                    }
                    if (mListener != null) {
                        mListener.onSliderValueChange(side, value);
                    }
                }
            };

    public AmbientVolumeLayout(@Nullable Context context) {
        this(context, /* attrs= */ null);
    }

    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, /* defStyleAttr= */ 0);
    }

    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        this(context, attrs, defStyleAttr, /* defStyleRes= */ 0);
    }

    public AmbientVolumeLayout(@Nullable Context context, @Nullable AttributeSet attrs,
            int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        inflate(context, R.layout.hearing_device_ambient_volume_layout, /* root= */ this);
        init();
    }

    private void init() {
        mVolumeIcon = requireViewById(R.id.ambient_volume_icon);
        mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
        mVolumeIcon.setOnClickListener(v -> {
            if (!isMutable()) {
                return;
            }
            int updatedMuteState = isMuted() ? MUTE_NOT_MUTED : MUTE_MUTED;
            setSliderMuteState(SIDE_UNIFIED, updatedMuteState);
            if (mUiEventLogger != null) {
                HearingDevicesUiEvent uiEvent = isMuted()
                        ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_MUTE
                        : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_UNMUTE;
                mUiEventLogger.log(uiEvent, mLaunchSourceId);
            }
            if (mListener != null) {
                mListener.onAmbientVolumeIconClick();
            }
        });
        updateVolumeIcon();

        mExpandIcon = requireViewById(R.id.ambient_expand_icon);
        mExpandIcon.setOnClickListener(v -> {
            if (!isControlExpandable()) {
                return;
            }
            setControlExpanded(!mExpanded);
            if (mUiEventLogger != null) {
                HearingDevicesUiEvent uiEvent = mExpanded
                        ? HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_EXPAND_CONTROLS
                        : HearingDevicesUiEvent.HEARING_DEVICES_AMBIENT_COLLAPSE_CONTROLS;
                mUiEventLogger.log(uiEvent, mLaunchSourceId);
            }
            if (mListener != null) {
                mListener.onExpandIconClick();
            }
        });
        updateExpandIcon();
    }

    @Override
    public void setVisible(boolean visible) {
        setVisibility(visible ? VISIBLE : GONE);
    }

    @Override
    public void setControlExpandable(boolean expandable) {
        if (mExpandable != expandable) {
            mExpandable = expandable;
            if (!mExpandable) {
                setControlExpanded(false);
            }
            updateExpandIcon();
        }
    }

    @Override
    public boolean isControlExpandable() {
        return mExpandable;
    }

    @Override
    public void setControlExpanded(boolean expanded) {
        if (mExpanded != expanded) {
            mExpanded = expanded;
            updateExpandIcon();
            updateLayout();
        }
    }

    @Override
    public boolean isControlExpanded() {
        return mExpanded;
    }

    @Override
    public boolean isMutable() {
        return mSideToMuteStateMap.values().stream().anyMatch(mute -> mute != MUTE_DISABLED);
    }

    @Override
    public boolean isMuted() {
        return mSideToMuteStateMap.values().stream().allMatch(mute -> mute == MUTE_MUTED);
    }

    @Override
    public void setSliderMuteState(int side, int muteState) {
        if (side == SIDE_UNIFIED) {
            // propagate the mute state to all other sliders
            mSideToSliderMap.keySet().forEach(s -> {
                if (s != SIDE_UNIFIED) {
                    setSliderMuteState(s, muteState);
                }
            });
        } else {
            AmbientVolumeSlider slider = mSideToSliderMap.get(side);
            if (slider != null) {
                mSideToMuteStateMap.put(side, muteState);
                if (muteState == MUTE_MUTED) {
                    slider.setValue(slider.getMin());
                }
                AmbientVolumeSlider unifiedSlider = mSideToSliderMap.get(SIDE_UNIFIED);
                if (isMuted() && unifiedSlider != null) {
                    unifiedSlider.setValue(unifiedSlider.getMin());
                }
                updateVolumeLevel();
            }
        }
    }

    @Override
    public int getSliderMuteState(int side) {
        if (side == SIDE_UNIFIED) {
            if (!isMutable()) {
                return MUTE_DISABLED;
            } else {
                return isMuted() ? MUTE_MUTED : MUTE_NOT_MUTED;
            }
        } else {
            return mSideToMuteStateMap.getOrDefault(side, MUTE_DISABLED);
        }
    }

    @Override
    public void setListener(@Nullable AmbientVolumeUiListener listener) {
        mListener = listener;
    }

    @Override
    public void setupSliders(@NonNull Set<Integer> sides) {
        sides.forEach(this::createSlider);
        createSlider(SIDE_UNIFIED);

        LinearLayout controlContainer = requireViewById(R.id.ambient_control_container);
        controlContainer.removeAllViews();
        if (!mSideToSliderMap.isEmpty()) {
            for (int side : VALID_SIDES) {
                final AmbientVolumeSlider slider = mSideToSliderMap.get(side);
                if (slider != null) {
                    controlContainer.addView(slider);
                }
            }
        }
        updateLayout();
    }

    @Override
    public void setSliderValue(int side, int value) {
        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
        if (slider != null && slider.getValue() != value) {
            slider.setValue(value);
            updateVolumeLevel();
        }
    }

    @Override
    public void setSliderEnabled(int side, boolean enabled) {
        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
        if (slider != null) {
            slider.setEnabled(enabled);
            if (!enabled) {
                slider.setValue(slider.getMin());
            }
            updateVolumeLevel();
        }
    }

    @Override
    public void setSliderRange(int side, int min, int max) {
        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
        if (slider != null) {
            slider.setMin(min);
            slider.setMax(max);
        }
    }

    private void updateLayout() {
        mSideToSliderMap.forEach((side, slider) -> {
            if (side == SIDE_UNIFIED) {
                slider.setVisibility(mExpanded ? GONE : VISIBLE);
            } else {
                slider.setVisibility(mExpanded ? VISIBLE : GONE);
            }
        });
        updateVolumeLevel();
    }

    void setUiEventLogger(HearingDevicesUiEventLogger uiEventLogger, int launchSourceId) {
        mUiEventLogger = uiEventLogger;
        mLaunchSourceId = launchSourceId;
    }

    private void updateVolumeLevel() {
        int leftLevel, rightLevel;
        if (isControlExpanded()) {
            leftLevel = getVolumeLevel(SIDE_LEFT);
            rightLevel = getVolumeLevel(SIDE_RIGHT);
        } else {
            final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
            leftLevel = unifiedLevel;
            rightLevel = unifiedLevel;
        }
        mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
                AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
        updateVolumeIcon();
    }

    private int getVolumeLevel(int side) {
        AmbientVolumeSlider slider = mSideToSliderMap.get(side);
        if (slider == null || !slider.isEnabled()) {
            return 0;
        }
        return slider.getVolumeLevel();
    }

    private void updateExpandIcon() {
        mExpandIcon.setVisibility(isControlExpandable() ? VISIBLE : GONE);
        mExpandIcon.setRotation(isControlExpanded() ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
        if (isControlExpandable()) {
            final int stringRes = isControlExpanded()
                    ? R.string.hearing_devices_ambient_collapse_controls
                    : R.string.hearing_devices_ambient_expand_controls;
            mExpandIcon.setContentDescription(mContext.getString(stringRes));
        } else {
            mExpandIcon.setContentDescription(null);
        }
    }

    private void updateVolumeIcon() {
        mVolumeIcon.setImageLevel(mVolumeLevel);
        if (isMutable()) {
            final int stringRes = isMuted() ? R.string.hearing_devices_ambient_unmute
                    : R.string.hearing_devices_ambient_mute;
            mVolumeIcon.setContentDescription(mContext.getString(stringRes));
            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }  else {
            mVolumeIcon.setContentDescription(null);
            mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
        }
    }

    private void createSlider(int side) {
        if (mSideToSliderMap.containsKey(side)) {
            return;
        }
        AmbientVolumeSlider slider = new AmbientVolumeSlider(mContext);
        slider.addOnChangeListener(mSliderOnChangeListener);
        if (side == SIDE_LEFT) {
            slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_left));
            slider.setContentDescription(
                    mContext.getString(R.string.hearing_devices_ambient_control_left));
            slider.setSliderContentDescription(
                    mContext.getString(R.string.hearing_devices_ambient_control_left_description));
        } else if (side == SIDE_RIGHT) {
            slider.setTitle(mContext.getString(R.string.hearing_devices_ambient_control_right));
            slider.setContentDescription(
                    mContext.getString(R.string.hearing_devices_ambient_control_right));
            slider.setSliderContentDescription(
                    mContext.getString(R.string.hearing_devices_ambient_control_right_description));
        } else {
            slider.setSliderContentDescription(
                    mContext.getString(R.string.hearing_devices_ambient_control_description));
        }
        mSideToSliderMap.put(side, slider);
    }

    @VisibleForTesting
    ImageView getVolumeIcon() {
        return mVolumeIcon;
    }

    @VisibleForTesting
    ImageView getExpandIcon() {
        return mExpandIcon;
    }

    @VisibleForTesting
    Map<Integer, AmbientVolumeSlider> getSliders() {
        return mSideToSliderMap;
    }
}
