package com.base.datarecovery.utils

import android.util.Log
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileFilter
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.Collections


object NewFileUtils {

    fun delete(filePath: String?): Boolean {
        return delete(getFileByPath(filePath))
    }

    private fun delete(file: File?): Boolean {
        if (file == null) return false
        if (file.isDirectory) {
            return deleteDir(file)
        }
        return deleteFile(file)
    }

    fun getFileByPath(filePath: String?): File? {
        return if (isSpace(filePath)) null else File(filePath)
    }

    private fun isSpace(s: String?): Boolean {
        if (s == null) return true
        var i = 0
        val len = s.length
        while (i < len) {
            if (!Character.isWhitespace(s[i])) {
                return false
            }
            ++i
        }
        return true
    }

    private fun deleteDir(dir: File?): Boolean {
        if (dir == null) return false
        // dir doesn't exist then return true
        if (!dir.exists()) return true
        // dir isn't a directory then return false
        if (!dir.isDirectory) return false
        val files = dir.listFiles()
        if (files != null && files.size > 0) {
            for (file in files) {
                if (file.isFile) {
                    if (!file.delete()) return false
                } else if (file.isDirectory) {
                    if (!deleteDir(file)) return false
                }
            }
        }
        return dir.delete()
    }

    private fun deleteFile(file: File?): Boolean {
        return file != null && (!file.exists() || file.isFile && file.delete())
    }

    fun listFilesInDir(dirPath: String?): List<File> {
        return listFilesInDir(dirPath, null)
    }

    private fun listFilesInDir(dirPath: String?, comparator: Comparator<File?>?): List<File> {
        return listFilesInDir(getFileByPath(dirPath), false, comparator)
    }


    private fun listFilesInDir(dir: File?, isRecursive: Boolean, comparator: java.util.Comparator<File?>?): List<File> {
        return listFilesInDirWithFilter(dir, { true }, isRecursive, comparator)
    }

    private fun listFilesInDirWithFilterInner(dir: File?, filter: FileFilter?, isRecursive: Boolean): List<File> {
        val list: MutableList<File> = ArrayList()
        if (!isDir(dir)) return list
        val files = dir?.listFiles()
        if (files != null && files.size > 0) {
            for (file in files) {
                if (filter?.accept(file) == true) {
                    list.add(file)
                }
                if (isRecursive && file.isDirectory) {
                    list.addAll(listFilesInDirWithFilterInner(file, filter, true))
                }
            }
        }
        return list
    }

    private fun isDir(dirPath: String?): Boolean {
        return isDir(getFileByPath(dirPath))
    }

    /**
     * Return whether it is a directory.
     *
     * @param file The file.
     * @return `true`: yes<br></br>`false`: no
     */
    private fun isDir(file: File?): Boolean {
        return file != null && file.exists() && file.isDirectory
    }


    private fun listFilesInDirWithFilter(dir: File?, filter: FileFilter?, isRecursive: Boolean, comparator: java.util.Comparator<File?>?): List<File> {
        val files = listFilesInDirWithFilterInner(dir, filter, isRecursive)
        if (comparator != null) {
            Collections.sort(files, comparator)
        }
        return files
    }

    fun getFileExtension(file: File?): String {
        if (file == null) return ""
        return getFileExtension(file.path)
    }

    fun getFileExtension(filePath: String): String {
        if (isSpace(filePath)) return ""
        val lastPoi = filePath.lastIndexOf('.')
        val lastSep = filePath.lastIndexOf(File.separator)
        if (lastPoi == -1 || lastSep >= lastPoi) return ""
        return filePath.substring(lastPoi + 1)
    }


    fun copy(srcPath: String?, destPath: String?): Boolean {
        return copy(getFileByPath(srcPath), getFileByPath(destPath), null)
    }

    private fun copy(src: File?,
                     dest: File?,
                     listener: OnReplaceListener?): Boolean {
        if (src == null) return false
        if (src.isDirectory) {
            return copyDir(src, dest, listener)
        }
        return copyFile(src, dest, listener)
    }


    private fun copyDir(srcDir: File,
                        destDir: File?,
                        listener: OnReplaceListener?): Boolean {
        return copyOrMoveDir(srcDir, destDir, listener, false)
    }

    private fun copyFile(srcFile: File,
                         destFile: File?,
                         listener: OnReplaceListener?): Boolean {
        return copyOrMoveFile(srcFile, destFile, listener, false)
    }


    fun move(srcPath: String?,
             destPath: String?): Boolean {
        return move(getFileByPath(srcPath), getFileByPath(destPath), null)
    }

    private fun move(src: File?,
                     dest: File?,
                     listener: OnReplaceListener?): Boolean {
        if (src == null) return false
        if (src.isDirectory) {
            return moveDir(src, dest, listener)
        }
        return moveFile(src, dest, listener)
    }

    private fun moveDir(srcDir: File?,
                        destDir: File?,
                        listener: OnReplaceListener?): Boolean {
        return copyOrMoveDir(srcDir, destDir, listener, true)
    }

