/*
 * Copyright (C) 2020 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.controls.management

import android.content.ComponentName
import android.testing.AndroidTestingRunner
import androidx.recyclerview.widget.RecyclerView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlInterface
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlInfo
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations

@SmallTest
@RunWith(AndroidTestingRunner::class)
class FavoritesModelTest : SysuiTestCase() {

    companion object {
        private val TEST_COMPONENT = ComponentName.unflattenFromString("test_pkg/.test_cls")!!
        private val ID_PREFIX = "control"
        private val INITIAL_FAVORITES = (0..5).map {
            ControlInfo("$ID_PREFIX$it", "title$it", "subtitle$it", it)
        }
    }

    @Mock
    private lateinit var callback: FavoritesModel.FavoritesModelCallback
    @Mock
    private lateinit var adapter: RecyclerView.Adapter<*>
    @Mock
    private lateinit var customIconCache: CustomIconCache
    private lateinit var model: FavoritesModel
    private lateinit var dividerWrapper: DividerWrapper

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)

        model = FavoritesModel(customIconCache, TEST_COMPONENT, INITIAL_FAVORITES, callback)
        model.attachAdapter(adapter)
        dividerWrapper = model.elements.first { it is DividerWrapper } as DividerWrapper
    }

    @After
    fun testListConsistency() {
        assertEquals(INITIAL_FAVORITES.size + 1, model.elements.toSet().size)
        val dividerIndex = getDividerPosition()
        model.elements.forEachIndexed { index, element ->
            if (index == dividerIndex) {
                assertEquals(dividerWrapper, element)
            } else {
                element as ControlInterface
                assertEquals(index < dividerIndex, element.favorite)
            }
        }
        assertEquals(model.favorites, model.elements.take(dividerIndex).map {
            (it as ControlInfoWrapper).controlInfo
        })
    }

    @Test
    fun testInitialElements() {
        val expected = INITIAL_FAVORITES.map {
            ControlInfoWrapper(TEST_COMPONENT, it, true, customIconCache::retrieve)
        } + DividerWrapper()
        assertEquals(expected, model.elements)
    }

    @Test
    fun testFavorites() {
        assertEquals(INITIAL_FAVORITES, model.favorites)
    }

    @Test
    fun testRemoveFavorite_notInFavorites() {
        val removed = 4
        val id = "$ID_PREFIX$removed"

        model.changeFavoriteStatus(id, false)

        assertTrue(model.favorites.none { it.controlId == id })

        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveFavorite_endOfElements() {
        val removed = 4
        val id = "$ID_PREFIX$removed"
        model.changeFavoriteStatus(id, false)

        assertEquals(ControlInfoWrapper(
                TEST_COMPONENT, INITIAL_FAVORITES[4], false), model.elements.last())
        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveFavorite_adapterNotified() {
        val removed = 4
        val id = "$ID_PREFIX$removed"
        model.changeFavoriteStatus(id, false)

        val lastPos = model.elements.size - 1
        verify(adapter).notifyItemChanged(eq(lastPos), any(Any::class.java))
        verify(adapter).notifyItemMoved(removed, lastPos)

        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveFavorite_dividerMovedBack() {
        val oldDividerPosition = getDividerPosition()
        val removed = 4
        val id = "$ID_PREFIX$removed"
        model.changeFavoriteStatus(id, false)

        assertEquals(oldDividerPosition - 1, getDividerPosition())

        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveFavorite_ShowDivider() {
        val oldDividerPosition = getDividerPosition()
        val removed = 4
        val id = "$ID_PREFIX$removed"
        model.changeFavoriteStatus(id, false)

        assertTrue(dividerWrapper.showDivider)
        verify(adapter).notifyItemChanged(oldDividerPosition)

        verify(callback).onFirstChange()
    }

    @Test
    fun testDoubleRemove_onlyOnce() {
        val removed = 4
        val id = "$ID_PREFIX$removed"
        model.changeFavoriteStatus(id, false)
        model.changeFavoriteStatus(id, false)

        verify(adapter /* only once */).notifyItemChanged(anyInt(), any(Any::class.java))
        verify(adapter /* only once */).notifyItemMoved(anyInt(), anyInt())
        verify(adapter /* only once (divider) */).notifyItemChanged(anyInt())

        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveTwo_InSameOrder() {
        val removedFirst = 3
        val removedSecond = 0
        model.changeFavoriteStatus("$ID_PREFIX$removedFirst", false)
        model.changeFavoriteStatus("$ID_PREFIX$removedSecond", false)

        assertEquals(listOf(
                ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedFirst], false),
                ControlInfoWrapper(TEST_COMPONENT, INITIAL_FAVORITES[removedSecond], false)
        ), model.elements.takeLast(2))

        verify(callback).onFirstChange()
    }

    @Test
    fun testRemoveAll_showNone() {
        INITIAL_FAVORITES.forEach {
            model.changeFavoriteStatus(it.controlId, false)
        }
        assertEquals(dividerWrapper, model.elements.first())
        assertTrue(dividerWrapper.showNone)
        verify(adapter, times(2)).notifyItemChanged(anyInt()) // divider
        verify(callback).onNoneChanged(true)

        verify(callback).onFirstChange()
    }

    @Test
    fun testAddFavorite_movedToEnd() {
        val added = 2
        val id = "$ID_PREFIX$added"
        model.changeFavoriteStatus(id, false)
        model.changeFavoriteStatus(id, true)

        assertEquals(id, model.favorites.last().controlId)

        verify(callback).onFirstChange()
    }

    @Test
    fun testAddFavorite_onlyOnce() {
        val added = 2
        val id = "$ID_PREFIX$added"
        model.changeFavoriteStatus(id, false)
        model.changeFavoriteStatus(id, true)
        model.changeFavoriteStatus(id, true)

        // Once for remove and once for add
        verify(adapter, times(2)).notifyItemChanged(anyInt(), any(Any::class.java))
        verify(adapter, times(2)).notifyItemMoved(anyInt(), anyInt())

        verify(callback).onFirstChange()
    }

    @Test
    fun testAddFavorite_notRemoved() {
        val added = 2
        val id = "$ID_PREFIX$added"
        model.changeFavoriteStatus(id, true)

        verifyNoMoreInteractions(adapter)

        verify(callback, never()).onFirstChange()
    }

    @Test
    fun testAddOnlyRemovedFavorite_dividerStopsShowing() {
        val added = 2
        val id = "$ID_PREFIX$added"
        model.changeFavoriteStatus(id, false)
        model.changeFavoriteStatus(id, true)

        assertFalse(dividerWrapper.showDivider)
        val inOrder = inOrder(adapter)
        inOrder.verify(adapter).notifyItemChanged(model.elements.size - 1)
        inOrder.verify(adapter).notifyItemChanged(model.elements.size - 2)

        verify(callback).onFirstChange()
    }

    @Test
    fun testAddFirstFavorite_dividerNotShowsNone() {
        INITIAL_FAVORITES.forEach {
            model.changeFavoriteStatus(it.controlId, false)
        }

        verify(callback).onNoneChanged(true)

        model.changeFavoriteStatus("${ID_PREFIX}3", true)
        assertEquals(1, getDividerPosition())

        verify(callback).onNoneChanged(false)

        verify(callback).onFirstChange()
    }

    @Test
    fun testMoveBetweenFavorites() {
        val from = 2
        val to = 4

        model.onMoveItem(from, to)
        assertEquals(
                listOf(0, 1, 3, 4, 2, 5).map { "$ID_PREFIX$it" },
                model.favorites.map(ControlInfo::controlId)
        )
        verify(adapter).notifyItemMoved(from, to)
        verify(adapter, never()).notifyItemChanged(anyInt(), any(Any::class.java))

        verify(callback).onFirstChange()
    }

    @Test
    fun testCacheCalledWhenGettingCustomIcon() {
        val wrapper = model.elements[0] as ControlInfoWrapper
        wrapper.customIcon

        verify(customIconCache).retrieve(TEST_COMPONENT, wrapper.controlId)
    }

    private fun getDividerPosition(): Int = model.elements.indexOf(dividerWrapper)
}