package io.legado.app.model.localBook

import io.legado.app.data.appDb
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.TxtTocRule
import io.legado.app.help.DefaultData
import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.StringUtils
import io.legado.app.utils.Utf8BomUtils
import java.io.FileNotFoundException
import java.nio.charset.Charset
import java.util.regex.Matcher
import java.util.regex.Pattern

class TextFile(private val book: Book) {

    companion object {

        @Throws(FileNotFoundException::class)
        fun getChapterList(book: Book): ArrayList<BookChapter> {
            return TextFile(book).getChapterList()
        }

        @Throws(FileNotFoundException::class)
        fun getContent(book: Book, bookChapter: BookChapter): String {
            val count = (bookChapter.end!! - bookChapter.start!!).toInt()
            val buffer = ByteArray(count)
            LocalBook.getBookInputStream(book).use { bis ->
                bis.skip(bookChapter.start!!)
                bis.read(buffer)
            }
            return String(buffer, book.fileCharset())
                .substringAfter(bookChapter.title)
                .replace("^[\\n\\s]+".toRegex(), "　　")
        }

    }

    private val blank: Byte = 0x0a

    //默认从文件中获取数据的长度
    private val bufferSize = 512000

    //没有标题的时候，每个章节的最大长度
    private val maxLengthWithNoToc = 10 * 1024
    //使用正则划分目录，每个章节的最大允许长度
    private val maxLengthWithToc = 102400
    private val limitContentLength = book.getLimitContentLength()

    private val tocRules = arrayListOf<Pattern>()
    private var charset: Charset = book.fileCharset()

    /**
     * 获取目录
     */
    @Throws(FileNotFoundException::class)
    fun getChapterList(): ArrayList<BookChapter> {
        val rulePattern: Pattern? = if (book.charset == null || book.tocUrl.isEmpty()) {
            LocalBook.getBookInputStream(book).use { bis ->
                val buffer = ByteArray(bufferSize)
                var blockContent: String
                var length = bis.read(buffer)
                book.charset = EncodingDetect.getEncode(buffer)
                charset = book.fileCharset()
                blockContent = String(buffer, 0, length, charset)
                if (book.tocUrl.isNotEmpty()) {
                    book.tocUrl.toPattern(Pattern.MULTILINE)
                } else {
                    if (blockContent.isEmpty()) {
                        length = bis.read(buffer)
                        book.charset = EncodingDetect.getEncode(buffer)
                        blockContent = String(buffer, 0, length, charset)
                    }
                    getTocRule(blockContent)
                }
            }
        } else {
            book.tocUrl.toPattern(Pattern.MULTILINE)
        }
        return rulePattern?.let {
            analyze(rulePattern)
        } ?: analyze()
    }

