/*
 * Copyright (C) 2011 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.dialer.voicemail;

import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE;
import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_CAN_BE_CONFIGURED;
import static android.provider.VoicemailContract.Status.CONFIGURATION_STATE_NOT_CONFIGURED;
import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE;
import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_NO_CONNECTION;
import static android.provider.VoicemailContract.Status.DATA_CHANNEL_STATE_OK;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION;
import static android.provider.VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.VoicemailContract.Status;
import android.test.AndroidTestCase;

import com.android.dialer.R;
import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;

import java.util.List;

/**
 * Unit tests for {@link VoicemailStatusHelperImpl}.
 */
public class VoicemailStatusHelperImplTest extends AndroidTestCase {
    private static final String[] TEST_PACKAGES = new String[] {
        "com.test.package1",
        "com.test.package2"
    };

    private static final Uri TEST_SETTINGS_URI = Uri.parse("http://www.visual.voicemail.setup");
    private static final Uri TEST_VOICEMAIL_URI = Uri.parse("tel:901");

    private static final int ACTION_MSG_CALL_VOICEMAIL =
            R.string.voicemail_status_action_call_server;
    private static final int ACTION_MSG_CONFIGURE = R.string.voicemail_status_action_configure;

    private static final int STATUS_MSG_NONE = -1;
    private static final int STATUS_MSG_VOICEMAIL_NOT_AVAILABLE =
            R.string.voicemail_status_voicemail_not_available;
    private static final int STATUS_MSG_AUDIO_NOT_AVAIALABLE =
            R.string.voicemail_status_audio_not_available;
    private static final int STATUS_MSG_MESSAGE_WAITING = R.string.voicemail_status_messages_waiting;
    private static final int STATUS_MSG_INVITE_FOR_CONFIGURATION =
            R.string.voicemail_status_configure_voicemail;

