package com.kdanmobile.reader.copyfile

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import android.content.Context
import android.content.Intent
import com.kdanmobile.reader.event.EventBroadcaster
import com.kdanmobile.reader.event.EventManager
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.io.File
import java.lang.Exception

/**
 * 1. if info.shouldCopyFile == false,
 *          just call onCopySuccess() to send Event.Success(destination path)
 * 2. if source path == destination path,
 *          just call onCopySuccess() to send Event.Success(destination path)
 * 3. if the application has no permission,
 *          send Event.Failed(destination path, FileNotFoundException) and delete the destination file
 * 4. if an IOException is thrown during copying,
 *          send Event.Failed(destination path, IOException) and delete the destination file
 * 5. otherwise, when CopyFileViewModel finishes copying file,
 *          call onCopySuccess() to send Event.Success(destination path)
 *
 * Finally, when CopyFileViewModel::onCleared() is called, delete the destination file if it is not complete copied
*/

class CopyFileViewModel(applicationContext: Context,
                        private val intent: Intent,
                        private val copiedFileFolder: File,
                        private val eventManager: EventManager<Event> = EventManager()
): ViewModel(), EventBroadcaster<CopyFileViewModel.Event> by eventManager {

    sealed class Event {
        data class Success(val filePath: String?): Event()
        data class Failed(val filePath: String?, val e: Throwable): Event()
    }

    val copyProgressLiveData: LiveData<Int>
        get() = mCopyProgressLiveData

    private val mCopyProgressLiveData = MutableLiveData<Int>().apply { value = 0 }

    private var copyFileDisposable: Disposable? = null
    private var srcFilePath: String? = null
    private var dstFilePath: String? = null
    private var hasCompleteCopy = false

    init {
        copyFile(applicationContext)
    }

    fun copyFile(applicationContext: Context) {
        val info = CopyFileInfoFactory().createInfo(applicationContext, intent, copiedFileFolder)
        if (!info.shouldCopyFile) {
            onCopySuccess()
            return
        }

        val source = if (!info.filePath.isNullOrEmpty()) {
            FilePathSource(info.filePath!!)
        } else if (info.isActionView && null != info.uri) {
            FileUriSource(applicationContext, info.uri)
        } else {
            null
        }
        srcFilePath = source?.getFilePath()
        if (isSrcFileEqualsDstFile()) {
            onCopySuccess()
            return
        }

        val fullFileSize = Math.max(1, source?.getFileSize() ?: 1)
        val task = CopyFileTask(copiedFileFolder)
        task.setCopyMode(CopyFileTask.Mode.CREATE_NEW_FILE)
        dstFilePath = task.generateUniqueFilePath(source)

        copyFileDisposable = task.copyFile(source)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    val progress = Math.min(100, (it * 100.0 / fullFileSize).toInt())
                    onCopyProgressUpdate(progress)
                }, {
                    onCopyFailed(it)
                }, {
                    onCopySuccess()
                })
    }

    //  check the destination path is the same with the source path or not
    private fun isSrcFileEqualsDstFile(): Boolean {
        return try {
            (!srcFilePath.isNullOrEmpty() && File(srcFilePath).canonicalPath == File(dstFilePath).canonicalPath)
        } catch (e: Exception) {
            false
        }
    }

    private fun onCopyFailed(e: Throwable) {
        if (!hasCompleteCopy) {
            deleteDstFile()
            eventManager.send(Event.Failed(dstFilePath, e))
        }
    }

    private fun onCopySuccess() {
        hasCompleteCopy = true
        eventManager.send(Event.Success(dstFilePath))
    }

    private fun onCopyProgressUpdate(progress: Int) {
        mCopyProgressLiveData.postValue(progress)
    }

    private fun deleteDstFile() {
        dstFilePath?.apply {
            val file = File(this)
            if (file.exists()) {
                file.delete()
            }
        }
    }

    override fun onCleared() {
        if (copyFileDisposable?.isDisposed == false) {
            copyFileDisposable?.dispose()
        }
        if (!hasCompleteCopy) {
            deleteDstFile()
        }
        super.onCleared()
    }
}