/*
 * 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.car.bluetooth;

import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHeadsetClient.NetworkServiceState;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.telephony.SignalStrength;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;

import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
import com.android.systemui.statusbar.policy.BluetoothController;

/**
 * Controller that monitors signal strength for a device that is connected via bluetooth.
 */
public class ConnectedDeviceSignalController extends BroadcastReceiver implements
        BluetoothController.Callback {
    private static final String TAG = "DeviceSignalCtlr";

    /**
     * The value that indicates if a network is unavailable. This value is according ot the
     * Bluetooth HFP 1.5 spec, which indicates this value is one of two: 0 or 1. These stand
     * for network unavailable and available respectively.
     */
    private static final int NETWORK_UNAVAILABLE = 0;
    private static final int NETWORK_UNAVAILABLE_ICON_ID = R.drawable.stat_sys_signal_null;

    /**
     * All possible signal strength icons. According to the Bluetooth HFP 1.5 specification,
     * signal strength is indicated by a value from 1-5, where these values represent the following:
     *
     * <p>0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
     *
     * <p>As a result, these are treated as an index into this array for the corresponding icon.
     * Note that the icon is the same for 0 and 1.
     */
    private static final int[] SIGNAL_STRENGTH_ICONS = {
            0,
            0,
            1,
            2,
            3,
            4,
    };

    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
    private final Context mContext;
    private final BluetoothController mController;

    private final View mSignalsView;
    private final ImageView mNetworkSignalView;

    private final float mIconScaleFactor;
    private final SignalDrawable mSignalDrawable;

    private BluetoothHeadsetClient mBluetoothHeadsetClient;
    private final ServiceListener mHfpServiceListener = new ServiceListener() {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            if (profile == BluetoothProfile.HEADSET_CLIENT) {
                mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
            }
        }

        @Override
        public void onServiceDisconnected(int profile) {
            if (profile == BluetoothProfile.HEADSET_CLIENT) {
                mBluetoothHeadsetClient = null;
            }
        }
    };

    public ConnectedDeviceSignalController(Context context, View signalsView) {
        mContext = context;
        mController = Dependency.get(BluetoothController.class);

        mSignalsView = signalsView;
        mNetworkSignalView = (ImageView)
                mSignalsView.findViewById(R.id.connected_device_network_signal);

        TypedValue typedValue = new TypedValue();
        context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
        mIconScaleFactor = typedValue.getFloat();
        mSignalDrawable = new SignalDrawable(mNetworkSignalView.getContext());
        mNetworkSignalView.setImageDrawable(
                new ScalingDrawableWrapper(mSignalDrawable, mIconScaleFactor));

        if (mAdapter == null) {
            return;
        }

        mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
                BluetoothProfile.HEADSET_CLIENT);
    }

    /** Starts listening for bluetooth broadcast messages. */
    public void startListening() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
        filter.addAction(BluetoothHeadsetClient.ACTION_NETWORK_SERVICE_STATE_CHANGED);
        mContext.registerReceiver(this, filter);

        mController.addCallback(this);
    }

    /** Stops listening for bluetooth broadcast messages. */
    public void stopListening() {
        mContext.unregisterReceiver(this);
        mController.removeCallback(this);
    }

    @Override
    public void onBluetoothDevicesChanged() {
        // Nothing to do here because this Controller is not displaying a list of possible
        // bluetooth devices.
    }

    @Override
    public void onBluetoothStateChange(boolean enabled) {
        if (DEBUG) {
            Log.d(TAG, "onBluetoothStateChange(). enabled: " + enabled);
        }

        // Only need to handle the case if bluetooth has been disabled, in which case the
        // signal indicators are hidden. If bluetooth has been enabled, then this class should
        // receive updates to the connection state via onReceive().
        if (!enabled) {
            mNetworkSignalView.setVisibility(View.GONE);
            mSignalsView.setVisibility(View.GONE);
        }
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (DEBUG) {
            Log.d(TAG, "onReceive(). action: " + action);
        }

        if (BluetoothHeadsetClient.ACTION_NETWORK_SERVICE_STATE_CHANGED.equals(action)) {
            if (DEBUG) {
                Log.d(TAG, "Received ACTION_NETWORK_SERVICE_STATE_CHANGED");
            }

            processActionNetworkServiceStateEvent(intent);
        } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
            int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);

            if (DEBUG) {
                int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
                Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
                        + oldState + " -> " + newState);
            }
            BluetoothDevice device =
                    (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
            updateViewVisibility(device, newState);
        }
    }

    /**
     * Processes an {@link Intent} that had an action of
     * {@link BluetoothHeadsetClient#ACTION_NETWORK_SERVICE_STATE_CHANGED}.
     */
    private void processActionNetworkServiceStateEvent(Intent intent) {
        NetworkServiceState state = (NetworkServiceState) intent.getExtra(
                BluetoothHeadsetClient.EXTRA_NETWORK_SERVICE_STATE, null);

        if (!state.isServiceAvailable()) {
            setNetworkSignalIcon(NETWORK_UNAVAILABLE_ICON_ID);
        }

        setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[state.getSignalStrength()]);
    }

    private void setNetworkSignalIcon(int level) {
        // Setting the icon on a child view of mSignalView, so toggle this container visible.
        mSignalsView.setVisibility(View.VISIBLE);

        mSignalDrawable.setLevel(SignalDrawable.getState(level,
                SignalStrength.NUM_SIGNAL_STRENGTH_BINS, false));
        mNetworkSignalView.setVisibility(View.VISIBLE);
    }

    private void updateViewVisibility(BluetoothDevice device, int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            if (DEBUG) {
                Log.d(TAG, "Device connected");
            }

            if (mBluetoothHeadsetClient == null || device == null) {
                return;
            }

            // Check if network signal strength information is available and immediately update.
            NetworkServiceState state = mBluetoothHeadsetClient.getNetworkServiceState(device);
            if (state == null) {
                return;
            }

            int signalStrength = state.getSignalStrength();
            if (DEBUG) {
                Log.d(TAG, "NetworkServiceState getSignalStrength(): " + signalStrength);
            }
            setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            if (DEBUG) {
                Log.d(TAG, "Device disconnected");
            }

            mNetworkSignalView.setVisibility(View.GONE);
            mSignalsView.setVisibility(View.GONE);
        }
    }
}
