package com.kdanmobile.reader.copyfile

import com.kdanmobile.reader.thumb.FileUtil
import com.kdanmobile.reader.utils.CloseUtils
import io.reactivex.Observable
import io.reactivex.Observer
import java.io.*
import java.lang.NullPointerException

class CopyFileTask(private val targetFolder: File) {

    interface ProgressListener {
        fun onCopySizeUpdated(currentCopySize: Long)
        fun onError(e: java.lang.Exception)
        fun onComplete()
    }

    enum class Mode {
        OVERWRITE, DO_NOTHING_IF_FILE_EXIST, DO_NOTHING, CREATE_NEW_FILE
    }

    private val BUFFER_SIZE = 1024
    private var copyMode = Mode.DO_NOTHING_IF_FILE_EXIST
    private var copySizeProgressListener: ProgressListener? = null

    fun setCopyMode(copyMode: Mode) {
        this.copyMode = copyMode
    }

    fun setCopySizeProgressListener(copySizeProgressListener: ProgressListener?) {
        this.copySizeProgressListener = copySizeProgressListener
    }

    private fun generateUniqueFile(fileName: String): File {
        val name = FileUtil.removeExtension(fileName)
        val extension = FileUtil.getExtension(fileName)
        var file = File(targetFolder, "$name.$extension")
        var index = 2
        while (file.exists()) {
            file = File(targetFolder, "$name($index).$extension")
            index++
        }
        return file
    }

    fun generateUniqueFilePath(source: CopyFileSource?): String? {
        if (copyMode == Mode.DO_NOTHING) return null
        if (null == source) return null

        val srcFileName = source.getFileName()
        if (srcFileName.isNullOrEmpty()) return null
        val srcFilePath = source.getFilePath()
        if (srcFilePath?.startsWith(targetFolder!!.absolutePath) == true) return srcFilePath

        var dstFile = File(targetFolder, srcFileName)
        if (dstFile.exists()) {
            when (copyMode) {
                Mode.DO_NOTHING_IF_FILE_EXIST -> return dstFile.absolutePath
                Mode.CREATE_NEW_FILE -> dstFile = generateUniqueFile(srcFileName!!)
                else -> {}
            }
        }
        return dstFile.absolutePath
    }

    fun copyFileFromSource(source: CopyFileSource?) {
        val dstPath = generateUniqueFilePath(source)
        if (null == dstPath || null == source?.getInputStream()) {
            copySizeProgressListener?.onError(NullPointerException())
            return
        } else if ((copyMode == Mode.DO_NOTHING_IF_FILE_EXIST && File(dstPath).exists()) || dstPath == source.getFilePath()) {
            copySizeProgressListener?.onComplete()
            return
        }

        var inputStream: InputStream? = null
        var outputStream: BufferedOutputStream? = null
        try {
            inputStream = BufferedInputStream(source.getInputStream())
            outputStream = BufferedOutputStream(FileOutputStream(dstPath))
            copyFile(inputStream, outputStream)
            copySizeProgressListener?.onComplete()
        } catch (e: Exception) {
            copySizeProgressListener?.onError(e)
        } finally {
            CloseUtils.closeIO(inputStream, outputStream)
        }
    }

    private fun copyFile(inputStream: InputStream, outputStream: OutputStream) {
        val buffer = ByteArray(BUFFER_SIZE)
        var read: Int
        var copySize = 0L
        copySizeProgressListener?.onCopySizeUpdated(copySize)
        while (true) {
            read = inputStream.read(buffer)
            if (read == -1) break
            outputStream.write(buffer, 0, read)
            copySize += read
            copySizeProgressListener?.onCopySizeUpdated(copySize)
        }
    }

    fun copyFile(source: CopyFileSource?): Observable<Long> {
        return Observable.create<Long> {
            val dstPath = generateUniqueFilePath(source)
            if (null == dstPath || null == source?.getInputStream()) {
                it.onError(NullPointerException())
                return@create
            } else if ((copyMode == Mode.DO_NOTHING_IF_FILE_EXIST && File(dstPath).exists()) || dstPath == source.getFilePath()) {
                it.onComplete()
                return@create
            }

            var inputStream: InputStream? = null
            var outputStream: BufferedOutputStream? = null
            try {
                inputStream = BufferedInputStream(source?.getInputStream())
                outputStream = BufferedOutputStream(FileOutputStream(dstPath))
                val buffer = ByteArray(BUFFER_SIZE)
                var read: Int
                var copySize = 0L
                it.onNext(copySize)
                while (true) {
                    read = inputStream.read(buffer)
                    if (read == -1) break
                    outputStream.write(buffer, 0, read)
                    copySize += read
                    it.onNext(copySize)
                }
                it.onComplete()
            } catch (e: Exception) {
                it.onError(e)
            } finally {
                CloseUtils.closeIO(inputStream, outputStream)
            }
        }
    }
}