    // Object under test.
    private VoicemailStatusHelper mStatusHelper;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mStatusHelper = new VoicemailStatusHelperImpl();
    }

    @Override
    protected void tearDown() throws Exception {
        for (String sourcePackage : TEST_PACKAGES) {
            deleteEntryForPackage(sourcePackage);
        }
        // Set member variables to null so that they are garbage collected across different runs
        // of the tests.
        mStatusHelper = null;
        super.tearDown();
    }


    public void testNoStatusEntries() {
        assertEquals(0, getStatusMessages().size());
    }

    public void testAllOK() {
        insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
        insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());
        assertEquals(0, getStatusMessages().size());
    }

    public void testNotAllOKForOnePackage() {
        insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
        insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());

        ContentValues values = new ContentValues();
        // Good data channel + no notification
        // action: call voicemail
        // msg: voicemail not available in call log page & none in call details page.
        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
                STATUS_MSG_NONE, ACTION_MSG_CALL_VOICEMAIL);

        // Message waiting + good data channel - no action.
        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkNoMessages(TEST_PACKAGES[1], values);

        // No data channel + no notification
        // action: call voicemail
        // msg: voicemail not available in call log page & audio not available in call details page.
        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_OK);
        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
                STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);

        // No data channel + Notification OK
        // action: call voicemail
        // msg: voicemail not available in call log page & audio not available in call details page.
        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_VOICEMAIL_NOT_AVAILABLE,
                STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);

        // No data channel + Notification OK
        // action: call voicemail
        // msg: message waiting in call log page & audio not available in call details page.
        values.put(NOTIFICATION_CHANNEL_STATE, NOTIFICATION_CHANNEL_STATE_MESSAGE_WAITING);
        values.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_MESSAGE_WAITING,
                STATUS_MSG_AUDIO_NOT_AVAIALABLE, ACTION_MSG_CALL_VOICEMAIL);

        // Not configured. No user action, so no message.
        values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_NOT_CONFIGURED);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkNoMessages(TEST_PACKAGES[1], values);

        // Can be configured - invite user for configure voicemail.
        values.put(CONFIGURATION_STATE, CONFIGURATION_STATE_CAN_BE_CONFIGURED);
        updateEntryForPackage(TEST_PACKAGES[1], values);
        checkExpectedMessage(TEST_PACKAGES[1], values, STATUS_MSG_INVITE_FOR_CONFIGURATION,
                STATUS_MSG_NONE, ACTION_MSG_CONFIGURE, TEST_SETTINGS_URI);
    }

    // Test that priority of messages are handled well.
    public void testMessageOrdering() {
        insertEntryForPackage(TEST_PACKAGES[0], getAllOkStatusValues());
        insertEntryForPackage(TEST_PACKAGES[1], getAllOkStatusValues());

        final ContentValues valuesNoNotificationGoodDataChannel = new ContentValues();
        valuesNoNotificationGoodDataChannel.put(NOTIFICATION_CHANNEL_STATE,
                NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
        valuesNoNotificationGoodDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_OK);

        final ContentValues valuesNoNotificationNoDataChannel = new ContentValues();
        valuesNoNotificationNoDataChannel.put(NOTIFICATION_CHANNEL_STATE,
                NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
        valuesNoNotificationNoDataChannel.put(DATA_CHANNEL_STATE, DATA_CHANNEL_STATE_NO_CONNECTION);

        // Package1 with valuesNoNotificationGoodDataChannel and
        // package2 with  valuesNoNotificationNoDataChannel. Package2 should be above.
        updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationGoodDataChannel);
        updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationNoDataChannel);
        List<StatusMessage> messages = getStatusMessages();
        assertEquals(2, messages.size());
        assertEquals(TEST_PACKAGES[0], messages.get(1).sourcePackage);
        assertEquals(TEST_PACKAGES[1], messages.get(0).sourcePackage);

        // Now reverse the values - ordering should be reversed as well.
        updateEntryForPackage(TEST_PACKAGES[0], valuesNoNotificationNoDataChannel);
        updateEntryForPackage(TEST_PACKAGES[1], valuesNoNotificationGoodDataChannel);
        messages = getStatusMessages();
        assertEquals(2, messages.size());
        assertEquals(TEST_PACKAGES[0], messages.get(0).sourcePackage);
        assertEquals(TEST_PACKAGES[1], messages.get(1).sourcePackage);
    }

    /** Checks that the expected source status message is returned by VoicemailStatusHelper. */
    private void checkExpectedMessage(String sourcePackage, ContentValues values,
            int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
            Uri expectedUri) {
        List<StatusMessage> messages = getStatusMessages();
        assertEquals(1, messages.size());
        checkMessageMatches(messages.get(0), sourcePackage, expectedCallLogMsg,
                expectedCallDetailsMsg, expectedActionMsg, expectedUri);
    }

    private void checkExpectedMessage(String sourcePackage, ContentValues values,
            int expectedCallLogMsg, int expectedCallDetailsMessage, int expectedActionMsg) {
        checkExpectedMessage(sourcePackage, values, expectedCallLogMsg, expectedCallDetailsMessage,
                expectedActionMsg, TEST_VOICEMAIL_URI);
    }

    private void checkMessageMatches(StatusMessage message, String expectedSourcePackage,
            int expectedCallLogMsg, int expectedCallDetailsMsg, int expectedActionMsg,
            Uri expectedUri) {
        assertEquals(expectedSourcePackage, message.sourcePackage);
        assertEquals(expectedCallLogMsg, message.callLogMessageId);
        assertEquals(expectedCallDetailsMsg, message.callDetailsMessageId);
        assertEquals(expectedActionMsg, message.actionMessageId);
        if (expectedUri == null) {
            assertNull(message.actionUri);
        } else {
            assertEquals(expectedUri, message.actionUri);
        }
    }

    private void checkNoMessages(String sourcePackage, ContentValues values) {
        assertEquals(1, updateEntryForPackage(sourcePackage, values));
        List<StatusMessage> messages = getStatusMessages();
        assertEquals(0, messages.size());
    }

    private ContentValues getAllOkStatusValues() {
        ContentValues values = new ContentValues();
        values.put(Status.SETTINGS_URI, TEST_SETTINGS_URI.toString());
        values.put(Status.VOICEMAIL_ACCESS_URI, TEST_VOICEMAIL_URI.toString());
        values.put(Status.CONFIGURATION_STATE, Status.CONFIGURATION_STATE_OK);
        values.put(Status.DATA_CHANNEL_STATE, Status.DATA_CHANNEL_STATE_OK);
        values.put(Status.NOTIFICATION_CHANNEL_STATE, Status.NOTIFICATION_CHANNEL_STATE_OK);
        return values;
    }

    private void insertEntryForPackage(String sourcePackage, ContentValues values) {
        // If insertion fails then try update as the record might already exist.
        if (getContentResolver().insert(Status.buildSourceUri(sourcePackage), values) == null) {
            updateEntryForPackage(sourcePackage, values);
        }
    }

    private void deleteEntryForPackage(String sourcePackage) {
        getContentResolver().delete(Status.buildSourceUri(sourcePackage), null, null);
    }

    private int updateEntryForPackage(String sourcePackage, ContentValues values) {
        return getContentResolver().update(
                Status.buildSourceUri(sourcePackage), values, null, null);
    }

    private List<StatusMessage> getStatusMessages() {
        // Restrict the cursor to only the the test packages to eliminate any side effects if there
        // are other status messages already stored on the device.
        Cursor cursor = getContentResolver().query(Status.CONTENT_URI,
                VoicemailStatusHelperImpl.PROJECTION, getTestPackageSelection(), null, null);
        return mStatusHelper.getStatusMessages(cursor);
    }

    private String getTestPackageSelection() {
        StringBuilder sb = new StringBuilder();
        for (String sourcePackage : TEST_PACKAGES) {
            if (sb.length() > 0) {
                sb.append(" OR ");
            }
            sb.append(String.format("(source_package='%s')", sourcePackage));
        }
        return sb.toString();
    }

    private ContentResolver getContentResolver() {
        return getContext().getContentResolver();
    }
}
