/*
 * Nextcloud - Android Client
 *
 * SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */
package com.owncloud.android.ui.preview

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBar
import androidx.core.content.ContextCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.editimage.EditImageActivity
import com.nextcloud.client.jobs.download.FileDownloadHelper
import com.nextcloud.client.jobs.download.FileDownloadWorker
import com.nextcloud.client.jobs.download.FileDownloadWorker.Companion.getDownloadFinishMessage
import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.getUploadFinishMessage
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.model.WorkerState
import com.nextcloud.model.WorkerStateLiveData
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.extensions.getSerializableArgument
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.VirtualFolderType
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener
import com.owncloud.android.lib.common.operations.RemoteOperation
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.RemoveFileOperation
import com.owncloud.android.operations.SynchronizeFileOperation
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.fragment.FileFragment
import com.owncloud.android.ui.fragment.GalleryFragment
import com.owncloud.android.ui.fragment.OCFileListFragment
import com.owncloud.android.ui.preview.model.PreviewImageActivityState
import com.owncloud.android.utils.DisplayUtils
import com.owncloud.android.utils.MimeTypeUtil
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
import java.io.Serializable
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min

/**
 * Holds a swiping gallery where image files contained in an Nextcloud directory are shown.
 */
@Suppress("TooManyFunctions")
class PreviewImageActivity :
    FileActivity(),
    FileFragment.ContainerActivity,
    OnRemoteOperationListener,
    Injectable {
    private var livePhotoFile: OCFile? = null
    private var viewPager: ViewPager2? = null
    private var previewImagePagerAdapter: PreviewImagePagerAdapter? = null
    private var savedPosition: Int? = null
    private var downloadFinishReceiver: DownloadFinishReceiver? = null
    private var fullScreenAnchorView: View? = null

    private var isDownloadWorkStarted = false
    private var screenState = PreviewImageActivityState.Idle

    @Inject
    lateinit var preferences: AppPreferences

    @Inject
    lateinit var localBroadcastManager: LocalBroadcastManager

    private var actionBar: ActionBar? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        actionBar = supportActionBar

        if (savedInstanceState != null &&
            !savedInstanceState.getBoolean(
                KEY_SYSTEM_VISIBLE,
                true
            ) &&
            actionBar != null
        ) {
            actionBar?.hide()
        }

        setContentView(R.layout.preview_image_activity)

        livePhotoFile = intent.getParcelableArgument(EXTRA_LIVE_PHOTO_FILE, OCFile::class.java)

        setupDrawer()

        val chosenFile = intent.getParcelableArgument(EXTRA_FILE, OCFile::class.java)
        updateActionBarTitleAndHomeButton(chosenFile)

        if (actionBar != null) {
            viewThemeUtils.files.setWhiteBackButton(this, actionBar!!)
            actionBar?.setDisplayHomeAsUpEnabled(true)
        }

        fullScreenAnchorView = window.decorView
        // to keep our UI controls visibility in line with system bars visibility
        setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)

        val requestWaitingForBinder = savedInstanceState?.getBoolean(KEY_WAITING_FOR_BINDER) ?: false
        if (requestWaitingForBinder) {
            screenState = PreviewImageActivityState.WaitingForBinder
        }

        observeWorkerState()
        applyDisplayCutOutTopPadding()
        handleBackPress()
    }

    private fun applyDisplayCutOutTopPadding() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
            return
        }

        window.decorView.setOnApplyWindowInsetsListener { view, insets ->
            val displayCutout = insets.displayCutout
            if (displayCutout != null) {
                val safeInsetTop = displayCutout.safeInsetTop
                val viewPager = findViewById<View>(R.id.fragmentPager)
                viewPager.setPadding(
                    viewPager.paddingLeft,
                    safeInsetTop,
                    viewPager.paddingRight,
                    viewPager.paddingBottom
                )
                viewPager.setBackgroundColor(ContextCompat.getColor(this, R.color.black))
            }

            view.onApplyWindowInsets(insets)
        }
    }

    fun toggleActionBarVisibility(hide: Boolean) {
        if (actionBar == null) {
            return
        }

        if (hide) {
            actionBar?.hide()
        } else {
            actionBar?.show()
        }
    }

    private fun initViewPager(user: User) {
        val virtualFolderType = intent.getSerializableArgument(EXTRA_VIRTUAL_TYPE, Serializable::class.java)
        if (virtualFolderType != null && virtualFolderType !== VirtualFolderType.NONE) {
            val type = virtualFolderType as VirtualFolderType

            previewImagePagerAdapter = PreviewImagePagerAdapter(
                this,
                type,
                user,
                storageManager,
                preferences
            )
        } else {
            // get parent from path
            var parentFolder = file?.let { storageManager.getFileById(it.parentId) }

            if (parentFolder == null) {
                // should not be necessary
                parentFolder = storageManager.getFileByEncryptedRemotePath(OCFile.ROOT_PATH)
            }

            previewImagePagerAdapter = PreviewImagePagerAdapter(
                this,
                livePhotoFile,
                parentFolder,
                user,
                storageManager,
                MainApp.isOnlyOnDevice(),
                preferences
            )
        }

        viewPager = findViewById(R.id.fragmentPager)

        var position = if (savedPosition !=
            null
        ) {
            savedPosition
        } else {
            file?.let { previewImagePagerAdapter?.getFilePosition(it) }
        }
        position = position?.toDouble()?.let { max(it, 0.0).toInt() }

        viewPager?.adapter = previewImagePagerAdapter
        viewPager?.registerOnPageChangeCallback(object : OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                selectPage(position)
            }
        })
        if (position != null) {
            viewPager?.setCurrentItem(position, false)
        }

        if (position == 0 && file?.isDown == false) {
            // this is necessary because mViewPager.setCurrentItem(0) just after setting the
            // adapter does not result in a call to #onPageSelected(0)
            screenState = PreviewImageActivityState.WaitingForBinder
        }
    }

    private fun updateViewPagerAfterDeletionAndAdvanceForward() {
        val deletePosition = viewPager?.currentItem ?: return
        previewImagePagerAdapter?.let { adapter ->
            val nextPosition = min(deletePosition, adapter.itemCount - 1)
            viewPager?.setCurrentItem(nextPosition, true)
            adapter.delete(deletePosition)
            // Page needs to be reselected after the adapter has been updated. Otherwise, wrong title is shown
            selectPage(nextPosition)
        }
    }

    private fun handleBackPress() {
        onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                sendRefreshSearchEventBroadcast()
                isEnabled = false
                onBackPressedDispatcher.onBackPressed()
            }
        })
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId != android.R.id.home) {
            return super.onOptionsItemSelected(item)
        }

        sendRefreshSearchEventBroadcast()

        if (isDrawerOpen) {
            closeDrawer()
        } else {
            backToDisplayActivity()
        }

        return true
    }

    private fun sendRefreshSearchEventBroadcast() {
        val intent = Intent(GalleryFragment.REFRESH_SEARCH_EVENT_RECEIVER)
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
    }

    public override fun onStart() {
        super.onStart()
        val optionalUser = user
        if (optionalUser.isPresent) {
            var file: OCFile? = file ?: throw IllegalStateException("Instanced with a NULL OCFile")
            // / Validate handled file (first image to preview)
            require(MimeTypeUtil.isImage(file)) { "Non-image file passed as argument" }

            // Update file according to DB file, if it is possible
            if (file!!.fileId > FileDataStorageManager.ROOT_PARENT_ID) {
                file = storageManager.getFileById(file.fileId)
            }

            if (file != null) {
                // / Refresh the activity according to the Account and OCFile set
                setFile(file) // reset after getting it fresh from storageManager
                updateActionBarTitle(getFile()?.fileName)
                // if (!stateWasRecovered) {
                initViewPager(optionalUser.get())

                // }
            } else {
                // handled file not in the current Account
                finish()
            }
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putBoolean(KEY_WAITING_FOR_BINDER, screenState == PreviewImageActivityState.WaitingForBinder)
        outState.putBoolean(KEY_SYSTEM_VISIBLE, isSystemUIVisible)
    }

    override fun onRemoteOperationFinish(operation: RemoteOperation<*>?, result: RemoteOperationResult<*>) {
        super.onRemoteOperationFinish(operation, result)

        if (operation is RemoveFileOperation) {
            previewImagePagerAdapter?.let {
                if (it.itemCount <= 1) {
                    backToDisplayActivity()
                    return
                }
            }

            if (result.isSuccess) {
                updateViewPagerAfterDeletionAndAdvanceForward()
            }
        } else if (operation is SynchronizeFileOperation) {
            onSynchronizeFileOperationFinish(result)
        }
    }

    private fun onSynchronizeFileOperationFinish(result: RemoteOperationResult<*>) {
        if (result.isSuccess) {
            supportInvalidateOptionsMenu()
        }
    }

    private fun observeWorkerState() {
        WorkerStateLiveData.instance().observe(this) { state: WorkerState? ->
            when (state) {
                is WorkerState.DownloadStarted -> {
                    Log_OC.d(TAG, "Download worker started")
                    isDownloadWorkStarted = true

                    if (screenState == PreviewImageActivityState.WaitingForBinder) {
                        selectPageOnDownload()
                    }
                }

                is WorkerState.DownloadFinished -> {
                    Log_OC.d(TAG, "Download worker stopped")
                    isDownloadWorkStarted = false

                    if (screenState == PreviewImageActivityState.Edit) {
                        onImageDownloadComplete(state.currentFile)
                    } else {
                        setDownloadedItem()
                    }
                }

                else -> {
                    Log_OC.d(TAG, "Worker stopped")
                    isDownloadWorkStarted = false
                }
            }
        }
    }

    private fun setDownloadedItem() {
        savedPosition?.let { position ->

            previewImagePagerAdapter?.run {
                file?.let {
                    updateFile(position, it)
                    notifyItemChanged(position)
                }
            }

            if (user.isPresent) {
                initViewPager(user.get())
                viewPager?.currentItem = position
            }
        }
    }

    private fun selectPageOnDownload() {
        screenState = PreviewImageActivityState.Idle
        Log_OC.d(
            TAG,
            "Simulating reselection of current page after connection " +
                "of download binder"
        )
        selectPage(viewPager?.currentItem)
    }

    private fun onImageDownloadComplete(downloadedFile: OCFile?) {
        dismissLoadingDialog()
        screenState = PreviewImageActivityState.Idle
        file = downloadedFile
        file?.let {
            startEditImageActivity(it)
        }
    }

    override fun onResume() {
        super.onResume()

        downloadFinishReceiver = DownloadFinishReceiver()
        val downloadIntentFilter = IntentFilter(getDownloadFinishMessage())
        localBroadcastManager.registerReceiver(downloadFinishReceiver!!, downloadIntentFilter)

        val uploadFinishReceiver = UploadFinishReceiver()
        val uploadIntentFilter = IntentFilter(getUploadFinishMessage())
        localBroadcastManager.registerReceiver(uploadFinishReceiver, uploadIntentFilter)
    }

    public override fun onPause() {
        if (downloadFinishReceiver != null) {
            localBroadcastManager.unregisterReceiver(downloadFinishReceiver!!)
            downloadFinishReceiver = null
        }

        super.onPause()
    }

    private fun backToDisplayActivity() {
        sendRefreshSearchEventBroadcast()
        finish()
    }

    @SuppressFBWarnings("DLS")
    override fun showDetails(file: OCFile) {
        val intent = Intent(this, FileDisplayActivity::class.java).apply {
            setAction(FileDisplayActivity.ACTION_DETAILS)
            putExtra(EXTRA_FILE, file)
            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
        }

        startActivity(intent)
        finish()
    }

    override fun showDetails(file: OCFile, activeTab: Int) {
        showDetails(file)
    }

    @JvmOverloads
    fun requestForDownload(file: OCFile?, downloadBehaviour: String? = null) {
        if (file == null) return
        val user = user.orElseThrow { RuntimeException() }
        FileDownloadHelper.instance().downloadFileIfNotStartedBefore(user, file)
    }

    /**
     * This method will be invoked when a new page becomes selected. Animation is not necessarily
     * complete.
     *
     * @param position        Position index of the new selected page
     */
    fun selectPage(position: Int?) {
        if (position == null) return
        savedPosition = position

        val currentFile = previewImagePagerAdapter?.getFileAt(position)

        if (!isDownloadWorkStarted) {
            screenState = PreviewImageActivityState.WaitingForBinder
        } else {
            if (currentFile != null) {
                if (currentFile.isEncrypted &&
                    !currentFile.isDown &&
                    previewImagePagerAdapter?.pendingErrorAt(position) == false
                ) {
                    requestForDownload(currentFile)
                }

                // Call to reset image zoom to initial state
                // ((PreviewImagePagerAdapter) mViewPager.getAdapter()).resetZoom();
            }
        }

        if (currentFile != null) {
            updateActionBarTitle(currentFile.fileName)
            setDrawerIndicatorEnabled(false)
        }
    }

    private fun updateActionBarTitle(title: String?) {
        supportActionBar?.title = title
    }

    /**
     * Class waiting for broadcast events from the [FileDownloadWorker] service.
     *
     *
     * Updates the UI when a download is started or finished, provided that it is relevant for the
     * folder displayed in the gallery.
     */
    private inner class DownloadFinishReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            previewNewImage(intent)
        }
    }

    private inner class UploadFinishReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            previewNewImage(intent)
        }
    }

    @Suppress("NestedBlockDepth", "ReturnCount")
    private fun previewNewImage(intent: Intent) {
        val accountName = intent.getStringExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME)
        val downloadedRemotePath = intent.getStringExtra(FileDownloadWorker.EXTRA_REMOTE_PATH)
        val downloadBehaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR)

        if (account.name != accountName || downloadedRemotePath == null) {
            return
        }

        val file = storageManager.getFileByEncryptedRemotePath(downloadedRemotePath)
        val downloadWasFine = intent.getBooleanExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, false)

        if (EditImageActivity.OPEN_IMAGE_EDITOR == downloadBehaviour) {
            startImageEditor(file)
        } else {
            val position = previewImagePagerAdapter?.getFilePosition(file) ?: return

            if (position >= 0) {
                if (downloadWasFine) {
                    previewImagePagerAdapter?.updateFile(position, file)
                } else {
                    previewImagePagerAdapter?.updateWithDownloadError(position)
                }
                previewImagePagerAdapter?.notifyItemChanged(position)
            } else if (downloadWasFine) {
                val user = user

                if (user.isPresent) {
                    initViewPager(user.get())
                    val newPosition = previewImagePagerAdapter?.getFilePosition(file) ?: return
                    if (newPosition >= 0) {
                        viewPager?.currentItem = newPosition
                    }
                }
            }
        }
    }

    val isSystemUIVisible: Boolean
        get() = supportActionBar == null || supportActionBar?.isShowing == true

    fun toggleFullScreen() {
        fullScreenAnchorView?.let {
            val visible = (it.systemUiVisibility and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
            if (visible) {
                hideSystemUI(it)
            } else {
                showSystemUI(it)
            }
        }
    }

    fun startImageEditor(file: OCFile) {
        if (file.isDown) {
            startEditImageActivity(file)
        } else {
            showLoadingDialog(getString(R.string.preview_image_downloading_image_for_edit))
            screenState = PreviewImageActivityState.Edit
            requestForDownload(file, EditImageActivity.OPEN_IMAGE_EDITOR)
        }
    }

    private fun startEditImageActivity(file: OCFile) {
        if (!file.isDown) {
            DisplayUtils.showSnackMessage(this, R.string.preview_image_file_is_not_downloaded)
            return
        }

        val intent = Intent(this, EditImageActivity::class.java).apply {
            putExtra(EditImageActivity.EXTRA_FILE, file)
        }
        startActivity(intent)
    }

    override fun onBrowsedDownTo(folder: OCFile) {
        // TODO Auto-generated method stub
    }

    override fun onTransferStateChanged(file: OCFile, downloading: Boolean, uploading: Boolean) {
        // TODO Auto-generated method stub
    }

    @Suppress("DEPRECATION")
    private fun hideSystemUI(anchorView: View) {
        anchorView.systemUiVisibility = (
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                or View.SYSTEM_UI_FLAG_FULLSCREEN
                or View.SYSTEM_UI_FLAG_IMMERSIVE
                or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            )
    }

    @Suppress("DEPRECATION")
    private fun showSystemUI(anchorView: View) {
        anchorView.systemUiVisibility = (
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            )
    }

    companion object {
        val TAG: String = PreviewImageActivity::class.java.simpleName
        const val EXTRA_VIRTUAL_TYPE: String = "EXTRA_VIRTUAL_TYPE"
        private const val KEY_WAITING_FOR_BINDER = "WAITING_FOR_BINDER"
        private const val KEY_SYSTEM_VISIBLE = "TRUE"

        fun previewFileIntent(context: Context?, user: User?, file: OCFile?): Intent =
            Intent(context, PreviewImageActivity::class.java).apply {
                putExtra(EXTRA_FILE, file)
                putExtra(EXTRA_USER, user)
            }
    }
}
