/*
 * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.os.ParcelUuid;

import com.googlecode.android_scripting.Log;
import com.googlecode.android_scripting.facade.FacadeManager;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
import com.googlecode.android_scripting.rpc.Rpc;
import com.googlecode.android_scripting.rpc.RpcParameter;

import java.util.List;

public class BluetoothHspFacade extends RpcReceiver {
    static final ParcelUuid[] UUIDS = {
            BluetoothUuid.HSP, BluetoothUuid.HFP
    };

    private final Service mService;
    private final BluetoothAdapter mBluetoothAdapter;

    private static boolean sIsHspReady = false;
    private static BluetoothHeadset sHspProfile = null;

    public BluetoothHspFacade(FacadeManager manager) {
        super(manager);
        mService = manager.getService();
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mBluetoothAdapter.getProfileProxy(mService, new HspServiceListener(),
                BluetoothProfile.HEADSET);
    }

    class HspServiceListener implements BluetoothProfile.ServiceListener {
        @Override
        public void onServiceConnected(int profile, BluetoothProfile proxy) {
            sHspProfile = (BluetoothHeadset) proxy;
            sIsHspReady = true;
        }

        @Override
        public void onServiceDisconnected(int profile) {
            sIsHspReady = false;
        }
    }

    /**
     * Connect to Hsp Profile
     * @param device - the BluetoothDevice object to connect to.
     * @return if the connection was successfull or not.
     */
    public Boolean hspConnect(BluetoothDevice device) {
        if (sHspProfile == null) return false;
        return sHspProfile.connect(device);
    }

    /**
     * Disconnect to Hsp Profile.
     * @param device - the Bluetooth Device object to disconnect from.
     * @return if the disconnection was successfull or not.
     */
    public Boolean hspDisconnect(BluetoothDevice device) {
        if (sHspProfile == null) return false;
        return sHspProfile.disconnect(device);
    }

    /**
     * Wait Hsp Profile ready until timeout.
     * @param timeout - Timeout second.
     * @return true if Hsp Profile is ready else false.
     */
    public Boolean waitHspReady(long timeout) {
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() < startTime + timeout * 1000) {
            if (sIsHspReady) return true;
        }
        Log.d("Hsp profile is not ready");
        return false;
    }

    /**
     * Is Hsp profile ready.
     * @return if Hsp profile is ready or not.
     */
    @Rpc(description = "Is Hsp profile ready.")
    public Boolean bluetoothHspIsReady() {
        return sIsHspReady;
    }

    /**
     * Set connection policy of the profile.
     * @param deviceStr - name or MAC address of a Bluetooth device.
     * @param connectionPolicy - Connection policy that needs to be set.
     */
    @Rpc(description = "Set priority of the profile.")
    public void bluetoothHspSetConnectionPolicy(
            @RpcParameter(name = "device", description = "Mac address of a BT device.")
                String deviceStr,
            @RpcParameter(name = "connectionPolicy",
              description = "Connection policy that needs to be set.")
                Integer connectionPolicy) throws Exception {
        if (!waitHspReady(10)) return;
        BluetoothDevice device = BluetoothFacade.getDevice(
                mBluetoothAdapter.getBondedDevices(), deviceStr);
        Log.d("Changing connection policy of device " + device.getAlias() + " cp: "
                + connectionPolicy);
        sHspProfile.setConnectionPolicy(device, connectionPolicy);
    }

    /**
     * Connect to an HSP device.
     * @param device - Name or MAC address of a bluetooth device.
     * @return True if the connection was successful; otherwise False.
     */
    @Rpc(description = "Connect to an HSP device.")
    public Boolean bluetoothHspConnect(
            @RpcParameter(name = "device", description =
                "Name or MAC address of a bluetooth device.")
                String device) throws Exception {
        if (!waitHspReady(10)) return false;
        BluetoothDevice mDevice = BluetoothFacade.getDevice(
                mBluetoothAdapter.getBondedDevices(), device);
        Log.d("Connecting to device " + mDevice.getAlias());
        return hspConnect(mDevice);
    }

    /**
     * Disconnect an HSP device.
     * @param device - Name or MAC address of a bluetooth device.
     * @return True if the disconnection was successful; otherwise False.
     */
    @Rpc(description = "Disconnect an HSP device.")
    public Boolean bluetoothHspDisconnect(
            @RpcParameter(name = "device", description = "Name or MAC address of a device.")
                String device) throws Exception {
        if (!waitHspReady(10)) return false;
        Log.d("Connected devices: " + sHspProfile.getConnectedDevices());
        BluetoothDevice mDevice = BluetoothFacade.getDevice(
                sHspProfile.getConnectedDevices(), device);
        return hspDisconnect(mDevice);
    }

     /**
     * Get all the devices connected through HSP.
     * @return List of all the devices connected through HSP.
     */
    @Rpc(description = "Get all the devices connected through HSP.")
    public List<BluetoothDevice> bluetoothHspGetConnectedDevices() {
        if (!waitHspReady(10)) return null;
        return sHspProfile.getConnectedDevices();
    }

    /**
     * Get the connection status of a device.
     * @param deviceID - Name or MAC address of a bluetooth device.
     * @return connection status of a device.
     */
    @Rpc(description = "Get the connection status of a device.")
    public Integer bluetoothHspGetConnectionStatus(
            @RpcParameter(name = "deviceID",
                description = "Name or MAC address of a bluetooth device.")
                    String deviceID) {
        if (!waitHspReady(10)) {
            return BluetoothProfile.STATE_DISCONNECTED;
        }
        List<BluetoothDevice> deviceList = sHspProfile.getConnectedDevices();
        BluetoothDevice device;
        try {
            device = BluetoothFacade.getDevice(deviceList, deviceID);
        } catch (Exception e) {
            return BluetoothProfile.STATE_DISCONNECTED;
        }
        return sHspProfile.getConnectionState(device);
    }

    /**
     * Force SCO audio on DUT, ignore all other restrictions
     *
     * @param force True to force SCO audio, False to resume normal
     * @return True if the setup is successful
     */
    @Rpc(description = "Force SCO audio connection on DUT.")
    public Boolean bluetoothHspForceScoAudio(
            @RpcParameter(name = "force", description = "whether to force SCO audio")
                Boolean force) {
        if (!waitHspReady(10)) {
            return false;
        }
        sHspProfile.setForceScoAudio(force);
        return true;
    }

    /**
     * Connect SCO audio to a remote device
     *
     * @param deviceAddress the Bluetooth MAC address of remote device
     * @return True if connection is successful, False otherwise
     */
    @Rpc(description = "Connect SCO audio for a remote device.")
    public Boolean bluetoothHspConnectAudio(
            @RpcParameter(name = "deviceAddress",
                description = "MAC address of a bluetooth device.")
                    String deviceAddress) {
        if (!waitHspReady(10)) {
            return false;
        }
        Log.d("Connected devices: " + sHspProfile.getConnectedDevices());
        BluetoothDevice device = null;
        if (sHspProfile.getConnectedDevices().size() > 1) {
            Log.d("More than one device available");
        }
        try {
            device = BluetoothFacade.getDevice(
                    sHspProfile.getConnectedDevices(), deviceAddress);
        } catch (Exception e) {
            Log.d("Cannot find device " + deviceAddress);
            return false;
        }
        return sHspProfile.connectAudio() == BluetoothStatusCodes.SUCCESS;
    }

    /**
     * Disconnect SCO audio for a remote device
     *
     * @param deviceAddress the Bluetooth MAC address of remote device
     * @return True if disconnection is successful, False otherwise
     */
    @Rpc(description = "Disconnect SCO audio for a remote device")
    public Boolean bluetoothHspDisconnectAudio(
            @RpcParameter(name = "deviceAddress",
                description = "MAC address of a bluetooth device.")
                    String deviceAddress) {
        if (!waitHspReady(10)) {
            return false;
        }
        Log.d("Connected devices: " + sHspProfile.getConnectedDevices());
        BluetoothDevice device = null;
        if (sHspProfile.getConnectedDevices().size() > 1) {
            Log.d("More than one device available");
        }
        try {
            device = BluetoothFacade.getDevice(
                    sHspProfile.getConnectedDevices(), deviceAddress);
        } catch (Exception e) {
            Log.d("Cannot find device " + deviceAddress);
            return false;
        }
        if (!sHspProfile.isAudioConnected(device)) {
            Log.d("SCO audio is not connected for device " + deviceAddress);
            return false;
        }
        return sHspProfile.disconnectAudio() == BluetoothStatusCodes.SUCCESS;
    }

    /**
     * Check if SCO audio is connected for a remote device
     *
     * @param deviceAddress the Bluetooth MAC address of remote device
     * @return True if device is connected to us via SCO audio, False otherwise
     */
    @Rpc(description = "Check if SCO audio is connected for a remote device")
    public Boolean bluetoothHspIsAudioConnected(
            @RpcParameter(name = "deviceAddress",
                description = "MAC address of a bluetooth device.")
                    String deviceAddress) {
        if (!waitHspReady(10)) {
            return false;
        }
        Log.d("Connected devices: " + sHspProfile.getConnectedDevices());
        BluetoothDevice device = null;
        if (sHspProfile.getConnectedDevices().size() > 1) {
            Log.d("More than one device available");
        }
        try {
            device = BluetoothFacade.getDevice(
                    sHspProfile.getConnectedDevices(), deviceAddress);
        } catch (Exception e) {
            Log.d("Cannot find device " + deviceAddress);
            return false;
        }
        return sHspProfile.isAudioConnected(device);
    }

    /**
     * Start voice recognition. Send BVRA command.
     *
     * @param deviceAddress the Bluetooth MAC address of remote device
     * @return True if started successfully, False otherwise.
     */
    @Rpc(description = "Start Voice Recognition.")
    public Boolean bluetoothHspStartVoiceRecognition(
            @RpcParameter(name = "deviceAddress",
                    description = "MAC address of a bluetooth device.")
                        String deviceAddress) throws Exception {
        BluetoothDevice device = BluetoothFacade.getDevice(
                sHspProfile.getConnectedDevices(), deviceAddress);
        return sHspProfile.startVoiceRecognition(device);
    }

    /**
     * Stop voice recognition. Send BVRA command.
     *
     * @param deviceAddress the Bluetooth MAC address of remote device
     * @return True if stopped successfully, False otherwise.
     */
    @Rpc(description = "Stop Voice Recognition.")
    public Boolean bluetoothHspStopVoiceRecognition(
            @RpcParameter(name = "deviceAddress",
                description = "MAC address of a bluetooth device.")
                    String deviceAddress) throws Exception {
        BluetoothDevice device = BluetoothFacade.getDevice(
                sHspProfile.getConnectedDevices(), deviceAddress);
        return sHspProfile.stopVoiceRecognition(device);
    }

    /**
     * Determine whether in-band ringtone is enabled or not.
     *
     * @return True if enabled, False otherwise.
     */
    @Rpc(description = "In-band ringtone enabled check.")
    public Boolean bluetoothHspIsInbandRingingEnabled() {
        return sHspProfile.isInbandRingingEnabled();
    }

    /**
     * Send a CLCC response from Sl4a (experimental).
     *
     * @param index the index of the call
     * @param direction the direction of the call
     * @param status the status of the call
     * @param mode the mode
     * @param mpty the mpty value
     * @param number the phone number
     * @param type the type
     */
    @Rpc(description = "Send generic clcc response.")
    public void bluetoothHspClccResponse(
            @RpcParameter(name = "index", description = "") Integer index,
            @RpcParameter(name = "direction", description = "") Integer direction,
            @RpcParameter(name = "status", description = "") Integer status,
            @RpcParameter(name = "mode", description = "") Integer mode,
            @RpcParameter(name = "mpty", description = "") Boolean mpty,
            @RpcParameter(name = "number", description = "") String number,
            @RpcParameter(name = "type", description = "") Integer type
                  ) {
        sHspProfile.clccResponse(index, direction, status, mode, mpty, number, type);
    }

    @Override
    public void shutdown() {
    }
}
