package dev.lowrespalmtree.zmingz

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.AsyncTask
import android.os.Bundle
import android.os.Environment
import android.os.Parcelable
import android.provider.MediaStore
import android.util.Log
import android.view.View
import android.widget.PopupMenu
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_CANCEL
import com.arthenica.mobileffmpeg.Config.RETURN_CODE_SUCCESS
import com.arthenica.mobileffmpeg.FFmpeg
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.lang.ref.WeakReference

class MainActivity: AppCompatActivity() {

    /** Path for first image/video. */
    private var path1 = ""
    /** Path for second image/video. */
    private var path2 = ""
    /** Temporary path for the media to save, to use after perms being granted. */
    private var savingPath = ""
    /** Temporary type for the media to save, to use after perms being granted. */
    private var savingType = MediaType.IMAGE

    enum class MediaType { IMAGE, VIDEO }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (intent.action == Intent.ACTION_SEND) {
            val type = if (intent.type?.startsWith("image") == true) {
                MediaType.IMAGE
            } else if (intent.type?.startsWith("video") == true) {
                MediaType.VIDEO
            } else {
                null
            }
            val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
            if (type != null && uri != null)
                processMedia(uri, type)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        when (requestCode) {
            REQ_PICK_IMG, REQ_PICK_VID -> handlePickResult(requestCode, resultCode, data)
            else -> super.onActivityResult(requestCode, resultCode, data)
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQ_WRITE_PERM) {
            if (permissions[0] == Manifest.permission.WRITE_EXTERNAL_STORAGE) {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    saveWithPerm(savingPath, savingType)
                    return
                }
            }
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    fun openFile(v: View) {
        val req: Int
        val pickIntent = when (v.id) {
            buttonImage.id -> {
                req = REQ_PICK_IMG
                Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
            }
            buttonVideo.id -> {
                req = REQ_PICK_VID
                Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
            }
            else -> return
        }
        startActivityForResult(pickIntent, req)
    }

    fun openViewMenu(v: View) {
        val path = when (v.id) {
            imageView1.id, videoView1.id -> path1
            imageView2.id, videoView2.id -> path2
            else -> return
        }
        val type = mediaTypeFromViewId(v.id)
            ?: return

        val menu = PopupMenu(this, v)
        menu.menu.add(R.string.save).setOnMenuItemClickListener {
            save(path, type)
            true
        }
        menu.menu.add(R.string.share).setOnMenuItemClickListener {
            share(path, type)
            true
        }
        menu.show()
    }

    private fun mediaTypeFromViewId(id: Int): MediaType? =
        when (id) {
            imageView1.id, imageView2.id -> MediaType.IMAGE
            videoView1.id, videoView2.id -> MediaType.VIDEO
            else -> null
        }

    @Suppress("UNUSED_PARAMETER")
    private fun handlePickResult(requestCode: Int, resultCode: Int, data: Intent?) {
        val uri = data?.data
            ?: return Unit.also { Log.e(TAG, "No intent or data") }
        val type = when (requestCode) {
            REQ_PICK_IMG -> MediaType.IMAGE
            REQ_PICK_VID -> MediaType.VIDEO
            else -> return
        }
        processMedia(uri, type)
    }

    private fun processMedia(uri: Uri, type: MediaType) {
        val uriPath = uri.path
            ?: return Unit.also { Log.e(TAG, "No path in URI") }
        val extension = getFileExtension(uriPath)
        // Copy picked file to cache dir for FFmpeg.
        val inputFile = File.createTempFile("input", ".$extension", cacheDir)
        contentResolver.openInputStream(uri).use { inputStream ->
            if (inputStream == null)
                return Unit .also { Log.e(TAG, "Could not open input file") }
            FileOutputStream(inputFile).use { fos ->
                fos.buffered().write(inputStream.buffered().readBytes())
            }
        }

        val mediaCacheDir = File(cacheDir, "media")
        if (!mediaCacheDir.exists() && !mediaCacheDir.mkdir()) {
            Log.e(TAG, "Could not create cache media dir")
            Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
            return
        }

        imageLayout.visibility = View.GONE
        videoLayout.visibility = View.GONE
        Toast.makeText(this, R.string.please_wait, Toast.LENGTH_SHORT).show()

        val outputFile1 = File.createTempFile("output1", ".$extension", mediaCacheDir)
        MirrorTask(WeakReference(this), type, 1)
            .execute(inputFile.canonicalPath, outputFile1.canonicalPath, VF1)
        val outputFile2 = File.createTempFile("output2", ".$extension", mediaCacheDir)
        MirrorTask(WeakReference(this), type, 2)
            .execute(inputFile.canonicalPath, outputFile2.canonicalPath, VF2)
    }

