package com.simplemobiletools.launcher.activities

import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.app.Activity
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.provider.Telephony
import android.telecom.TelecomManager
import android.view.*
import android.view.animation.DecelerateInterpolator
import android.widget.PopupMenu
import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.isVisible
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isPiePlus
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.launcher.BuildConfig
import com.simplemobiletools.launcher.R
import com.simplemobiletools.launcher.dialogs.RenameItemDialog
import com.simplemobiletools.launcher.extensions.*
import com.simplemobiletools.launcher.fragments.AllAppsFragment
import com.simplemobiletools.launcher.fragments.MyFragment
import com.simplemobiletools.launcher.fragments.WidgetsFragment
import com.simplemobiletools.launcher.helpers.*
import com.simplemobiletools.launcher.interfaces.FlingListener
import com.simplemobiletools.launcher.models.AppLauncher
import com.simplemobiletools.launcher.models.HiddenIcon
import com.simplemobiletools.launcher.models.HomeScreenGridItem
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.view.*
import kotlinx.android.synthetic.main.all_apps_fragment.view.*
import kotlinx.android.synthetic.main.widgets_fragment.view.*

class MainActivity : SimpleActivity(), FlingListener {
    private val ANIMATION_DURATION = 150L

    private var mTouchDownX = -1
    private var mTouchDownY = -1
    private var mAllAppsFragmentY = 0
    private var mWidgetsFragmentY = 0
    private var mScreenHeight = 0
    private var mMoveGestureThreshold = 0
    private var mIgnoreUpEvent = false
    private var mIgnoreMoveEvents = false
    private var mLongPressedIcon: HomeScreenGridItem? = null
    private var mOpenPopupMenu: PopupMenu? = null
    private var mCachedLaunchers = ArrayList<AppLauncher>()
    private var mLastTouchCoords = Pair(-1f, -1f)
    private var mActionOnCanBindWidget: ((granted: Boolean) -> Unit)? = null
    private var mActionOnWidgetConfiguredWidget: ((granted: Boolean) -> Unit)? = null
    private var mActionOnAddShortcut: ((label: String, icon: Bitmap?, intent: String) -> Unit)? = null

    private lateinit var mDetector: GestureDetectorCompat

    companion object {
        private var mLastUpEvent = 0L
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        useDynamicTheme = false

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        appLaunched(BuildConfig.APPLICATION_ID)

        mDetector = GestureDetectorCompat(this, MyGestureListener(this))

        if (isRPlus()) {
            window.setDecorFitsSystemWindows(false)
        }

        mScreenHeight = realScreenSize.y
        mAllAppsFragmentY = mScreenHeight
        mWidgetsFragmentY = mScreenHeight
        mMoveGestureThreshold = resources.getDimension(R.dimen.move_gesture_threshold).toInt()

        arrayOf(all_apps_fragment as MyFragment, widgets_fragment as MyFragment).forEach { fragment ->
            fragment.setupFragment(this)
            fragment.y = mScreenHeight.toFloat()
            fragment.beVisible()
        }

        if (intent.action == LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT) {
            val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
            val item = launcherApps.getPinItemRequest(intent)
            if (item.shortcutInfo == null) {
                return
            }

            ensureBackgroundThread {
                val shortcutId = item.shortcutInfo?.id!!
                val label = item.shortcutInfo?.shortLabel?.toString() ?: item.shortcutInfo?.longLabel?.toString() ?: ""
                val icon = launcherApps.getShortcutIconDrawable(item.shortcutInfo!!, resources.displayMetrics.densityDpi)
                val rect = findFirstEmptyCell() ?: return@ensureBackgroundThread
                val gridItem = HomeScreenGridItem(
                    null,
                    rect.left,
                    rect.top,
                    rect.right,
                    rect.bottom,
                    item.shortcutInfo!!.`package`,
                    "",
                    label,
                    ITEM_TYPE_SHORTCUT,
                    "",
                    -1,
                    "",
                    shortcutId,
                    icon.toBitmap(),
                    icon
                )

                // delay showing the shortcut both to let the user see adding it in realtime and hackily avoid concurrent modification exception at HomeScreenGrid
                Thread.sleep(2000)

                try {
                    item.accept()
                    home_screen_grid.storeAndShowGridItem(gridItem)
                } catch (ignored: IllegalStateException) {
                }
            }
        }
    }

