/*
 * Copyright (C) 2024 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.launcher3.celllayout

import android.content.Context
import android.graphics.Point
import android.util.Log
import android.view.View
import androidx.core.view.get
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.CellLayout
import com.android.launcher3.celllayout.board.CellLayoutBoard
import com.android.launcher3.celllayout.board.IconPoint
import com.android.launcher3.celllayout.board.PermutedBoardComparator
import com.android.launcher3.celllayout.board.WidgetRect
import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.views.DoubleShadowBubbleTextView
import java.util.Random
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

private class HotseatReorderTestCase(
    val startBoard: CellLayoutBoard,
    val endBoard: CellLayoutBoard
) {
    override fun toString(): String {
        return "$startBoard#endBoard:\n$endBoard"
    }
}

@SmallTest
@RunWith(AndroidJUnit4::class)
class HotseatReorderUnitTest {

    private val applicationContext: Context =
        ActivityContextWrapper(ApplicationProvider.getApplicationContext())

    @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()

    /**
     * This test generates random CellLayout configurations and then try to reorder it and makes
     * sure the result is a valid board meaning it didn't remove any widget or icon.
     */
    @Test
    fun generateValidTests() {
        val generator = Random(Companion.SEED.toLong())
        for (i in 0 until Companion.TOTAL_OF_CASES_GENERATED) {
            // Using a new seed so that we can replicate the same test cases.
            val seed = generator.nextInt()
            Log.d(Companion.TAG, "Seed = $seed")

            val testCase: HotseatReorderTestCase =
                generateRandomTestCase(RandomBoardGenerator(Random(seed.toLong())))
            Log.d(Companion.TAG, "testCase = $testCase")

            Assert.assertTrue(
                "invalid case $i",
                PermutedBoardComparator().compare(testCase.startBoard, testCase.endBoard) == 0
            )
        }
    }

    private fun addViewInCellLayout(
        cellLayout: CellLayout,
        cellX: Int,
        cellY: Int,
        spanX: Int,
        spanY: Int,
        isWidget: Boolean
    ) {
        val cell =
            if (isWidget) View(applicationContext)
            else DoubleShadowBubbleTextView(applicationContext)
        cell.layoutParams = CellLayoutLayoutParams(cellX, cellY, spanX, spanY)
        cellLayout.addViewToCellLayout(
            cell,
            -1,
            cell.id,
            cell.layoutParams as CellLayoutLayoutParams,
            true
        )
    }

    private fun solve(board: CellLayoutBoard): CellLayout {
        val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false)
        // The views have to be sorted or the result can vary
        board.icons
            .map(IconPoint::coord)
            .sortedWith(
                Comparator.comparing { p: Any -> (p as Point).x }
                    .thenComparing { p: Any -> (p as Point).y }
            )
            .forEach { p ->
                addViewInCellLayout(
                    cellLayout = cl,
                    cellX = p.x,
                    cellY = p.y,
                    spanX = 1,
                    spanY = 1,
                    isWidget = false
                )
            }
        board.widgets
            .sortedWith(Comparator.comparing(WidgetRect::cellX).thenComparing(WidgetRect::cellY))
            .forEach { widget ->
                addViewInCellLayout(
                    cl,
                    widget.cellX,
                    widget.cellY,
                    widget.spanX,
                    widget.spanY,
                    isWidget = true
                )
            }
        if (cl.makeSpaceForHotseatMigration(true)) {
            commitTempPosition(cl)
        }
        return cl
    }

    private fun commitTempPosition(cellLayout: CellLayout) {
        val count = cellLayout.shortcutsAndWidgets.childCount
        for (i in 0 until count) {
            val params = cellLayout.shortcutsAndWidgets[i].layoutParams as CellLayoutLayoutParams
            params.cellX = params.tmpCellX
            params.cellY = params.tmpCellY
        }
    }

    private fun boardFromCellLayout(cellLayout: CellLayout): CellLayoutBoard {
        val views = mutableListOf<View>()
        for (i in 0 until cellLayout.shortcutsAndWidgets.childCount) {
            views.add(cellLayout.shortcutsAndWidgets.getChildAt(i))
        }
        return CellLayoutTestUtils.viewsToBoard(views, cellLayout.countX, cellLayout.countY)
    }

    private fun generateRandomTestCase(
        boardGenerator: RandomBoardGenerator
    ): HotseatReorderTestCase {
        val width: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
        val height: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
        val targetWidth: Int = boardGenerator.getRandom(1, width - 2)
        val targetHeight: Int = boardGenerator.getRandom(1, height - 2)
        val board: CellLayoutBoard =
            boardGenerator.generateBoard(width, height, targetWidth * targetHeight)
        val finishBoard: CellLayoutBoard = boardFromCellLayout(solve(board))
        return HotseatReorderTestCase(board, finishBoard)
    }

    companion object {
        private const val MAX_BOARD_SIZE = 13

        /**
         * There is nothing special about this numbers, the random seed is just to be able to
         * reproduce the test cases and the height and width is a random number similar to what
         * users expect on their devices
         */
        private const val SEED = -194162315
        private const val TOTAL_OF_CASES_GENERATED = 300
        private const val TAG = "HotseatReorderUnitTest"
    }
}