    class MirrorTask(
        private val activity: WeakReference<MainActivity>,
        private val type: MediaType,
        private val index: Int
    ): AsyncTask<String, Void, Boolean>() {
        private lateinit var outputPath: String

        override fun doInBackground(vararg params: String?): Boolean {
            val inputPath = params[0]!!
            outputPath = params[1]!!
            val vf = params[2]!!
            val command = "-i $inputPath -vf \"$vf\" -y $outputPath"
            return when (val rc = FFmpeg.execute(command)) {
                RETURN_CODE_SUCCESS -> true. also { Log.d(TAG, "FFmpeg success") }
                RETURN_CODE_CANCEL -> false .also { Log.d(TAG, "FFmpeg canceled") }
                else -> false .also { Log.d(TAG, "FFmpeg failed with rc $rc") }
            }
        }

        override fun onPostExecute(result: Boolean?) {
            if (result == true) {
                activity.get()?.updateViews(type, index, outputPath)
            }
        }

    }

    internal fun updateViews(type: MediaType, viewIndex: Int, outputPath: String) {
        when (type) {
            MediaType.IMAGE -> {
                if (imageLayout.visibility != View.VISIBLE)
                    imageLayout.visibility = View.VISIBLE
                val mirrored = BitmapFactory.decodeFile(outputPath)
                val view = when (viewIndex) {
                    1 -> { path1 = outputPath; imageView1 }
                    2 -> { path2 = outputPath; imageView2 }
                    else -> return
                }
                view.setImageBitmap(mirrored)
            }
            MediaType.VIDEO -> {
                if (videoLayout.visibility != View.VISIBLE)
                    videoLayout.visibility = View.VISIBLE
                val view = when (viewIndex) {
                    1 -> { path1 = outputPath; videoView1 }
                    2 -> { path2 = outputPath; videoView2 }
                    else -> return
                }
                view.setVideoPath(outputPath)
                view.setOnPreparedListener { mp -> mp.isLooping = true }
                view.start()
            }
        }
    }

    private fun save(path: String, type: MediaType) {
        val writePermission = Manifest.permission.WRITE_EXTERNAL_STORAGE
        val permissionStatus = checkSelfPermission(writePermission)
        if (permissionStatus == PackageManager.PERMISSION_GRANTED) {
            saveWithPerm(path, type)
        } else {
            savingPath = path
            savingType = type
            requestPermissions(arrayOf(writePermission), REQ_WRITE_PERM)
        }
    }

    private fun saveWithPerm(path: String, type: MediaType) {
        // Ensure media directories for the app exist.
        val envDirId = when (type) {
            MediaType.IMAGE -> Environment.DIRECTORY_PICTURES
            MediaType.VIDEO -> Environment.DIRECTORY_MOVIES
        }
        val externalMediaDir = getExternalFilesDir(envDirId)
            ?: return Unit .also { Log.e(TAG, "Can't find an external dir") }
        val mediaDir = File(externalMediaDir, getString(R.string.app_name))
        if (!mediaDir.exists() && !mediaDir.mkdir()) {
            Log.e(TAG, "Failed to create app media dir: $mediaDir")
            Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
            return
        }

        val extension = getFileExtension(path).let { if (it.isEmpty()) "xxx" else it }
        val outputFile = File(mediaDir.canonicalPath, "${System.currentTimeMillis()}.$extension")
        if (!outputFile.createNewFile()) {
            Log.e(TAG, "Failed to create new file: $outputFile")
            Toast.makeText(this, R.string.write_error, Toast.LENGTH_SHORT).show()
            return
        }

        FileInputStream(path).use { fis ->
            FileOutputStream(outputFile).use { fos ->
                fos.buffered().write(fis.buffered().readBytes())
            }
        }
        Log.i(TAG, "File saved at $outputFile")
    }

    private fun share(path: String, type: MediaType) {
        val uri = FileProvider.getUriForFile(
            this,
            "dev.lowrespalmtree.fileprovider",
            File(path)
        )
        Log.i(TAG, "share with uri $uri")
        val shareIntent = Intent().also {
            it.action = Intent.ACTION_SEND
            it.putExtra(Intent.EXTRA_STREAM, uri)
            it.type = when (type) {
                MediaType.IMAGE -> "image/*"
                MediaType.VIDEO -> "video/*"
            }
        }
        startActivity(Intent.createChooser(shareIntent, getString(R.string.send)))
    }

    companion object {
        private const val TAG = "ZMINGZ"
        private const val REQ_PICK_IMG = 1
        private const val REQ_PICK_VID = 2
        private const val REQ_WRITE_PERM = 3
        private const val VF1 = "crop=iw/2:ih:0:0,split[left][tmp];[tmp]hflip[right];[left][right] hstack"
        private const val VF2 = "crop=iw/2:ih:iw/2:0,split[left][tmp];[tmp]hflip[right];[right][left] hstack"

        private fun getFileExtension(path: String): String {
            if (!path.contains('.'))
                return ""
            return path.substring(path.lastIndexOf('.') + 1)
        }
    }

}