/*
 * Copyright (C) 2020 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.media.dialog;

import static android.media.MediaRoute2ProviderService.REASON_INVALID_COMMAND;
import static android.media.MediaRoute2ProviderService.REASON_NETWORK_ERROR;
import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Log;

import com.android.settingslib.media.MediaDevice;
import com.android.systemui.shared.system.SysUiStatsLog;

import java.util.List;

/**
 * Metric logger for media output features
 */
public class MediaOutputMetricLogger {

    private static final String TAG = "MediaOutputMetricLogger";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

    private final Context mContext;
    private final String mPackageName;
    private MediaDevice mSourceDevice, mTargetDevice;
    private int mWiredDeviceCount;
    private int mConnectedBluetoothDeviceCount;
    private int mRemoteDeviceCount;
    private int mAppliedDeviceCountWithinRemoteGroup;

    public MediaOutputMetricLogger(Context context, String packageName) {
        mContext = context;
        mPackageName = packageName;
    }

    /**
     * Update the endpoints of a content switching operation.
     * This method should be called before a switching operation, so the metric logger can track
     * source and target devices.
     *
     * @param source the current connected media device
     * @param target the target media device for content switching to
     */
    public void updateOutputEndPoints(MediaDevice source, MediaDevice target) {
        mSourceDevice = source;
        mTargetDevice = target;

        if (DEBUG) {
            Log.d(TAG, "updateOutputEndPoints -"
                    + " source:" + mSourceDevice.toString()
                    + " target:" + mTargetDevice.toString());
        }
    }

