package com.duolebo.blyrobot.util

import android.util.Log
import com.duolebo.blyrobot.data.EmulateKey
import java.io.BufferedReader
import java.io.DataOutputStream
import java.io.IOException
import java.io.InputStreamReader

object AdbUtil {
    const val TAG = "AdbUtil"
    const val SHELL = "sh"
    const val ROOT_SHELL = "su"
    const val LINE_BREAK = "\n"
    const val EXIT = "exit"

    fun checkRootPermission(): Boolean {
        val proc = getRootShell()
        return proc != null
    }

    fun getRootShell(): Process? {
        var proc: Process? = null
        try {
            proc = Runtime.getRuntime().exec(ROOT_SHELL)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return proc
    }

    fun exeCmdSync(cmd: String): Process? {
        Log.i(TAG, "exe cmd: $cmd")
        var proc: Process? = null
        try {
            proc = Runtime.getRuntime().exec(ROOT_SHELL)
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return proc
    }

    fun exeCmdEcho(cmd: String, root: Boolean = false) {
        val exeCommands = ArrayList<String>()
        exeCommands.add(cmd)
        exeCmdEcho(exeCommands, root)
    }

    fun exeCmdEcho(commands: ArrayList<String>, root: Boolean = false): List<String>? {
        Log.i(TAG, "exe cmd echo: $commands")
        val exeCommands = ArrayList<String>()
        exeCommands.addAll(commands)
        val results = sendCommands(exeCommands, root)
        for (line in results!!) {
            Log.i(TAG, "echo line: $line")
        }

        return results
    }

    fun sendCommands(commands: ArrayList<String>, root: Boolean = false): List<String>? {
        val results = ArrayList<String>()
        var status = -1
        if (commands.isEmpty()) {
            return null
        }
        Log.i(TAG, "exe commands : $commands")
        var proc: Process? = null
        var successBufReader: BufferedReader? = null
        var errorBufReader: BufferedReader? = null
        var errorMsg: StringBuilder? = null

        var dos: DataOutputStream? = null
        try {
            proc = if (root)
                Runtime.getRuntime().exec(ROOT_SHELL)
            else
                Runtime.getRuntime().exec(SHELL)
            dos = DataOutputStream(proc!!.outputStream)
            for (command in commands) {
                if (command.isEmpty()) {
                    continue
                }
                dos.writeBytes(command)
                dos.writeBytes(LINE_BREAK)
                dos.flush()
            }

            dos.writeBytes(EXIT + LINE_BREAK)
            dos.flush()

            status = proc.waitFor()

            errorMsg = StringBuilder()
            successBufReader = BufferedReader(InputStreamReader(proc.inputStream))
            errorBufReader = BufferedReader(InputStreamReader(proc.errorStream))
            var lineStr: String? = null
            do {
                lineStr = successBufReader.readLine()
                if (lineStr.isNullOrEmpty())
                    break
                lineStr = trimLine(lineStr)
                if (lineStr !in commands)
                    results.add(lineStr)
                Log.i(TAG, " success line echo : $lineStr")
            } while (true)

            results.add("----------")

            do {
                lineStr = errorBufReader.readLine()
                if (lineStr.isNullOrEmpty())
                    break
                lineStr = trimLine(lineStr)
                if (lineStr !in commands)
                    results.add(lineStr)
                Log.i(TAG, " error line echo : $lineStr")
            } while (true)

        } catch (e: IOException) {
            e.printStackTrace()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            try {
                dos?.close()
                successBufReader?.close()
                errorBufReader?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }

            proc?.destroy()
        }
        Log.i(TAG, "error message:$errorMsg and status $status")
        return results
    }

    private fun trimLine(line: String): String {
        var res = line
        if ('$' in line)
            res = line.split('$')[1].trimStart()
        if ('#' in line)
            res = line.split('$')[1].trimStart()
        res = res.replace("\r", "").replace("\n", "")

        return res
    }

    fun screenShot(storePath: String) {
//        Runtime.getRuntime().exec("screencap -p /sdcard/screen.png")
        exeCmdEcho("screencap -p $storePath", true)
    }

    fun sendKey(keyCode: String) {
        val cmdKey = "input keyevent $keyCode"
        exeCmdEcho(cmdKey, true)
    }

    fun sendMultiKey(keyPath: String) {
        val keyEvents = ArrayList<EmulateKey>()
        val keys = keyPath.split(Constants.SLASH)
        for (stepKey in keys) {
            var delay = "1"
            if (Constants.VERTICAL_BAR in stepKey) {
                val params = stepKey.split(Constants.VERTICAL_BAR)
                delay = params[1]
            }

            if (Constants.ASTERISK in stepKey) {
                val params = stepKey.split(Constants.ASTERISK)
                for (i in 0..params[1].toInt()) {
                    val keyEvent = EmulateKey()
                    keyEvent.key = params[0]
                    keyEvent.delay = delay
                    keyEvents.add(keyEvent)
                }
            } else {
                val keyEvent = EmulateKey()
                keyEvent.key = stepKey
                keyEvent.delay = delay
                keyEvents.add(keyEvent)
            }
        }

        val commands = ArrayList<String>()
        for (keyEvent in keyEvents) {
            Log.i(TAG, "send key: $keyEvent")
            val cmdKey = "input keyevent ${Constants.KEY_MAP[keyEvent.key]}"
            commands.add(cmdKey)
            commands.add("sleep ${keyEvent.delay}")
        }

        exeCmdEcho(commands, true)
    }

    fun tcpCapture(tcpdump: String): Process? {
        val captureCmd = "tcpdump -p -vv -s 0 -A 'tcp dst port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'" // >> /sdcard/capture.txt\n"
        val proc = getRootShell()

        var dos: DataOutputStream? = null
        try {
            dos = DataOutputStream(proc!!.outputStream)
            dos.write(captureCmd.toByteArray())
            dos.flush()

            val result = proc.waitFor()
            Log.i(TAG, "result: $result")
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            dos?.close()
        }

        object : Thread() {
            override fun run() {
                val insBr = BufferedReader(InputStreamReader(proc?.inputStream))
                try {
                    var lineStr: String? = null
                    do {
                        lineStr = insBr.readLine()
                        if (lineStr.isNullOrEmpty())
                            break
                        lineStr = trimLine(lineStr)
                        Log.i(TAG, " tcpdump echo line : $lineStr")
                    } while (true)
                } catch (e: IOException) {
                    e.printStackTrace()
                } finally {
                    try {
                        proc?.inputStream?.close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }.start()

        object : Thread() {
            override fun run() {
                val errBr = BufferedReader(InputStreamReader(proc?.errorStream))
                try {
                    var errLine: String? = null
                    do {
                        errLine = errBr.readLine()
                        if (errLine.isNullOrEmpty())
                            break
                        errLine = trimLine(errLine)
                        Log.i(TAG, " tcpdump error line : $errLine")
                    } while (true)
                } catch (e: IOException) {
                    e.printStackTrace()
                } finally {
                    try {
                        proc?.errorStream?.close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }.start()

        return proc
    }

    fun killTcpdump() {
        exeCmdEcho("killall", true)
//        exeCmdEcho("killall tcpdump", true)
    }

    fun resetApp(packageName: String) {
        exeCmdEcho("pm clear $packageName", true)
    }

    fun launchApp(launchInfo: String) {
        exeCmdEcho("am start -n $launchInfo", true)
    }

    fun stopApp(packageName: String) {
        exeCmdEcho("am force-stop $packageName", true)
    }

}