package com.agineek.agineek_pad_plugin

// import android.os.Message
import android.util.Log
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import java.lang.Exception
import java.net.Socket
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger

/**
 * BIG TODO: instance var uuid doesn't get initialized by the software (really hard to test things), (is it written by users in another
 * place? on firmware? on previous app?)
 * Proposal: make uuids in PadSequence once it initializes.
 **/

@kotlin.ExperimentalStdlibApi
class Pad(
    private val socket: Socket,
    private val plugin: AgineekPadPlugin
) {
    companion object {
        @JvmStatic private val Tag = "Pad"
    }

    public lateinit var uuid: String
    private lateinit var input: BufferedReader
    private lateinit var output: OutputStreamWriter

    private lateinit var sequence: PadSequence
    private lateinit var offPad: Thread
    private lateinit var mainPadThread: Thread
    private var hitColor: String = ""
    private var alive: AtomicBoolean = AtomicBoolean(true)
    private var currentStatus: Commands = Commands.Off
    private var order: Int = -1
    private val confirmTimer: AtomicInteger = AtomicInteger(1)
    private val gameStarted: AtomicInteger = AtomicInteger(1)
    private val offConfirmed: AtomicBoolean = AtomicBoolean(false)
    public val padOff: AtomicBoolean = AtomicBoolean(false)
    public val activePad: AtomicBoolean = AtomicBoolean(false)
    private val localActivePad: AtomicBoolean = AtomicBoolean(false)

    init {
        input = BufferedReader(InputStreamReader(socket.getInputStream()))
        output = OutputStreamWriter(socket.getOutputStream(), "UTF-8")
        uuid = "0" // TODO: Var might get initialized before an access so this placeholder is here.
    }

    public fun connect() {
        Log.d(Tag, "calling connect")
        Log.d(Tag, "getting buffer reader " + socket.isConnected)
        try {
            offPad = Thread {
                fun run() {
                    while (!Thread.currentThread().isInterrupted()) {
                        try {
                            confirmTimer.set(confirmTimer.get() + 1)
                            Thread.sleep(250)
                            if (confirmTimer.get() > 4) {
                                if (gameStarted.get() == 10) {
                                    PadSequence.padOn.set(false)
                                    if (activePad.get() == true) {
                                        plugin.broadcast(this@Pad, "PAD_HIT", uuid)
                                    }
                                    gameStarted.set(0)
                                    padOff.set(true)
                                }
                            }
                        } catch (e: Exception) {
                            shutdown()
                        }
                    }
                }
            }
            offPad.start()

            mainPadThread = Thread {
                fun run() {
                    try {
                        input = BufferedReader(InputStreamReader(socket.getInputStream()))
                        output = OutputStreamWriter(socket.getOutputStream(), "UTF-8")
                        sendMessage(Commands.Connect)
                        Log.d(Tag, "starting thread connect " + socket.isConnected)
                        while ((!socket.isClosed || !socket.isOutputShutdown && !mainPadThread.isInterrupted)) {
                            var data: String = ""

                            try {
                                data = input.readLine()
                                confirmTimer.set(0)
                                padOff.set(false)
                            } catch (e: Exception) {
                                Log.d(Tag, "The 2 writes failed Pad.kt")
                                data = "3"
                                shutdown()
                                break
                            }

                            if (data.length > 2) {
                                uuid = data
                                Log.d(Tag, "UUID received " + uuid)
                            }
                            if (data.equals("-1", true)) {
                                alive.set(true)
                            } else if (data.equals("0", true)) {
                                Log.d(
                                    Tag,
                                    "${Commands.Shutdown}"
                                ) // broadcast needs a String so i made is the Number of Shutdown
                                shutdown()
                                Thread.currentThread().interrupt()
                            } else if (data.equals("1", true)) {
                                Log.d(Tag, "Connected received")
                                plugin.broadcast(this@Pad, "PAD_CONNECTION_CHANGE", uuid)
                            } else if (data.equals("9", true)) {
                                offConfirmed.set(true)
                            } else if (data.equals("7", true)) {
                            } else if (data.equals("3", true)) {
                                gameStarted.set(10)
                                PadSequence.padOn.set(false)
                                Log.d(" ", "Hit received")
                                if (localActivePad.get()) {
                                    // broadcast needs a String so i made is the Number of Padhit
                                    plugin.broadcast(this@Pad, "PAD_HIT", uuid)
                                    localActivePad.set(false)
                                }
                            }
                        }
                    } catch (e: Exception) {
                        Log.d(Tag, "interrupt thread from Pad")
                        e.printStackTrace()
                        shutdown()
                        Thread.currentThread().interrupt()
                    }
                }
            }
            mainPadThread.start()
        } catch (e: Exception) {
            Log.d(Tag, "Couldn't start thread Pad.kt")
        }
    }

    public fun handshakePollingService() {
        Thread {
            @Override
            fun run() {
                while (alive.get()) {
                    sendMessage(Commands.Alive)
                    var startTime: Long = System.currentTimeMillis()
                    alive = AtomicBoolean(false)
                    // We probably need to make a better buffer
                    while (10 * 1000 < System.currentTimeMillis() - startTime) {
                    }
                }
                shutdown()
                Thread.currentThread().interrupt()
            }
        }.start()
    }

    public fun turnOn() {
        if (Game.gameOver.get() == false && PadSequence.offFlag() == true) {
            if (sequence.byHits == false) {
                sequence.turnAllOff(uuid)
                Thread.sleep(100)
            }
            val playerHitColor = plugin.playerColors[0] // this should be dynamic?
            turnOn(playerHitColor)
        } else {
            Log.d(Tag, "Could not turn on Pad " + uuid)
            Log.d(Tag, "Sequence pad on = " + PadSequence.padOn.get())
        }
    }

    public fun turnOn(color: String?) {
        activePad.set(true)
        localActivePad.set(true)
        if (color == "orange") {
            sendMessage(Commands.PlayerOneOn)
            offConfirmed.set(false) // Place one at the end instead of one in each if statement
        } else if (color == "blue") {
            sendMessage(Commands.PlayerTwoOn)
            offConfirmed.set(false)
        } else {
            sendMessage(Commands.LightOn)
            offConfirmed.set(false)
        }
    }

    public fun turnOff() {
        sendMessage(Commands.LightOff)
        localActivePad.set(false)
        PadSequence.padOn.set(false)
    }

    public fun shutdown() {
        try {
            if (socket.isConnected) {
                input.close() // TODO: causes lateinit property not initialized error
                output.close()
                socket.close()
            }
        } catch (e: Exception) {
            Log.e(Tag, "shut down error: ", e)
        }

        if (plugin.pads.contains(this@Pad)) {
            plugin.pads.remove(this@Pad)
        }
    }

    public fun startGame() {
        sendMessage(Commands.StartGame)
    }

    public fun endGame() {
        sendMessage(Commands.EndGame)
    }

    public fun setSeq(seq: PadSequence) {
        sequence = seq
    }

    private fun sendMessage(message: Commands) {
        if (socket.isConnected()) {
            Log.d(Tag, "Sending message $message to Pad $uuid")
            output.write("${message.ordinal}")
            output.flush()
        }
    }
}
