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): CmdResult  {
        val exeCommands = ArrayList<String>()
        exeCommands.add(cmd)
        return exeCmdEcho(exeCommands, root)
    }

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

        for (line in result.errorLines) {
            Log.i(TAG, "echo error line: $line")
        }

        return result
    }

    fun sendCommands(commands: ArrayList<String>, root: Boolean = false): CmdResult {
        val result = CmdResult(-1, "", "")
        var status = -1
        if (commands.isEmpty()) {
            return result
        }
        Log.i(TAG, "exe commands : $commands")
        var proc: Process? = null
        var successBufReader: BufferedReader? = null
        var errorBufReader: BufferedReader? = 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()

            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)
                    result.successLines.add(lineStr)
                Log.i(TAG, " success line echo : $lineStr")
            } while (true)


            do {
                lineStr = errorBufReader.readLine()
                if (lineStr.isNullOrEmpty())
                    break
                lineStr = trimLine(lineStr)
                if (lineStr !in commands)
                    result.errorLines.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:${result.errorLines} and status $status")
        if (result.errorLines.size > 0 && result.successLines.size <= 0)
            status = -1

        result.status = status

        return result
    }

    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(path: String): Process? {
        val captureCmd = "tcpdump -p -vv -s 0 -A 'tcp dst port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' >> $path"
        val proc = getRootShell()

        var dos: DataOutputStream? = null
        try {
            dos = DataOutputStream(proc!!.outputStream)
            dos.write(captureCmd.toByteArray())
            dos.writeBytes(LINE_BREAK)
            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() {
        val result = exeCmdEcho("killall", true)

        if (result.status != -1)
            exeCmdEcho("killall tcpdump", true)
        else {
            val psResult = exeCmdEcho("ps | grep tcpdump", true)
            for (line in psResult.successLines) {
                val arr = line.split(' ')
                val items = ArrayList<String>()
                arr.forEach {
                    if (!it.isNullOrEmpty())
                        items.add(it)
                }
                if (items.isNotEmpty()) {
                    exeCmdEcho("kill " + items[1], true)
                }
            }
        }
    }

    fun isAppActive(packageName: String): Boolean {
        val result = exeCmdEcho("dumpsys activity | grep mFocusedActivity", true)
        if (result.status == 0) {
            for (line in result.successLines) {
                if (line.contains(packageName))
                    return true
            }
        }

        return false
    }

    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)
    }

    class CmdResult {

        var status: Int = 0
        var successLines = ArrayList<String>()
        var errorLines = ArrayList<String>()

        constructor(status: Int) {
            this.status = status
        }

        constructor(result: Int, successMsg: String, errorMsg: String) {
            this.status = result
            if (!successMsg.isNullOrEmpty())
                this.successLines.add(successMsg)
            if (!errorMsg.isNullOrEmpty())
                this.errorLines.add(errorMsg)
        }
    }
}