    /**
     * 按规则解析目录
     */
    private fun analyze(pattern: Pattern): ArrayList<BookChapter> {
        val toc = arrayListOf<BookChapter>()
        LocalBook.getBookInputStream(book).use { bis ->
            var blockContent: String
            //加载章节
            var curOffset: Long = 0
            //读取的长度
            var length: Int
            val buffer = ByteArray(bufferSize)
            var bufferStart = 3
            bis.read(buffer, 0, 3)
            if (Utf8BomUtils.hasBom(buffer)) {
                bufferStart = 0
                curOffset = 3
            }
            //获取文件中的数据到buffer，直到没有数据为止
            while (bis.read(buffer, bufferStart, bufferSize - bufferStart)
                    .also { length = it } > 0
            ) {
                var end = bufferStart + length
                for (i in bufferStart + length - 1 downTo 0) {
                    if (buffer[i] == blank) {
                        end = i
                        break
                    }
                }
                //将数据转换成String, 不能超过length
                blockContent = String(buffer, 0, end, charset)
                buffer.copyInto(buffer, 0, end, bufferStart + length)
                bufferStart = bufferStart + length - end
                length = end
                //当前Block下使过的String的指针
                var seekPos = 0
                //进行正则匹配
                val matcher: Matcher = pattern.matcher(blockContent)
                //如果存在相应章节
                while (matcher.find()) { //获取匹配到的字符在字符串中的起始位置
                    val chapterStart = matcher.start()
                    //获取章节内容
                    val chapterContent = blockContent.substring(seekPos, chapterStart)
                    val chapterLength = chapterContent.toByteArray(charset).size
                    val lastStart = toc.lastOrNull()?.start ?: curOffset
                    if (limitContentLength && curOffset + chapterLength - lastStart > maxLengthWithToc) {
                        bis.close()
                        return analyze()
                    }
                    //如果 seekPos == 0 && chapterStart != 0 表示当前block处前面有一段内容
                    //第一种情况一定是序章 第二种情况是上一个章节的内容
                    if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容
                        if (toc.isEmpty()) { //如果当前没有章节，那么就是序章
                            //加入简介
                            if (StringUtils.trim(chapterContent).isNotEmpty()) {
                                val qyChapter = BookChapter()
                                qyChapter.title = "前言"
                                qyChapter.start = curOffset
                                qyChapter.end = chapterLength.toLong()
                                toc.add(qyChapter)
                            }
                            //创建当前章节
                            val curChapter = BookChapter()
                            curChapter.title = matcher.group()
                            curChapter.start = chapterLength.toLong()
                            toc.add(curChapter)
                        } else { //否则就block分割之后，上一个章节的剩余内容
                            //获取上一章节
                            val lastChapter = toc.last()
                            toc.last().isVolume = chapterContent.substringAfter(lastChapter.title).isBlank()
                            //将当前段落添加上一章去
                            lastChapter.end =
                                lastChapter.end!! + chapterLength.toLong()
                            //创建当前章节
                            val curChapter = BookChapter()
                            curChapter.title = matcher.group()
                            curChapter.start = lastChapter.end
                            toc.add(curChapter)
                        }
                    } else {
                        if (toc.isNotEmpty()) { //获取章节内容
                            //获取上一章节
                            val lastChapter = toc.last()
                            toc.last().isVolume = chapterContent.substringAfter(lastChapter.title).isBlank()
                            lastChapter.end =
                                lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong()
                            //创建当前章节
                            val curChapter = BookChapter()
                            curChapter.title = matcher.group()
                            curChapter.start = lastChapter.end
                            toc.add(curChapter)
                        } else { //如果章节不存在则创建章节
                            val curChapter = BookChapter()
                            curChapter.title = matcher.group()
                            curChapter.start = curOffset
                            curChapter.end = curOffset
                            toc.add(curChapter)
                        }
                    }
                    //设置指针偏移
                    seekPos += chapterContent.length
                }
                //block的偏移点
                curOffset += length.toLong()
                //设置上一章的结尾
                toc.lastOrNull()?.end = curOffset
            }
        }
        toc.forEachIndexed { index, bookChapter ->
            bookChapter.index = index
            bookChapter.bookUrl = book.bookUrl
            bookChapter.url = MD5Utils.md5Encode16(book.originName + index + bookChapter.title)
        }
        book.latestChapterTitle = toc.last().title
        book.totalChapterNum = toc.size