    private fun findFirstEmptyCell(): Rect? {
        val gridItems = homeScreenGridItemsDB.getAllItems() as ArrayList<HomeScreenGridItem>
        val occupiedCells = ArrayList<Pair<Int, Int>>()
        gridItems.forEach { item ->
            for (xCell in item.left..item.right) {
                for (yCell in item.top..item.bottom) {
                    occupiedCells.add(Pair(xCell, yCell))
                }
            }
        }

        for (checkedYCell in 0 until COLUMN_COUNT) {
            for (checkedXCell in 0 until ROW_COUNT - 1) {
                val wantedCell = Pair(checkedXCell, checkedYCell)
                if (!occupiedCells.contains(wantedCell)) {
                    return Rect(wantedCell.first, wantedCell.second, wantedCell.first, wantedCell.second)
                }
            }
        }

        return null
    }

    override fun onStart() {
        super.onStart()
        home_screen_grid.appWidgetHost.startListening()
    }

    override fun onResume() {
        super.onResume()
        updateStatusbarColor(Color.TRANSPARENT)

        main_holder.onGlobalLayout {
            if (isPiePlus()) {
                val addTopPadding = main_holder.rootWindowInsets?.displayCutout != null
                (all_apps_fragment as AllAppsFragment).setupViews(addTopPadding)
                (widgets_fragment as WidgetsFragment).setupViews(addTopPadding)
            }
        }

        ensureBackgroundThread {
            if (mCachedLaunchers.isEmpty()) {
                val hiddenIcons = hiddenIconsDB.getHiddenIcons().map {
                    it.getIconIdentifier()
                }

                mCachedLaunchers = launchersDB.getAppLaunchers().filter {
                    val showIcon = !hiddenIcons.contains(it.getLauncherIdentifier())
                    if (!showIcon) {
                        try {
                            launchersDB.deleteById(it.id!!)
                        } catch (ignored: Exception) {
                        }
                    }
                    showIcon
                }.toMutableList() as ArrayList<AppLauncher>

                (all_apps_fragment as AllAppsFragment).gotLaunchers(mCachedLaunchers)
            }

            refetchLaunchers()
        }

        // avoid showing fully colored navigation bars
        if (window.navigationBarColor != resources.getColor(R.color.semitransparent_navigation)) {
            window.navigationBarColor = Color.TRANSPARENT
        }
    }

    override fun onStop() {
        super.onStop()
        home_screen_grid?.appWidgetHost?.stopListening()
    }

