package com.jihe.punchnet.sdlan.utils

import android.os.Environment
import com.google.protobuf.ByteString
import com.google.protobuf.kotlin.toByteString
import com.jihe.punchnet.sdlan.config.SDLanConfig
import com.jihe.punchnet.sdlan.logs.TerminalLogger
import com.jihe.punchnet.sdlan.network.Mac
import java.io.File
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
import kotlin.experimental.and
import kotlin.experimental.inv
import kotlin.experimental.or
import kotlin.random.Random

object UniqueNodeID {
    var id: String = ""
    private var baseDir: String = ""

    fun setBaseDir(basedir: String) {
        baseDir = basedir
    }

    fun getUUID(): String {
        if (baseDir.length == 0) {
            baseDir = Environment.getExternalStorageDirectory().name
        }
        if (id.length == 0) {
            val dirpath = File(baseDir)
            dirpath.mkdirs()
            val f = File(baseDir, SDLanConfig.ID_FILENAME)
            if (f.exists()) {
                val value = f.readText()
                id = value
            } else {
                // file not exists
                val uuid = UUID.randomUUID()
                id = uuid.toString().replace("-", "")
                f.writeText(id)
            }
        }
        return id
    }
}

fun parseScoketAddressV4FromString(data: String): InetSocketAddress? {
    val pieces = data.split(":")
    if (pieces.size != 2) {
        TerminalLogger.errorf {"socker format error: $data"}
        return null
    }
    val host = Inet4Address.getByName(pieces[0])
    val port = pieces[1].toIntOrNull()

    if (port != null) {
        return InetSocketAddress(host, port)
    }
    TerminalLogger.errorf {"invalid port: ${port}"}
    return null
}

fun ipToString(ip: Int): String {
    val d1 = ip.ushr(24).and(0x000000ff).toUByte()
    val d2 = ip.ushr(16).and(0x000000ff).toUByte()
    val d3 = ip.ushr(8).and(0x000000ff).toUByte()
    val d4 = ip.and(0x000000ff).toUByte()
    return "$d1.$d2.$d3.$d4"
}

fun macToString(mac: Mac): String {
    return mac.joinToString(separator = ":") { it.toUByte().toString(16) }
}

fun generateRandomMAC(): Mac {
    var result = Random.nextBytes(6)
    val k: Byte = 0x01

    result[0] = result[0].and(k.inv())
    result[0] = result[0].or(0x02)
    return result.toByteString()
}

fun isMultiBroadcast(mac: ByteString): Boolean {
    return isBroadcast(mac) || isMulticast(mac) || isIP6Multicast(mac)
}

inline fun isBroadcast(mac: ByteString): Boolean {
    return mac.all {it == 0xff.toByte()}
}

inline fun isMulticast(mac: ByteString): Boolean {
    return mac.size()==6 && mac.byteAt(0) == 0x01.toByte()
            && mac.byteAt(1) == 0x00.toByte()
            && mac.byteAt(2) == 0x5e.toByte()
            && (mac.byteAt(3).and(0x80.toByte()) == 0.toByte())
}

inline fun isIP6Multicast(mac: ByteString): Boolean {
    return mac.size() == 6 && mac.byteAt(0) == 0x33.toByte()
            && mac.byteAt(1) == 0x33.toByte()
}


class EthHdr(
    var dest: ByteArray,
    var src: ByteArray,
    var etherType: Short,
)

class ArpHdr(
    val ethhdr: EthHdr,
    val hwtype: Short,
    val protocol: Short,
    val hwlen: Byte,
    val protolen: Byte,
    var opcode: Short,

    var shwaddr: ByteArray,
    var sipaddr: Int,
    var dhwaddr: ByteArray,
    var dipaddr: Int,
) {
    companion object {
        // 需要保证，长度够
        fun parseFrom(ethhdr: EthHdr, data: ByteBuffer): ArpHdr {
            val hwtype = data.getShort()
            val protocol = data.getShort()
            val hwlen = data.get()
            val protolen = data.get()
            val opcode = data.getShort()

            val shwaddr = ByteArray(6)
            data.get(shwaddr)
            val sipaddr = data.getInt()

            val dhwaddr = ByteArray(6)
            data.get(dhwaddr)
            val dipaddr = data.getInt()

            return ArpHdr(ethhdr, hwtype, protocol, hwlen, protolen, opcode, shwaddr, sipaddr, dhwaddr, dipaddr)
        }
    }

    fun marshalToBytes(): ByteBuffer {
        val buff = ByteBuffer.allocate(64).order(ByteOrder.BIG_ENDIAN)
        buff.put(ethhdr.dest)
        buff.put(ethhdr.src)
        buff.putShort(ethhdr.etherType)

        buff.putShort(hwtype)
        buff.putShort(protocol)
        buff.put(hwlen)
        buff.put(protolen)
        buff.putShort(opcode)

        buff.put(shwaddr)
        buff.putInt(sipaddr)

        buff.put(dhwaddr)
        buff.putInt(dipaddr)

        buff.flip()
        return buff
    }
}
