package com.convenient.android.common.utils.file

import com.convenient.android.common.extension.outputStream
import com.convenient.android.common.extension.toFile
import java.io.*
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.nio.charset.Charset

/**
 * @classname:
 * @author: LiuXiaoLong
 * @date: 2022/7/27
 * description: 文件相关的io操作工具类
 */
object FileIOUtils {

    private val DEFAULT_CHARSET_NAME = "UTF-8"

    /**
     * 从输入流写入文件
     * @param filePath 文件路径
     * @param inputStream 输入流
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromIS(filePath: String, inputStream: InputStream?, append : Boolean = false): Boolean {
        return writeFileFromIS(filePath.toFile(), inputStream, append)
    }


    /**
     * 从输入流写入文件
     * @param file 文件
     * @param is 要写入的数据输入流
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromIS(file: File?, inputStream : InputStream?, append : Boolean = false): Boolean {
        return writeFileFromBytesByStream(file = file, bytes = inputStream?.readBytes(), append = append)
    }

    /**
     * 写入字节流
     * @param filePath 写入的文件地址
     * @param bytes 要写入的数组
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByStream(filePath: String, bytes: ByteArray?, append: Boolean = false): Boolean {
        return writeFileFromBytesByStream(filePath.toFile(), bytes, append)
    }


    /**
     * Write file from bytes by stream.
     * @param file The file.
     * @param bytes The bytes.
     * @param append 是否附加数据 true,在后面添加
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByStream(
        file: File?,
        bytes: ByteArray?,
        append: Boolean
    ): Boolean {
        if (bytes == null || !createOrExistsFile(file)) {
            return false
        }
        return try {
            if (append){
                file?.appendBytes(bytes)
            }else{
                file?.writeBytes(bytes)
            }
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }



    /**
     * Write file from bytes by channel.
     * @param filePath The path of file.
     * @param bytes The bytes.
     * @param append True to append, false otherwise.
     * @param isForce True to force write file, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByChannel(
        filePath: String,
        bytes: ByteArray?,
        append: Boolean = false,
        isForce: Boolean
    ): Boolean {
        return writeFileFromBytesByChannel(filePath.toFile(), bytes, append, isForce)
    }


    /**
     * Write file from bytes by channel.
     * @param file The file.
     * @param bytes The bytes.
     * @param append True to append, false otherwise.
     * @param isForce True to force write file, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByChannel(
        file: File?,
        bytes: ByteArray?,
        append: Boolean,
        isForce: Boolean
    ): Boolean {
        if (bytes == null) {
            return false
        }
        return try {
            file?.outputStream(append)?.channel?.use {
                it.position(it.size())
                it.write(ByteBuffer.wrap(bytes))
                if (isForce){
                    it.force(true)
                }
            }
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }


    /**
     * Write file from bytes by map.
     * @param filePath The path of file.
     * @param bytes The bytes.
     * @param append True to append, false otherwise.
     * @param isForce True to force write file, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByMap(
        filePath: String,
        bytes: ByteArray?,
        append: Boolean = false,
        isForce: Boolean
    ): Boolean {
        return writeFileFromBytesByMap(filePath.toFile(), bytes, append, isForce)
    }


    /**
     * Write file from bytes by map.
     * @param file The file.
     * @param bytes The bytes.
     * @param append True to append, false otherwise.
     * @param isForce True to force write file, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromBytesByMap(
        file: File?,
        bytes: ByteArray?,
        append: Boolean,
        isForce: Boolean
    ): Boolean {
        if (bytes == null || !createOrExistsFile(file)) {
            return false
        }
        return try {
            file?.outputStream(append)?.channel?.use {
              val mbb = it.map(FileChannel.MapMode.READ_WRITE, it.size(), bytes.size.toLong())
               mbb.put(bytes)
               if (isForce){
                   mbb.force()
               }
           }
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }


    /**
     * Write file from string.
     * @param filePath The path of file.
     * @param content The string of content.
     * @param append True to append, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromString(
        filePath: String,
        content: String?,
        append: Boolean = false
    ): Boolean {
        return writeFileFromString(filePath.toFile(), content, append)
    }


    /**
     * Write file from string.
     * @param file The file.
     * @param content The string of content.
     * @param append True to append, false otherwise.
     * @return `true`: success<br></br>`false`: fail
     */
    fun writeFileFromString(
        file: File?,
        content: String?,
        append: Boolean
    ): Boolean {
        if (content == null) {
            return false
        }
        if (!createOrExistsFile(file)) {
            return false
        }
        return try {
            if (append){
                file?.appendText(content)
            }else{
                file?.writeText(content)
            }
            true
        } catch (e: IOException) {
            e.printStackTrace()
            false
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // the divide line of write and read
    ///////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////
    // the divide line of write and read
    ///////////////////////////////////////////////////////////////////////////

    /**
     * Return the lines in file.
     * @param filePath The path of file.
     * @param charsetName The name of charset.
     * @return the lines in file
     */
    fun readFile2List(filePath: String, charsetName: String = DEFAULT_CHARSET_NAME): List<String?>? {
        return readFile2List(filePath.toFile(), charsetName)
    }

    /**
     * Return the lines in file.
     * @param file The file.
     * @param charsetName The name of charset.
     * @return the lines in file
     */
    fun readFile2List(file: File?, charsetName: String = DEFAULT_CHARSET_NAME): List<String?>? {
        return readFile2List(file, 0, 0x7FFFFFFF, charsetName)
    }

    /**
     * Return the lines in file.
     * @param filePath The path of file.
     * @param st The line's index of start.
     * @param end The line's index of end.
     * @param charsetName The name of charset.
     * @return the lines in file
     */
    fun readFile2List(
        filePath: String,
        st: Int,
        end: Int,
        charsetName: String = DEFAULT_CHARSET_NAME
    ): List<String?>? {
        return readFile2List(filePath.toFile(), st, end, charsetName)
    }


    /**
     * Return the lines in file.
     * @param file The file.
     * @param st The line's index of start.
     * @param end The line's index of end.
     * @param charsetName The name of charset.
     * @return the lines in file
     */
    fun readFile2List(
        file: File?,
        st: Int,
        end: Int,
        charsetName: String = "UTF-8"
    ): List<String?>? {
        if (!isFileExists(file)) {
            return null
        }
        if (st > end) {
            return null
        }
        return try {
            var line: String?
            var curLine = 1

            val list: MutableList<String?> = ArrayList()

            val reader = file!!.bufferedReader(Charset.forName(charsetName))

            while (reader.readLine().also { line = it } != null) {
                if (curLine > end) {
                    break
                }
                if (curLine in st..end) {
                    list.add(line)
                }
                ++curLine
            }

            list
        } catch (e: IOException) {
            e.printStackTrace()
            null
        } finally {
        }
    }




    /**
     * Return the bytes in file by channel.
     * @param filePath The path of file.
     * @return the bytes in file
     */
    fun readFile2BytesByChannel(filePath: String): ByteArray? {
        return readFile2BytesByChannel(File(filePath))
    }

    /**
     * Return the bytes in file by channel.
     * @param file The file.
     * @return the bytes in file
     */
    fun readFile2BytesByChannel(file: File?): ByteArray? {
        if (!isFileExists(file)) {
            return null
        }
        var fc: FileChannel? = null
        return try {

            fc = RandomAccessFile(file, "r").channel
            val byteBuffer = ByteBuffer.allocate(fc.size().toInt())
            while (true) {
                if (fc.read(byteBuffer) <= 0) {
                    break
                }
            }
            byteBuffer.array()
        } catch (e: IOException) {
            e.printStackTrace()
            null
        } finally {
            CloseUtils.closeIO(fc)
        }
    }

    /**
     * Return the bytes in file by map.
     * @param filePath The path of file.
     * @return the bytes in file
     */
    fun readFile2BytesByMap(filePath: String): ByteArray? {
        return readFile2BytesByMap(File(filePath))
    }

    /**
     * Return the bytes in file by map.
     * @param file The file.
     * @return the bytes in file
     */
    fun readFile2BytesByMap(file: File?): ByteArray? {
        if (!isFileExists(file)) {
            return null
        }
        var fc: FileChannel? = null
        return try {
            fc = RandomAccessFile(file, "r").channel
            val size = fc.size().toInt()
            val mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size.toLong()).load()
            val result = ByteArray(size)
            mbb[result, 0, size]
            result
        } catch (e: IOException) {
            e.printStackTrace()
            null
        } finally {
            CloseUtils.closeIO(fc)
        }
    }

    fun createOrExistsFile(filePath: String): Boolean {
        return createOrExistsFile(File(filePath))
    }

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

    fun createOrExistsDir(file: File?): Boolean {
        return file != null && if (file.exists()) file.isDirectory else file.mkdirs()
    }

    private fun isFileExists(file: File?): Boolean {
        return file?.exists() == true
    }

}