    /**
     * Move the file.
     *
     * @param srcFile  The source file.
     * @param destFile The destination file.
     * @param listener The replace listener.
     * @return `true`: success<br></br>`false`: fail
     */
    private fun moveFile(srcFile: File?, destFile: File?, listener: OnReplaceListener?): Boolean {
        return copyOrMoveFile(srcFile, destFile, listener, true)
    }

    private fun copyOrMoveDir(srcDir: File?,
                              destDir: File?,
                              listener: OnReplaceListener?,
                              isMove: Boolean): Boolean {
        if (srcDir == null || destDir == null) return false
        // destDir's path locate in srcDir's path then return false
        val srcPath = srcDir.path + File.separator
        val destPath = destDir.path + File.separator
        if (destPath.contains(srcPath)) return false
        if (!srcDir.exists() || !srcDir.isDirectory) return false
        if (!createOrExistsDir(destDir)) return false
        val files = srcDir.listFiles()
        if (files != null && files.size > 0) {
            for (file in files) {
                val oneDestFile = File(destPath + file.name)
                if (file.isFile) {
                    if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false
                } else if (file.isDirectory) {
                    if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false
                }
            }
        }
        return !isMove || deleteDir(srcDir)
    }

    private fun copyOrMoveFile(srcFile: File?, destFile: File?, listener: OnReplaceListener?, isMove: Boolean): Boolean {
        if (srcFile == null || destFile == null) return false
        // srcFile equals destFile then return false
        if (srcFile == destFile) return false
        // srcFile doesn't exist or isn't a file then return false
        if (!srcFile.exists() || !srcFile.isFile) return false
        if (destFile.exists()) {
            if (listener == null || listener.onReplace(srcFile, destFile)) { // require delete the old file
                if (!destFile.delete()) { // unsuccessfully delete then return false
                    return false
                }
            } else {
                return true
            }
        }
        if (!createOrExistsDir(destFile.parentFile)) return false
        try {
            return (writeFileFromIS(destFile.absolutePath, FileInputStream(srcFile))
                    && !(isMove && !deleteFile(srcFile)))
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
            return false
        }
    }
    fun createOrExistsDir(dirPath: String?): Boolean {
        return createOrExistsDir(getFileByPath(dirPath))
    }

    ///////////////////////////////////////////////////////////////////////////
    // writeFileFromIS without progress
    ///////////////////////////////////////////////////////////////////////////
    private fun writeFileFromIS(filePath: String?, `is`: InputStream?): Boolean {
        return writeFileFromIS(getFileByPath(filePath), `is`, false, null)
    }

    private fun writeFileFromIS(file: File?,
                                `is`: InputStream?,
                                append: Boolean,
                                listener: OnProgressUpdateListener?): Boolean {
        if (`is` == null || !createOrExistsFile(file)) {
            Log.e("FileIOUtils", "create file <$file> failed.")
            return false
        }
        var os: OutputStream? = null
        try {
            os = BufferedOutputStream(FileOutputStream(file, append), sBufferSize)
            if (listener == null) {
                val data = ByteArray(sBufferSize)
                var len: Int
                while ((`is`.read(data).also { len = it }) != -1) {
                    os.write(data, 0, len)
                }
            } else {
                val totalSize = `is`.available().toDouble()
                var curSize = 0
                listener.onProgressUpdate(0.0)
                val data = ByteArray(sBufferSize)
                var len: Int
                while ((`is`.read(data).also { len = it }) != -1) {
                    os.write(data, 0, len)
                    curSize += len
                    listener.onProgressUpdate(curSize / totalSize)
                }
            }
            return true
        } catch (e: IOException) {
            e.printStackTrace()
            return false
        } finally {
            try {
                `is`.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
            try {
                os?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }



    fun rename(filePath: String?, newName: String?): Boolean {
        return rename(getFileByPath(filePath), newName)
    }
    private  fun rename(file: File?, newName: String?): Boolean {
        // file is null then return false
        if (file == null) return false
        // file doesn't exist then return false
        if (!file.exists()) return false
        // the new name is space then return false
        if (isSpace(newName)) return false
        // the new name equals old name then return true
        if (newName == file.name) return true
        val newFile = File(file.parent + File.separator + newName)
        // the new name of file exists then return false
        return (!newFile.exists()
                && file.renameTo(newFile))
    }

    fun createOrExistsDir(file: File?): Boolean {
        return file != null && (if (file.exists()) file.isDirectory else file.mkdirs())
    }
    private fun createOrExistsFile(file: File?): Boolean {
        if (file == null) return false
        if (file.exists()) return file.isFile
        if (!createOrExistsDir(file.parentFile)) return false
        try {
            return file.createNewFile()
        } catch (e: IOException) {
            e.printStackTrace()
            return false
        }
    }
    private var sBufferSize = 524288

    interface OnProgressUpdateListener {
        fun onProgressUpdate(progress: Double)
    }

    interface OnReplaceListener {
        fun onReplace(srcFile: File?, destFile: File?): Boolean
    }
}