/*
 * Copyright (C) 2016 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.internal.util;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

import android.app.AlarmManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.test.suitebuilder.annotation.SmallTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

/**
 * Unit tests for {@link com.android.internal.util.WakeupMessage}.
 */
@SmallTest
public class WakeupMessageTest {
    private static final String TEST_CMD_NAME = "TEST cmd Name";
    private static final int TEST_CMD = 18;
    private static final int TEST_ARG1 = 33;
    private static final int TEST_ARG2 = 182;
    private static final Object TEST_OBJ = "hello";

    @Mock Context mContext;
    @Mock AlarmManager mAlarmManager;
    WakeupMessage mMessage;
    // Make a spy so that we can verify calls to it
    @Spy MessageCapturingHandler mHandler = new MessageCapturingHandler();

    ArgumentCaptor<AlarmManager.OnAlarmListener> mListenerCaptor =
            ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);

    /**
     * A Handler that will capture the most recent message sent to it.
     *
     * This handler is setup on the main Looper
     */
    public static class MessageCapturingHandler extends Handler {
        private Message mLastMessage;

        public MessageCapturingHandler() {
            super(Looper.getMainLooper(), /* Nothing is actually dispatched on this Looper */
                    null, false);
        }

        @Override
        public void handleMessage(Message m) {
            // need to copy since it will be recycled after this method returns
            mLastMessage = Message.obtain(m);
        }

        public Message getLastMessage() {
            return mLastMessage;
        }
    }

    /**
     * Sets up the test.
     */
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
        // capture the listener for each AlarmManager.setExact call
        doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
                mListenerCaptor.capture(), any(Handler.class));

        mMessage = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
                TEST_ARG2, TEST_OBJ);
    }

    /**
     * Ensure the test is cleaned up and ready for the next test.
     */
    @After
    public void cleanup() {
        validateMockitoUsage();
    }

    private void scheduleAndVerifyAlarm(long when) {
        mMessage.schedule(when);
        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
                eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
    }

    private void verifyMessageDispatchedOnce() {
        verify(mHandler, times(1)).handleMessage(any(Message.class));
        assertEquals("what", TEST_CMD, mHandler.getLastMessage().what);
        assertEquals("arg1", TEST_ARG1, mHandler.getLastMessage().arg1);
        assertEquals("arg2", TEST_ARG2, mHandler.getLastMessage().arg2);
        assertEquals("obj", TEST_OBJ, mHandler.getLastMessage().obj);
    }

    /**
     * Schedule and deliver a single message
     */
    @Test
    public void scheduleAndDeliverMessage() {
        final long when = 1001;
        scheduleAndVerifyAlarm(when);
        verify(mHandler, never()).handleMessage(any(Message.class));
        mListenerCaptor.getValue().onAlarm();
        verifyMessageDispatchedOnce();
    }

    /**
     * Check that the message is not delivered if cancel is called it after its alarm fires but
     * before onAlarm is called.
     *
     * This ensures that if cancel is called on the handler thread, any previously-scheduled message
     * is guaranteed not to be delivered.
     */
    @Test
    public void scheduleAndCancelMessage() {
        final long when = 1010;
        scheduleAndVerifyAlarm(when);
        mMessage.cancel();
        mListenerCaptor.getValue().onAlarm();
        verify(mHandler, never()).handleMessage(any(Message.class));
    }

    /**
     * Verify nothing happens when cancel is called without a schedule
     */
    @Test
    public void cancelWithoutSchedule() {
        mMessage.cancel();
    }

    /**
     * Verify that the message is silently rescheduled if schedule is called twice without the
     * message being dispatched first.
     */
    @Test
    public void scheduleTwiceWithoutMessageDispatched() {
        final long when1 = 1011;
        final long when2 = 1012;
        scheduleAndVerifyAlarm(when1);
        scheduleAndVerifyAlarm(when2);
        mListenerCaptor.getValue().onAlarm();
        verifyMessageDispatchedOnce();
    }

    /**
     * Verify that a Runnable is scheduled and dispatched.
     */
    @Test
    public void scheduleRunnable() {
        final long when = 1011;
        final Runnable runnable = mock(Runnable.class);
        WakeupMessage dut = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, runnable);
        dut.schedule(when);
        verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
                eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
        mListenerCaptor.getValue().onAlarm();
        verify(runnable, times(1)).run();
    }

}