        System.gc()
        System.runFinalization()
        book.tocUrl = pattern.pattern()
        book.save()
        return toc
    }

    /**
     * 无规则拆分目录
     */
    private fun analyze(): ArrayList<BookChapter> {
        nextTocRule()?.let {
            return analyze(it)
        }
        val toc = arrayListOf<BookChapter>()
        LocalBook.getBookInputStream(book).use { bis ->
            //block的个数
            var blockPos = 0
            //加载章节
            var curOffset: Long = 0
            var chapterPos = 0
            //读取的长度
            var length: Int
            val buffer = ByteArray(bufferSize)
            var bufferStart = 3
            bis.read(buffer, 0, 3)
            if (Utf8BomUtils.hasBom(buffer)) {
                bufferStart = 0
                curOffset = 3
            }
            //获取文件中的数据到buffer，直到没有数据为止
            while (bis.read(buffer, bufferStart, bufferSize - bufferStart)
                    .also { length = it } > 0
            ) {
                blockPos++
                //章节在buffer的偏移量
                var chapterOffset = 0
                //当前剩余可分配的长度
                length += bufferStart
                var strLength = length
                //分章的位置
                chapterPos = 0
                while (strLength > 0) {
                    ++chapterPos
                    //是否长度超过一章
                    if (strLength > maxLengthWithNoToc) { //在buffer中一章的终止点
                        var end = length
                        //寻找换行符作为终止点
                        for (i in chapterOffset + maxLengthWithNoToc until length) {
                            if (buffer[i] == blank) {
                                end = i
                                break
                            }
                        }
                        val chapter = BookChapter()
                        chapter.title = "第${blockPos}章($chapterPos)"
                        chapter.start = toc.lastOrNull()?.end ?: curOffset
                        chapter.end = chapter.start!! + end - chapterOffset
                        toc.add(chapter)
                        //减去已经被分配的长度
                        strLength -= (end - chapterOffset)
                        //设置偏移的位置
                        chapterOffset = end
                    } else {
                        buffer.copyInto(buffer, 0, length - strLength, length)
                        length -= strLength
                        bufferStart = strLength
                        strLength = 0
                    }
                }
                //block的偏移点
                curOffset += length.toLong()
            }
            //设置结尾章节
            if (bufferStart > 100) {
                val chapter = BookChapter()
                chapter.title = "第${blockPos}章(${chapterPos + 1})"
                chapter.start = toc.lastOrNull()?.end ?: curOffset
                chapter.end = chapter.start!! + bufferStart
                toc.add(chapter)
            } else {
                toc.lastOrNull()?.let {
                    it.end = it.end!! + bufferStart
                }
            }
        }
        if (toc.isEmpty()) {
            return analyze()
        }
        toc.forEachIndexed { index, bookChapter ->
            bookChapter.index = index
            bookChapter.bookUrl = book.bookUrl
            bookChapter.url = MD5Utils.md5Encode16(book.originName + index + bookChapter.title)
        }
        book.latestChapterTitle = toc.last().title
        book.totalChapterNum = toc.size

        System.gc()
        System.runFinalization()
        book.tocUrl = ""
        book.save()
        return toc
    }

    /**
     * 初始化并获取匹配次数最多的规则
     */
    private fun getTocRule(content: String): Pattern? {
        tocRules.addAll(getTocRules(content, getTocRules().reversed()))
        //tocRules.addAll(getTocRules(content, appDb.txtTocRuleDao.disabled.reversed()))
        return tocRules.firstOrNull()
    }

    /**
     * 获取下一个规则并移除上一个
     */
    private fun nextTocRule(): Pattern? {
        tocRules.removeFirstOrNull()
        return tocRules.firstOrNull()
    }

    /**
     * 获取所有匹配次数大于1的目录规则
     */
    private fun getTocRules(content: String, rules: List<TxtTocRule>): ArrayList<Pattern> {
        val list = arrayListOf<Pattern>()
        var maxCs = 1
        for (tocRule in rules) {
            val pattern = tocRule.rule.toPattern(Pattern.MULTILINE)
            val matcher = pattern.matcher(content)
            var cs = 0
            while (matcher.find()) {
                cs++
            }
            if (cs >= maxCs) {
                maxCs = cs
                list.add(0, pattern)
            } else if (cs > 1) {
                list.add(pattern)
            }
        }
        return list
    }

    /**
     * 获取启用的目录规则
     */
    private fun getTocRules(): List<TxtTocRule> {
        var rules = appDb.txtTocRuleDao.enabled
        if (rules.isEmpty()) {
            rules = DefaultData.txtTocRules.apply {
                appDb.txtTocRuleDao.insert(*this.toTypedArray())
            }.filter {
                it.enable
            }
        }
        return rules
    }

}