package com.android.bluetooth.hfpclient;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.util.Log;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;

import com.android.bluetooth.btservice.AdapterService;

import static org.mockito.Mockito.*;
import org.mockito.ArgumentCaptor;

public class HeadsetClientStateMachineTest extends AndroidTestCase {
    private BluetoothAdapter mAdapter = null;

    @Override
    protected void setUp() throws Exception {
        AdapterService inst = AdapterService.getAdapterService();
        assertTrue(inst != null);
        mAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    // Test that default state is disconnected
    public void testDefaultDisconnectedState() {
        HeadsetClientService mockService = mock(HeadsetClientService.class);
        AudioManager mockAudioManager = mock(AudioManager.class);

        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);

        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
            mockService, getContext().getMainLooper());
        assertEquals(
            mockSM.getConnectionState((BluetoothDevice) null), BluetoothProfile.STATE_DISCONNECTED);
    }

    // Test that an incoming connection with low priority is rejected
    public void testIncomingPriorityReject() {
        HeadsetClientService mockService = mock(HeadsetClientService.class);
        AudioManager mockAudioManager = mock(AudioManager.class);
        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");

        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);

        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
            mockService, getContext().getMainLooper());
        mockSM.start();

        // Return false for priority.
        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
            BluetoothProfile.PRIORITY_OFF);

        // Inject an event for when incoming connection is requested
        StackEvent connStCh =
            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
        connStCh.valueInt2 = 0;
        connStCh.valueInt3 = 0;
        connStCh.device = device;
        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);

        // Verify that no connection state broadcast is executed
        verify(mockService, never()).sendBroadcast(any(Intent.class), anyString());
        // Check we are in disconnected state still.
        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Disconnected);
    }

    // Test that an incoming connection with high priority is accepted
    public void testIncomingPriorityAccept() {
        HeadsetClientService mockService = mock(HeadsetClientService.class);
        AudioManager mockAudioManager = mock(AudioManager.class);
        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");

        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
        // Set a valid volume
        when(mockAudioManager.getStreamVolume(anyInt())).thenReturn(2);
        when(mockAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
        when(mockAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);


        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
            mockService, getContext().getMainLooper());
        mockSM.start();

        // Return false for priority.
        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
            BluetoothProfile.PRIORITY_ON);

        // Inject an event for when incoming connection is requested
        StackEvent connStCh =
            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
        connStCh.valueInt2 = 0;
        connStCh.valueInt3 = 0;
        connStCh.device = device;
        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);

        // Verify that one connection state broadcast is executed
        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
        verify(mockService,
            timeout(1000)).sendBroadcast(intentArgument1.capture(), anyString());
        assertEquals(BluetoothProfile.STATE_CONNECTING,
            intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));

        // Check we are in connecting state now.
        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connecting);

        // Send a message to trigger SLC connection
        StackEvent slcEvent =
            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
        slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
        slcEvent.valueInt3 = 0;
        slcEvent.device = device;
        mockSM.sendMessage(StackEvent.STACK_EVENT, slcEvent);

        // Verify that one connection state broadcast is executed
        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
        verify(mockService,
            timeout(1000).times(2)).sendBroadcast(intentArgument2.capture(), anyString());
        assertEquals(BluetoothProfile.STATE_CONNECTED,
            intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
        // Check we are in connecting state now.
        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connected);
    }

    // Test that an incoming connection that times out
    public void testIncomingTimeout() {
        HeadsetClientService mockService = mock(HeadsetClientService.class);
        AudioManager mockAudioManager = mock(AudioManager.class);
        BluetoothDevice device = mAdapter.getRemoteDevice("00:01:02:03:04:05");

        when(mockService.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mockAudioManager);
        // Set a valid volume
        when(mockAudioManager.getStreamVolume(anyInt())).thenReturn(2);
        when(mockAudioManager.getStreamMaxVolume(anyInt())).thenReturn(10);
        when(mockAudioManager.getStreamMinVolume(anyInt())).thenReturn(1);


        HeadsetClientStateMachine mockSM = new HeadsetClientStateMachine(
            mockService, getContext().getMainLooper());
        mockSM.start();

        // Return false for priority.
        when(mockService.getPriority(any(BluetoothDevice.class))).thenReturn(
            BluetoothProfile.PRIORITY_ON);

        // Inject an event for when incoming connection is requested
        StackEvent connStCh =
            new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
        connStCh.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
        connStCh.valueInt2 = 0;
        connStCh.valueInt3 = 0;
        connStCh.device = device;
        mockSM.sendMessage(StackEvent.STACK_EVENT, connStCh);

        // Verify that one connection state broadcast is executed
        ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
        verify(mockService, timeout(1000)).sendBroadcast(intentArgument1.capture(), anyString());
        assertEquals(BluetoothProfile.STATE_CONNECTING,
            intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));

        // Check we are in connecting state now.
        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Connecting);

        // Verify that one connection state broadcast is executed
        ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
        verify(mockService,
            timeout(HeadsetClientStateMachine.CONNECTING_TIMEOUT_MS * 2).times(2)).sendBroadcast(
            intentArgument2.capture(), anyString());
        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
            intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));

        // Check we are in connecting state now.
        assertTrue(mockSM.getCurrentState() instanceof HeadsetClientStateMachine.Disconnected);
    }
}
