/*
 * 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.android.systemui.statusbar.policy;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.leak.LeakDetector;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Map;
import java.util.function.Consumer;

@RunWith(AndroidTestingRunner.class)
@SmallTest
public class ExtensionControllerImplTest extends SysuiTestCase {

    @Mock
    private ConfigurationController mConfigurationController;

    private PluginManager mPluginManager;
    private TunerService mTunerService;
    private ExtensionController mExtensionController;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mPluginManager = mDependency.injectMockDependency(PluginManager.class);
        mTunerService = mDependency.injectMockDependency(TunerService.class);
        mExtensionController = new ExtensionControllerImpl(
                mContext,
                mock(LeakDetector.class),
                mPluginManager,
                mTunerService,
                mConfigurationController);
    }

    @Test
    public void testPlugin() {
        OverlayPlugin plugin = mock(OverlayPlugin.class);

        Extension ext = mExtensionController.newExtension(OverlayPlugin.class)
                .withPlugin(OverlayPlugin.class)
                .build();
        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
        verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), listener.capture(),
                eq(OverlayPlugin.class));

        listener.getValue().onPluginConnected(plugin, null);
        assertEquals(plugin, ext.get());

        ext.destroy();
        verify(mPluginManager).removePluginListener(any());
    }

    @Test
    public void testTuner() {
        String[] keys = new String[] { "key1", "key2" };
        TunerFactory<Object> factory = new ExtensionController.TunerFactory() {
            @Override
            public String[] keys() {
                return keys;
            }

            @Override
            public Object create(Map settings) {
                return null;
            }
        };
        Extension ext = mExtensionController.newExtension(Object.class)
                .withTunerFactory(factory)
                .build();
        verify(mTunerService).addTunable(any(), eq(keys[0]), eq(keys[1]));

        ext.destroy();
        verify(mTunerService).removeTunable(any());
    }

    @Test
    @RunWithLooper
    public void testUiMode() {
        Object o = new Object();
        Extension ext = mExtensionController.newExtension(Object.class)
                .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> o)
                .build();
        ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass(
                ConfigurationListener.class);
        verify(mConfigurationController).addCallback(captor.capture());

        Configuration c = new Configuration(mContext.getResources().getConfiguration());
        c.uiMode = 0;
        captor.getValue().onConfigChanged(c);
        TestableLooper.get(this).processAllMessages();
        assertNull(ext.get());

        c.uiMode = Configuration.UI_MODE_TYPE_CAR;
        captor.getValue().onConfigChanged(c);
        TestableLooper.get(this).processAllMessages();
        assertEquals(o, ext.get());

        ext.destroy();
        verify(mConfigurationController).removeCallback(eq(captor.getValue()));
    }

    @Test
    public void testDefault() {
        Object o = new Object();
        Extension ext = mExtensionController.newExtension(Object.class)
                .withDefault(() -> o)
                .build();
        assertEquals(o, ext.get());
    }

    @Test
    @RunWithLooper
    public void testSortOrder() {
        Object def = new Object();
        Object uiMode = new Object();
        Object tuner = new Object();
        Plugin plugin = mock(Plugin.class);
        TunerFactory<Object> factory = mock(TunerFactory.class);
        Extension ext = mExtensionController.newExtension(Object.class)
                .withDefault(() -> def)
                .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> uiMode)
                .withTunerFactory(factory)
                .withPlugin(Object.class, "some_action")
                .build();

        // Test default first.
        assertEquals(def, ext.get());

        // Enable a UI mode and check that.
        ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass(
                ConfigurationListener.class);
        verify(mConfigurationController).addCallback(captor.capture());
        Configuration c = new Configuration(mContext.getResources().getConfiguration());
        c.uiMode |= Configuration.UI_MODE_TYPE_CAR;
        captor.getValue().onConfigChanged(c);
        TestableLooper.get(this).processAllMessages();
        assertEquals(uiMode, ext.get());

        // Turn on tuner item and check that.
        when(factory.create(any())).thenReturn(tuner);
        ArgumentCaptor<Tunable> tunable = ArgumentCaptor.forClass(Tunable.class);
        verify(mTunerService).addTunable(tunable.capture(), any());
        tunable.getValue().onTuningChanged(null, null);
        assertEquals(tuner, ext.get());

        // Lastly, check a plugin.
        ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class);
        verify(mPluginManager).addPluginListener(any(), listener.capture(), any());
        listener.getValue().onPluginConnected(plugin, null);
        assertEquals(plugin, ext.get());
    }

    @Test
    public void testCallback() {
        Consumer<Object> callback = mock(Consumer.class);
        final Object o = new Object();
        mExtensionController.newExtension(Object.class)
                .withDefault(() -> o)
                .withCallback(callback)
                .build();
        verify(callback).accept(eq(o));
    }

}
