package com.gh.gamecenter.xapk

import com.gh.gamecenter.xapk.core.XApkFile
import com.gh.gamecenter.xapk.core.XApkUnZipOutputFactory
import com.gh.gamecenter.xapk.core.XApkUnZipTaskCallback
import com.gh.gamecenter.xapk.io.XApkFileWriterCancelException
import com.gh.gamecenter.xapk.io.XApkFileWriterWriteException
import com.gh.gamecenter.xapk.tool.ProgressRecorder
import java.io.File
import java.io.OutputStream

class XApkUnZipTask internal constructor(
    val id: String,
    val file: File,
    val isXapkApks: Boolean,
    val useFlawToUnzip: Boolean,
    private val factory: XApkUnZipOutputFactory,
    private val callback: XApkUnZipTaskCallback
) : Runnable {

    private val progressRecorder = ProgressRecorder()

    val apk = XApkFile(file, isXapkApks, useFlawToUnzip)

    private var runningThread: Thread? = null

    override fun run() {
        runningThread = Thread.currentThread()
        try {
            if (canceled) return

            if (!file.exists()) {
                throw XApkFileWriterWriteException(
                    ERROR_MSG_FILE_NOT_FOUND.format(file.absolutePath)
                )
            }

            if (file.isDirectory) {
                throw XApkFileWriterWriteException(
                    ERROR_MSG_FILE_IS_DIRECTORY.format(file.absolutePath)
                )
            }

            val manifest = apk.manifest

            val xAPkWriter = XApkWriter()

            progressRecorder.start(manifest.totalSize)

            if(!isXapkApks) {
                factory.onCreateOBBOutput(apk).output(xAPkWriter, manifest)
            }

            val apkOutput = factory.onCreateApkOutput(apk)

            val packageInstaller = apkOutput.output(xAPkWriter, manifest)

            callback.onSuccess(this, packageInstaller)
        } catch (_: XApkFileWriterCancelException) {

        } catch (e: Throwable) {
            callback.onError(this, e)
        }
    }

    @Volatile
    private var canceled: Boolean = false

    fun cancel() {
        canceled = true
        callback.onCancel(this)
    }

    inner class XApkWriter internal constructor() {

        fun write(
            fileName: String,
            output: OutputStream,
            bufferedSize: Int = DEFAULT_BUFFER_SIZE,
            onCopyFinish: (OutputStream) -> Unit = {}
        ) {
            callback.onNext(this@XApkUnZipTask, fileName)
            with(apk.getInputStream(fileName)) {
                try {
                    var bytesCopied: Long = 0
                    val buffer = ByteArray(bufferedSize)
                    var bytes = read(buffer)
                    while (bytes >= 0) {
                        if (canceled) {
                            throw XApkFileWriterCancelException()
                        }
                        output.write(buffer, 0, bytes)
                        if (!canceled && progressRecorder.record(bytes)) {
                            callback.onProgress(this@XApkUnZipTask, progressRecorder.progress)
                        }
                        bytesCopied += bytes
                        bytes = read(buffer)
                    }
                    onCopyFinish(output)
                } catch (e: Throwable) {
                    throw e
                } finally {
                    close()
                    output.close()
                }
            }
        }
    }

    companion object {
        private const val ERROR_MSG_FILE_NOT_FOUND = "未找到压缩包文件：%s"
        private const val ERROR_MSG_FILE_IS_DIRECTORY = "压缩包文件%s是一个文件夹"
    }
}