/*
 * Copyright (C) 2022 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
 *
 *     https://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.example.platform.ui.haptics.basic

import android.app.Application
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.view.HapticFeedbackConstants
import android.view.View
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.example.platform.ui.haptics.R

/**
 * The state object the home screen needs to render the haptic feedback categories and buttons.
 */
data class HapticsBasicUiState(val hapticCategories: List<HapticCategory> = emptyList())

/**
 * Represents a category of haptic effects and constants on the home screen.
 */
data class HapticCategory(
    val label: String = "", // The label displayed before the rows of buttons.
    val categoryType: HapticCategoryType,
    val buttons: List<HapticButton> = emptyList()
)

/**
 * A single button on the home page, and the corresponding information we need to react to a click.
 */
data class HapticButton(
    val label: String = "",
    // On some devices, effect won't work.
    val worksOnUserDevice: Boolean,
    // The corresponding constant for the various vibration effects available to developers.
    val hapticId: Int = -1
)

/**
 * The category type, necessary since each haptic feedback effect calls the APIs differently.
 */
enum class HapticCategoryType {
    PREDEFINED_EFFECTS, HAPTIC_FEEDBACK_CONSTANTS, COMPOSITION_PRIMITIVES
}

/**
 * ViewModel that handles the business logic of the home screen.
 */
class HapticsBasicViewModel(
    private val vibrator: Vibrator,
    val hapticsBasicUiState: HapticsBasicUiState,
) : ViewModel() {

    /**
     * On each button click on the home page, the composable calls this event, passing along all
     * the necessary information to call the correct haptics API.
     */
    fun onButtonClicked(view: View, category: HapticCategoryType, effect: Int) {
        when (category) {
            HapticCategoryType.PREDEFINED_EFFECTS -> {
                if (isAndroidQOrLater) {
                    // https://developer.android.com/reference/android/os/VibrationEffect#createPredefined(int)
                    vibrator.vibrate(VibrationEffect.createPredefined(effect))
                }
            }
            HapticCategoryType.HAPTIC_FEEDBACK_CONSTANTS -> {
                // https://developer.android.com/reference/android/view/View#performHapticFeedback(int)
                view.performHapticFeedback(effect)
            }
            HapticCategoryType.COMPOSITION_PRIMITIVES -> {
                if (isAndroidROrLater) {
                    // https://developer.android.com/reference/android/os/VibrationEffect.Composition
                    vibrator.vibrate(
                        VibrationEffect.startComposition()
                            .addPrimitive(effect)
                            .compose()
                    )
                }
            }
        }
    }

    /**
     * Companion object containing factory for HomeViewModel that can provide vibrator dependency
     * and construct HomeViewModelState using Application context.
     */
    companion object {
        val isAndroidQOrLater get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q // API Level 29.
        val isAndroidROrLater get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R // API Level 30.
        val isAndroidSOrLater get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S // API Level 31.

        fun provideFactory(
            application: Application
        ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                val vibrator = ContextCompat.getSystemService(application, Vibrator::class.java)!!

                // This state object is responsible for providing all the information necessary
                // for the UI to build the home page of buttons, separated into categories.
                val viewModelState = HapticsBasicUiState(
                    hapticCategories = listOf(
                        // List of predefined effects.
                        HapticCategory(
                            application.getString(R.string.vibrations_screen_predefined_effects),
                            categoryType = HapticCategoryType.PREDEFINED_EFFECTS,
                            buttons = listOf(
                                HapticButton(
                                    label = application.getString(R.string.vibrations_screen_tick),
                                    worksOnUserDevice = isAndroidQOrLater,
                                    hapticId = VibrationEffect.EFFECT_TICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_click),
                                    worksOnUserDevice = isAndroidQOrLater,
                                    VibrationEffect.EFFECT_CLICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_heavy_click),
                                    worksOnUserDevice = isAndroidQOrLater,
                                    VibrationEffect.EFFECT_HEAVY_CLICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_double_click),
                                    worksOnUserDevice = isAndroidQOrLater,
                                    VibrationEffect.EFFECT_DOUBLE_CLICK
                                ),
                            )
                        ),
                        // List of haptic feedback constants.
                        HapticCategory(
                            application.getString(R.string.vibrations_screen_haptic_feedback_constants),
                            categoryType = HapticCategoryType.HAPTIC_FEEDBACK_CONSTANTS,
                            buttons = listOf(
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_confirm),
                                    isAndroidROrLater,
                                    HapticFeedbackConstants.CONFIRM
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_reject),
                                    isAndroidROrLater,
                                    HapticFeedbackConstants.REJECT
                                ),
                            )
                        ),
                        // List of composition primitives.
                        HapticCategory(
                            application.getString(R.string.vibrations_screen_composition_primitives),
                            categoryType = HapticCategoryType.COMPOSITION_PRIMITIVES,
                            buttons = listOf(
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_low_tick),
                                    isAndroidSOrLater && isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_LOW_TICK),
                                    VibrationEffect.Composition.PRIMITIVE_LOW_TICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_tick),
                                    isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_TICK),
                                    VibrationEffect.Composition.PRIMITIVE_TICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_click),
                                    isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_CLICK),
                                    VibrationEffect.Composition.PRIMITIVE_CLICK
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_slow_rise),
                                    isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_SLOW_RISE),
                                    VibrationEffect.Composition.PRIMITIVE_SLOW_RISE
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_quick_rise),
                                    isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_QUICK_RISE),
                                    VibrationEffect.Composition.PRIMITIVE_QUICK_RISE
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_quick_fall),
                                    isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_QUICK_FALL),
                                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_spin),
                                    isAndroidSOrLater && isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_SPIN),
                                    VibrationEffect.Composition.PRIMITIVE_SPIN
                                ),
                                HapticButton(
                                    application.getString(R.string.vibrations_screen_thud),
                                    isAndroidSOrLater && isPrimitiveSupported(vibrator, VibrationEffect.Composition.PRIMITIVE_THUD),
                                    VibrationEffect.Composition.PRIMITIVE_THUD
                                ),

                            )
                        ),
                    )
                )
                return HapticsBasicViewModel(
                    vibrator = vibrator, hapticsBasicUiState = viewModelState,
                ) as T
            }
        }

        /**
         * Query whether the vibrator supports all of the given primitives.
         * If a primitive is not supported by the device, then no vibration will occur if it is played.
         */
        private fun isPrimitiveSupported(vibrator: Vibrator, primitiveId: Int): Boolean {
            if (isAndroidROrLater) return vibrator.areAllPrimitivesSupported(primitiveId)
            return false
        }
    }
}