    /**
     * Do the metric logging of content switching success.
     *
     * @param selectedDeviceType string representation of the target media device
     * @param deviceItemList     media item list for device count updating
     */
    public void logOutputItemSuccess(String selectedDeviceType, List<MediaItem> deviceItemList) {
        if (DEBUG) {
            Log.d(TAG, "logOutputSuccess - selected device: " + selectedDeviceType);
        }

        if (mSourceDevice == null && mTargetDevice == null) {
            return;
        }

        updateLoggingMediaItemCount(deviceItemList);

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
                getLoggingDeviceType(mSourceDevice, true),
                getLoggingDeviceType(mTargetDevice, false),
                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__OK,
                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NO_ERROR,
                getLoggingPackageName(),
                mWiredDeviceCount,
                mConnectedBluetoothDeviceCount,
                mRemoteDeviceCount,
                mAppliedDeviceCountWithinRemoteGroup,
                mTargetDevice.isSuggestedDevice(),
                mTargetDevice.hasOngoingSession());
    }

    /**
     * Do the metric logging of volume adjustment.
     *
     * @param source the device been adjusted
     */
    public void logInteractionAdjustVolume(MediaDevice source) {
        if (DEBUG) {
            Log.d(TAG, "logInteraction - AdjustVolume");
        }

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__ADJUST_VOLUME,
                getInteractionDeviceType(source),
                getLoggingPackageName(),
                source.isSuggestedDevice());
    }

    /**
     * Do the metric logging of stop casting.
     */
    public void logInteractionStopCasting() {
        if (DEBUG) {
            Log.d(TAG, "logInteraction - Stop casting");
        }

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__STOP_CASTING,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE,
                getLoggingPackageName(),
                /*isSuggestedDevice = */false);
    }

    /**
     * Do the metric logging of device expansion.
     */
    public void logInteractionExpansion(MediaDevice source) {
        if (DEBUG) {
            Log.d(TAG, "logInteraction - Expansion");
        }

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__EXPANSION,
                getInteractionDeviceType(source),
                getLoggingPackageName(),
                source.isSuggestedDevice());
    }

    /**
     * Do the metric logging of muting device.
     */
    public void logInteractionMute(MediaDevice source) {
        if (DEBUG) {
            Log.d(TAG, "logInteraction - Mute");
        }

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__MUTE,
                getInteractionDeviceType(source),
                getLoggingPackageName(),
                source.isSuggestedDevice());
    }

    /**
     * Do the metric logging of unmuting device.
     */
    public void logInteractionUnmute(MediaDevice source) {
        if (DEBUG) {
            Log.d(TAG, "logInteraction - Unmute");
        }

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_INTERACTION_REPORT,
                SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__INTERACTION_TYPE__UNMUTE,
                getInteractionDeviceType(source),
                getLoggingPackageName(),
                source.isSuggestedDevice());
    }

    /**
     * Do the metric logging of content switching failure.
     *
     * @param deviceItemList media item list for device count updating
     * @param reason         the reason of content switching failure
     */
    public void logOutputItemFailure(List<MediaItem> deviceItemList, int reason) {
        if (DEBUG) {
            Log.e(TAG, "logRequestFailed - " + reason);
        }

        if (mSourceDevice == null && mTargetDevice == null) {
            return;
        }

        updateLoggingMediaItemCount(deviceItemList);

        SysUiStatsLog.write(
                SysUiStatsLog.MEDIAOUTPUT_OP_SWITCH_REPORTED,
                getLoggingDeviceType(mSourceDevice, true),
                getLoggingDeviceType(mTargetDevice, false),
                SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__RESULT__ERROR,
                getLoggingSwitchOpSubResult(reason),
                getLoggingPackageName(),
                mWiredDeviceCount,
                mConnectedBluetoothDeviceCount,
                mRemoteDeviceCount,
                mAppliedDeviceCountWithinRemoteGroup,
                mTargetDevice.isSuggestedDevice(),
                mTargetDevice.hasOngoingSession());
    }

    private void updateLoggingDeviceCount(List<MediaDevice> deviceList) {
        mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
        mAppliedDeviceCountWithinRemoteGroup = 0;

        for (MediaDevice mediaDevice : deviceList) {
            if (mediaDevice.isConnected()) {
                switch (mediaDevice.getDeviceType()) {
                    case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
                    case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
                        mWiredDeviceCount++;
                        break;
                    case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
                        mConnectedBluetoothDeviceCount++;
                        break;
                    case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
                    case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
                        mRemoteDeviceCount++;
                        break;
                    default:
                }
            }
        }

        if (DEBUG) {
            Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
                    + " bluetooth: " + mConnectedBluetoothDeviceCount
                    + " remote: " + mRemoteDeviceCount);
        }
    }

    private void updateLoggingMediaItemCount(List<MediaItem> deviceItemList) {
        mWiredDeviceCount = mConnectedBluetoothDeviceCount = mRemoteDeviceCount = 0;
        mAppliedDeviceCountWithinRemoteGroup = 0;

        for (MediaItem mediaItem : deviceItemList) {
            if (mediaItem.getMediaDevice().isPresent()
                    && mediaItem.getMediaDevice().get().isConnected()) {
                switch (mediaItem.getMediaDevice().get().getDeviceType()) {
                    case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
                    case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
                        mWiredDeviceCount++;
                        break;
                    case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
                        mConnectedBluetoothDeviceCount++;
                        break;
                    case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
                    case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
                        mRemoteDeviceCount++;
                        break;
                    default:
                }
            }
        }

        if (DEBUG) {
            Log.d(TAG, "connected devices:" + " wired: " + mWiredDeviceCount
                    + " bluetooth: " + mConnectedBluetoothDeviceCount
                    + " remote: " + mRemoteDeviceCount);
        }
    }

    private int getLoggingDeviceType(MediaDevice device, boolean isSourceDevice) {
        if (device == null) {
            return isSourceDevice
                    ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
                    : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
        }
        switch (device.getDeviceType()) {
            case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BUILTIN_SPEAKER
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BUILTIN_SPEAKER;
            case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog
                        .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__WIRED_3POINT5_MM_AUDIO
                        : SysUiStatsLog
                                .MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
            case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__USB_C_AUDIO
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__USB_C_AUDIO;
            case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__BLUETOOTH
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__BLUETOOTH;
            case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_SINGLE
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_SINGLE;
            case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__REMOTE_GROUP
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__REMOTE_GROUP;
            case MediaDevice.MediaDeviceType.TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__AVR
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__AVR;
            default:
                return isSourceDevice
                        ? SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SOURCE__UNKNOWN_TYPE
                        : SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__TARGET__UNKNOWN_TYPE;
        }
    }

    private int getInteractionDeviceType(MediaDevice device) {
        if (device == null) {
            return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
        }
        switch (device.getDeviceType()) {
            case MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BUILTIN_SPEAKER;
            case MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE:
                return SysUiStatsLog
                        .MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__WIRED_3POINT5_MM_AUDIO;
            case MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__USB_C_AUDIO;
            case MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__BLUETOOTH;
            case MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__REMOTE_SINGLE;
            case MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__REMOTE_GROUP;
            default:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_INTERACTION_REPORTED__TARGET__UNKNOWN_TYPE;
        }
    }


    private int getLoggingSwitchOpSubResult(int reason) {
        switch (reason) {
            case REASON_REJECTED:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__REJECTED;
            case REASON_NETWORK_ERROR:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__NETWORK_ERROR;
            case REASON_ROUTE_NOT_AVAILABLE:
                return SysUiStatsLog
                        .MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__ROUTE_NOT_AVAILABLE;
            case REASON_INVALID_COMMAND:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__INVALID_COMMAND;
            case REASON_UNKNOWN_ERROR:
            default:
                return SysUiStatsLog.MEDIA_OUTPUT_OP_SWITCH_REPORTED__SUBRESULT__UNKNOWN_ERROR;
        }
    }

    private String getLoggingPackageName() {
        if (mPackageName != null && !mPackageName.isEmpty()) {
            try {
                final ApplicationInfo applicationInfo = mContext.getPackageManager()
                        .getApplicationInfo(mPackageName, /* default flag */ 0);
                if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
                        || (applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
                    return mPackageName;
                }
            } catch (Exception ex) {
                Log.e(TAG, mPackageName + " is invalid.");
            }
        }

        return "";
    }
}