    override fun onBackPressed() {
        if (isAllAppsFragmentExpanded()) {
            hideFragment(all_apps_fragment)
        } else if (isWidgetsFragmentExpanded()) {
            hideFragment(widgets_fragment)
        } else if (home_screen_grid.resize_frame.isVisible) {
            home_screen_grid.hideResizeLines()
        } else {
            // this is a home launcher app, avoid glitching by pressing Back
            //super.onBackPressed()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
        super.onActivityResult(requestCode, resultCode, resultData)

        when (requestCode) {
            UNINSTALL_APP_REQUEST_CODE -> {
                ensureBackgroundThread {
                    refetchLaunchers()
                }
            }
            REQUEST_ALLOW_BINDING_WIDGET -> mActionOnCanBindWidget?.invoke(resultCode == Activity.RESULT_OK)
            REQUEST_CONFIGURE_WIDGET -> mActionOnWidgetConfiguredWidget?.invoke(resultCode == Activity.RESULT_OK)
            REQUEST_CREATE_SHORTCUT -> {
                if (resultCode == Activity.RESULT_OK && resultData != null) {
                    val launchIntent = resultData.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT) as? Intent
                    val label = resultData.getStringExtra(Intent.EXTRA_SHORTCUT_NAME) ?: ""
                    val icon = resultData.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON) as? Bitmap
                    mActionOnAddShortcut?.invoke(label, icon, launchIntent?.toUri(0).toString())
                }
            }
        }
    }

    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        (all_apps_fragment as? AllAppsFragment)?.onConfigurationChanged()
        (widgets_fragment as? WidgetsFragment)?.onConfigurationChanged()
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event == null) {
            return false
        }

        if (mLongPressedIcon != null && event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
            mLastUpEvent = System.currentTimeMillis()
        }

        try {
            mDetector.onTouchEvent(event)
        } catch (ignored: Exception) {
        }

        when (event.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                mTouchDownX = event.x.toInt()
                mTouchDownY = event.y.toInt()
                mAllAppsFragmentY = all_apps_fragment.y.toInt()
                mWidgetsFragmentY = widgets_fragment.y.toInt()
                mIgnoreUpEvent = false
            }

            MotionEvent.ACTION_MOVE -> {
                // if the initial gesture was handled by some other view, fix the Down values
                val hasFingerMoved = if (mTouchDownX == -1 || mTouchDownY == -1) {
                    mTouchDownX = event.x.toInt()
                    mTouchDownY = event.y.toInt()
                    false
                } else {
                    hasFingerMoved(event)
                }

                if (mLongPressedIcon != null && mOpenPopupMenu != null && hasFingerMoved) {
                    mOpenPopupMenu?.dismiss()
                    mOpenPopupMenu = null
                    home_screen_grid.itemDraggingStarted(mLongPressedIcon!!)
                    hideFragment(all_apps_fragment)
                }

                if (mLongPressedIcon != null && hasFingerMoved) {
                    home_screen_grid.draggedItemMoved(event.x.toInt(), event.y.toInt())
                }

                if (mTouchDownY != -1 && !mIgnoreMoveEvents) {
                    val diffY = mTouchDownY - event.y

                    if (isWidgetsFragmentExpanded()) {
                        val newY = mWidgetsFragmentY - diffY
                        widgets_fragment.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat())
                    } else if (mLongPressedIcon == null) {
                        val newY = mAllAppsFragmentY - diffY
                        all_apps_fragment.y = Math.min(Math.max(0f, newY), mScreenHeight.toFloat())
                    }
                }

                mLastTouchCoords = Pair(event.x, event.y)
            }

            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                mTouchDownX = -1
                mTouchDownY = -1
                mIgnoreMoveEvents = false
                mLongPressedIcon = null
                mLastTouchCoords = Pair(-1f, -1f)
                resetFragmentTouches()
                home_screen_grid.itemDraggingStopped()

                if (!mIgnoreUpEvent) {
                    if (all_apps_fragment.y < mScreenHeight * 0.5) {
                        showFragment(all_apps_fragment)
                    } else if (isAllAppsFragmentExpanded()) {
                        hideFragment(all_apps_fragment)
                    }

                    if (widgets_fragment.y < mScreenHeight * 0.5) {
                        showFragment(widgets_fragment)
                    } else if (isWidgetsFragmentExpanded()) {
                        hideFragment(widgets_fragment)
                    }
                }
            }
        }

        return true
    }

    // some devices ACTION_MOVE keeps triggering for the whole long press duration, but we are interested in real moves only, when coords change
    private fun hasFingerMoved(event: MotionEvent) = mTouchDownX != -1 && mTouchDownY != -1 &&
        (Math.abs(mTouchDownX - event.x) > mMoveGestureThreshold) || (Math.abs(mTouchDownY - event.y) > mMoveGestureThreshold)

    private fun refetchLaunchers() {
        val launchers = getAllAppLaunchers()
        (all_apps_fragment as AllAppsFragment).gotLaunchers(launchers)
        (widgets_fragment as WidgetsFragment).getAppWidgets()

        var hasDeletedAnything = false
        mCachedLaunchers.map { it.packageName }.forEach { packageName ->
            if (!launchers.map { it.packageName }.contains(packageName)) {
                hasDeletedAnything = true
                launchersDB.deleteApp(packageName)
                homeScreenGridItemsDB.deleteByPackageName(packageName)
            }
        }

        if (hasDeletedAnything) {
            home_screen_grid.fetchGridItems()
        }

        mCachedLaunchers = launchers

        if (!config.wasHomeScreenInit) {
            ensureBackgroundThread {
                getDefaultAppPackages(launchers)
                config.wasHomeScreenInit = true
                home_screen_grid.fetchGridItems()
            }
        }
    }

    fun isAllAppsFragmentExpanded() = all_apps_fragment.y != mScreenHeight.toFloat()

    private fun isWidgetsFragmentExpanded() = widgets_fragment.y != mScreenHeight.toFloat()

    fun startHandlingTouches(touchDownY: Int) {
        mLongPressedIcon = null
        mTouchDownY = touchDownY
        mAllAppsFragmentY = all_apps_fragment.y.toInt()
        mWidgetsFragmentY = widgets_fragment.y.toInt()
        mIgnoreUpEvent = false
    }

    private fun showFragment(fragment: View) {
        ObjectAnimator.ofFloat(fragment, "y", 0f).apply {
            duration = ANIMATION_DURATION
            interpolator = DecelerateInterpolator()
            start()
        }

        window.navigationBarColor = resources.getColor(R.color.semitransparent_navigation)
        home_screen_grid.fragmentExpanded()
        home_screen_grid.hideResizeLines()
    }

    private fun hideFragment(fragment: View) {
        ObjectAnimator.ofFloat(fragment, "y", mScreenHeight.toFloat()).apply {
            duration = ANIMATION_DURATION
            interpolator = DecelerateInterpolator()
            start()
        }

        window.navigationBarColor = Color.TRANSPARENT
        home_screen_grid.fragmentCollapsed()
        Handler().postDelayed({
            if (fragment is AllAppsFragment) {
                fragment.all_apps_grid.scrollToPosition(0)
                fragment.touchDownY = -1
            } else if (fragment is WidgetsFragment) {
                fragment.widgets_list.scrollToPosition(0)
                fragment.touchDownY = -1
            }
        }, ANIMATION_DURATION)
    }

    fun homeScreenLongPressed(x: Float, y: Float) {
        if (isAllAppsFragmentExpanded()) {
            return
        }

        mIgnoreMoveEvents = true
        val clickedGridItem = home_screen_grid.isClickingGridItem(x.toInt(), y.toInt())
        if (clickedGridItem != null) {
            if (clickedGridItem.type == ITEM_TYPE_ICON || clickedGridItem.type == ITEM_TYPE_SHORTCUT) {
                main_holder.performHapticFeedback()
            }

            val anchorY = home_screen_grid.sideMargins.top + (clickedGridItem.top * home_screen_grid.cellHeight.toFloat())
            showHomeIconMenu(x, anchorY, clickedGridItem, false)
            return
        }

        main_holder.performHapticFeedback()
        showMainLongPressMenu(x, y)
    }

    fun homeScreenClicked(x: Float, y: Float) {
        home_screen_grid.hideResizeLines()
        val clickedGridItem = home_screen_grid.isClickingGridItem(x.toInt(), y.toInt())
        if (clickedGridItem != null) {
            if (clickedGridItem.type == ITEM_TYPE_ICON) {
                launchApp(clickedGridItem.packageName, clickedGridItem.activityName)
            } else if (clickedGridItem.type == ITEM_TYPE_SHORTCUT) {
                if (clickedGridItem.intent.isNotEmpty()) {
                    launchShortcutIntent(clickedGridItem)
                } else {
                    // launch pinned shortcuts
                    val id = clickedGridItem.shortcutId
                    val packageName = clickedGridItem.packageName
                    val userHandle = android.os.Process.myUserHandle()
                    val shortcutBounds = home_screen_grid.getClickableRect(clickedGridItem)
                    val launcherApps = applicationContext.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
                    launcherApps.startShortcut(packageName, id, shortcutBounds, null, userHandle)
                }
            }
        }
    }

    fun showHomeIconMenu(x: Float, y: Float, gridItem: HomeScreenGridItem, isOnAllAppsFragment: Boolean) {
        home_screen_grid.hideResizeLines()
        mLongPressedIcon = gridItem
        val anchorY = if (isOnAllAppsFragment || gridItem.type == ITEM_TYPE_WIDGET) {
            y
        } else if (gridItem.top == ROW_COUNT - 1) {
            home_screen_grid.sideMargins.top + (gridItem.top * home_screen_grid.cellHeight.toFloat())
        } else {
            (gridItem.top * home_screen_grid.cellHeight.toFloat())
        }

        home_screen_popup_menu_anchor.x = x
        home_screen_popup_menu_anchor.y = anchorY

        if (mOpenPopupMenu == null) {
            mOpenPopupMenu = handleGridItemPopupMenu(home_screen_popup_menu_anchor, gridItem, isOnAllAppsFragment)
        }
    }

    fun widgetLongPressedOnList(gridItem: HomeScreenGridItem) {
        mLongPressedIcon = gridItem
        hideFragment(widgets_fragment)
        home_screen_grid.itemDraggingStarted(mLongPressedIcon!!)
    }

    private fun showMainLongPressMenu(x: Float, y: Float) {
        home_screen_grid.hideResizeLines()
        home_screen_popup_menu_anchor.x = x
        home_screen_popup_menu_anchor.y = y - resources.getDimension(R.dimen.long_press_anchor_button_offset_y) * 2
        val contextTheme = ContextThemeWrapper(this, getPopupMenuTheme())
        PopupMenu(contextTheme, home_screen_popup_menu_anchor, Gravity.TOP or Gravity.END).apply {
            inflate(R.menu.menu_home_screen)
            setOnMenuItemClickListener { item ->
                when (item.itemId) {
                    R.id.widgets -> showWidgetsFragment()
                    R.id.wallpapers -> launchWallpapersIntent()
                }
                true
            }
            show()
        }
    }

    private fun handleGridItemPopupMenu(anchorView: View, gridItem: HomeScreenGridItem, isOnAllAppsFragment: Boolean): PopupMenu {
        var visibleMenuButtons = 6
        visibleMenuButtons -= when (gridItem.type) {
            ITEM_TYPE_ICON -> 1
            ITEM_TYPE_WIDGET -> 3
            else -> 4
        }

        if (isOnAllAppsFragment) {
            visibleMenuButtons--
        }

        if (gridItem.type != ITEM_TYPE_WIDGET) {
            visibleMenuButtons--
        }

        val yOffset = resources.getDimension(R.dimen.long_press_anchor_button_offset_y) * (visibleMenuButtons - 1)
        anchorView.y -= yOffset

        val contextTheme = ContextThemeWrapper(this, getPopupMenuTheme())
        return PopupMenu(contextTheme, anchorView, Gravity.TOP or Gravity.END).apply {
            if (isQPlus()) {
                setForceShowIcon(true)
            }

            inflate(R.menu.menu_app_icon)
            menu.findItem(R.id.rename).isVisible = gridItem.type == ITEM_TYPE_ICON && !isOnAllAppsFragment
            menu.findItem(R.id.hide_icon).isVisible = gridItem.type == ITEM_TYPE_ICON && isOnAllAppsFragment
            menu.findItem(R.id.resize).isVisible = gridItem.type == ITEM_TYPE_WIDGET
            menu.findItem(R.id.app_info).isVisible = gridItem.type == ITEM_TYPE_ICON
            menu.findItem(R.id.uninstall).isVisible = gridItem.type == ITEM_TYPE_ICON
            menu.findItem(R.id.remove).isVisible = !isOnAllAppsFragment
            setOnMenuItemClickListener { item ->
                resetFragmentTouches()
                when (item.itemId) {
                    R.id.hide_icon -> hideIcon(gridItem)
                    R.id.rename -> renameItem(gridItem)
                    R.id.resize -> home_screen_grid.widgetLongPressed(gridItem)
                    R.id.app_info -> launchAppInfo(gridItem.packageName)
                    R.id.remove -> home_screen_grid.removeAppIcon(gridItem)
                    R.id.uninstall -> uninstallApp(gridItem.packageName)
                }
                true
            }

            setOnDismissListener {
                mOpenPopupMenu = null
                resetFragmentTouches()
            }

            show()
        }
    }

    private fun resetFragmentTouches() {
        (widgets_fragment as WidgetsFragment).apply {
            touchDownY = -1
            ignoreTouches = false
        }

        (all_apps_fragment as AllAppsFragment).apply {
            touchDownY = -1
            ignoreTouches = false
        }
    }

    private fun showWidgetsFragment() {
        showFragment(widgets_fragment)
    }

    private fun hideIcon(item: HomeScreenGridItem) {
        ensureBackgroundThread {
            val hiddenIcon = HiddenIcon(null, item.packageName, item.activityName, item.title, null)
            hiddenIconsDB.insert(hiddenIcon)

            runOnUiThread {
                (all_apps_fragment as AllAppsFragment).hideIcon(item)
            }
        }
    }

    private fun renameItem(homeScreenGridItem: HomeScreenGridItem) {
        RenameItemDialog(this, homeScreenGridItem) {
            home_screen_grid.fetchGridItems()
        }
    }

    private fun launchWallpapersIntent() {
        try {
            Intent(Intent.ACTION_SET_WALLPAPER).apply {
                startActivity(this)
            }
        } catch (e: ActivityNotFoundException) {
            toast(R.string.no_app_found)
        } catch (e: Exception) {
            showErrorToast(e)
        }
    }

    private class MyGestureListener(private val flingListener: FlingListener) : GestureDetector.SimpleOnGestureListener() {
        override fun onSingleTapUp(event: MotionEvent): Boolean {
            (flingListener as MainActivity).homeScreenClicked(event.x, event.y)
            return super.onSingleTapUp(event)
        }

        override fun onFling(event1: MotionEvent, event2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
            // ignore fling events just after releasing an icon from dragging
            if (System.currentTimeMillis() - mLastUpEvent < 500L) {
                return true
            }

            if (velocityY > 0) {
                flingListener.onFlingDown()
            } else {
                flingListener.onFlingUp()
            }
            return true
        }

        override fun onLongPress(event: MotionEvent) {
            (flingListener as MainActivity).homeScreenLongPressed(event.x, event.y)
        }
    }

    override fun onFlingUp() {
        if (!isWidgetsFragmentExpanded()) {
            mIgnoreUpEvent = true
            showFragment(all_apps_fragment)
        }
    }

    @SuppressLint("WrongConstant")
    override fun onFlingDown() {
        mIgnoreUpEvent = true
        if (isAllAppsFragmentExpanded()) {
            hideFragment(all_apps_fragment)
        } else if (isWidgetsFragmentExpanded()) {
            hideFragment(widgets_fragment)
        } else {
            try {
                Class.forName("android.app.StatusBarManager").getMethod("expandNotificationsPanel").invoke(getSystemService("statusbar"))
            } catch (e: Exception) {
            }
        }
    }

    @SuppressLint("WrongConstant")
    fun getAllAppLaunchers(): ArrayList<AppLauncher> {
        val hiddenIcons = hiddenIconsDB.getHiddenIcons().map {
            it.getIconIdentifier()
        }

        val allApps = ArrayList<AppLauncher>()
        val intent = Intent(Intent.ACTION_MAIN, null)
        intent.addCategory(Intent.CATEGORY_LAUNCHER)

        val simpleLauncher = applicationContext.packageName
        val microG = "com.google.android.gms"
        val list = packageManager.queryIntentActivities(intent, PackageManager.PERMISSION_GRANTED)
        for (info in list) {
            val componentInfo = info.activityInfo.applicationInfo
            val packageName = componentInfo.packageName
            if (packageName == simpleLauncher || packageName == microG) {
                continue
            }

            val activityName = info.activityInfo.name
            if (hiddenIcons.contains("$packageName/$activityName")) {
                continue
            }

            val label = info.loadLabel(packageManager).toString()
            val drawable = info.loadIcon(packageManager) ?: getDrawableForPackageName(packageName) ?: continue
            val placeholderColor = calculateAverageColor(drawable.toBitmap())
            allApps.add(AppLauncher(null, label, packageName, activityName, 0, placeholderColor, drawable))
        }

        // add Simple Launchers settings as an app
        val drawable = getDrawableForPackageName(packageName)
        val placeholderColor = calculateAverageColor(drawable!!.toBitmap())
        val launcherSettings = AppLauncher(null, getString(R.string.launcher_settings), packageName, "", 0, placeholderColor, drawable)
        allApps.add(launcherSettings)
        launchersDB.insertAll(allApps)
        return allApps
    }

    private fun getDefaultAppPackages(appLaunchers: ArrayList<AppLauncher>) {
        val homeScreenGridItems = ArrayList<HomeScreenGridItem>()
        try {
            val defaultDialerPackage = (getSystemService(Context.TELECOM_SERVICE) as TelecomManager).defaultDialerPackage
            appLaunchers.firstOrNull { it.packageName == defaultDialerPackage }?.apply {
                val dialerIcon =
                    HomeScreenGridItem(null, 0, ROW_COUNT - 1, 0, ROW_COUNT - 1, defaultDialerPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null)
                homeScreenGridItems.add(dialerIcon)
            }
        } catch (e: Exception) {
        }

        try {
            val defaultSMSMessengerPackage = Telephony.Sms.getDefaultSmsPackage(this)
            appLaunchers.firstOrNull { it.packageName == defaultSMSMessengerPackage }?.apply {
                val SMSMessengerIcon =
                    HomeScreenGridItem(null, 1, ROW_COUNT - 1, 1, ROW_COUNT - 1, defaultSMSMessengerPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null)
                homeScreenGridItems.add(SMSMessengerIcon)
            }
        } catch (e: Exception) {
        }

        try {
            val browserIntent = Intent("android.intent.action.VIEW", Uri.parse("http://"))
            val resolveInfo = packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
            val defaultBrowserPackage = resolveInfo!!.activityInfo.packageName
            appLaunchers.firstOrNull { it.packageName == defaultBrowserPackage }?.apply {
                val browserIcon =
                    HomeScreenGridItem(null, 2, ROW_COUNT - 1, 2, ROW_COUNT - 1, defaultBrowserPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null)
                homeScreenGridItems.add(browserIcon)
            }
        } catch (e: Exception) {
        }

        try {
            val potentialStores = arrayListOf("com.android.vending", "org.fdroid.fdroid", "com.aurora.store")
            val storePackage = potentialStores.firstOrNull { isPackageInstalled(it) && appLaunchers.map { it.packageName }.contains(it) }
            if (storePackage != null) {
                appLaunchers.firstOrNull { it.packageName == storePackage }?.apply {
                    val storeIcon = HomeScreenGridItem(null, 3, ROW_COUNT - 1, 3, ROW_COUNT - 1, storePackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null)
                    homeScreenGridItems.add(storeIcon)
                }
            }
        } catch (e: Exception) {
        }

        try {
            val cameraIntent = Intent("android.media.action.IMAGE_CAPTURE")
            val resolveInfo = packageManager.resolveActivity(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY)
            val defaultCameraPackage = resolveInfo!!.activityInfo.packageName
            appLaunchers.firstOrNull { it.packageName == defaultCameraPackage }?.apply {
                val cameraIcon =
                    HomeScreenGridItem(null, 4, ROW_COUNT - 1, 4, ROW_COUNT - 1, defaultCameraPackage, "", title, ITEM_TYPE_ICON, "", -1, "", "", null)
                homeScreenGridItems.add(cameraIcon)
            }
        } catch (e: Exception) {
        }

        homeScreenGridItemsDB.insertAll(homeScreenGridItems)
    }

    fun handleWidgetBinding(
        appWidgetManager: AppWidgetManager,
        appWidgetId: Int,
        appWidgetInfo: AppWidgetProviderInfo,
        callback: (canBind: Boolean) -> Unit
    ) {
        mActionOnCanBindWidget = null
        val canCreateWidget = appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId, appWidgetInfo.provider)
        if (canCreateWidget) {
            callback(true)
        } else {
            mActionOnCanBindWidget = callback
            Intent(AppWidgetManager.ACTION_APPWIDGET_BIND).apply {
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
                putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, appWidgetInfo.provider)
                startActivityForResult(this, REQUEST_ALLOW_BINDING_WIDGET)
            }
        }
    }

    fun handleWidgetConfigureScreen(appWidgetHost: AppWidgetHost, appWidgetId: Int, callback: (canBind: Boolean) -> Unit) {
        mActionOnWidgetConfiguredWidget = callback
        appWidgetHost.startAppWidgetConfigureActivityForResult(
            this,
            appWidgetId,
            0,
            REQUEST_CONFIGURE_WIDGET,
            null
        )
    }

    fun handleShorcutCreation(activityInfo: ActivityInfo, callback: (label: String, icon: Bitmap?, intent: String) -> Unit) {
        mActionOnAddShortcut = callback
        val componentName = ComponentName(activityInfo.packageName, activityInfo.name)
        Intent(Intent.ACTION_CREATE_SHORTCUT).apply {
            component = componentName
            startActivityForResult(this, REQUEST_CREATE_SHORTCUT)
        }
    }

    // taken from https://gist.github.com/maxjvh/a6ab15cbba9c82a5065d
    private fun calculateAverageColor(bitmap: Bitmap): Int {
        var red = 0
        var green = 0
        var blue = 0
        val height = bitmap.height
        val width = bitmap.width
        var n = 0
        val pixels = IntArray(width * height)
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
        var i = 0
        while (i < pixels.size) {
            val color = pixels[i]
            red += Color.red(color)
            green += Color.green(color)
            blue += Color.blue(color)
            n++
            i += 1
        }

        return Color.rgb(red / n, green / n, blue / n)
    }
}
