package com.agineek.agineek_pad_plugin

import android.util.Log
import java.util.ArrayDeque
import java.util.ArrayList
import java.util.Deque
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.locks.Lock
import java.util.concurrent.locks.ReentrantLock
import kotlin.collections.LinkedHashSet

/**
 * PadSequence class
 *
 * @author Karanveer Sandhu
 * @since v1.0.0
 */
@kotlin.ExperimentalStdlibApi
class PadSequence(
    var gameMode: GameMode,
    var gameType: GameType,
    val pads: LinkedHashSet<Pad>,
    val players: LinkedHashSet<Player>
) {
    var sequence: Deque<Int> = ArrayDeque<Int>()
    var currentLitPad: Pad? = null
    var nextPad: Pad? = null
    var currentActiveUUID: String? = null
    var byHits: Boolean = false
    var lastUsedColor: String? = null
    var currentPadLitIndex: Int = -1
    var lastPadLit: Int = -1
    var nextPadLit: Int = -1
    var uuids: ArrayList<String> = ArrayList<String>()

    companion object {
        val TAG: String = "Sequence"
        var padOn: AtomicBoolean = AtomicBoolean(false)
        var lock: Lock = ReentrantLock()

        // implementing the lock
        // TODO: may not return value as static methods are not appreciated in Kotlin
        @JvmStatic
        fun offFlag(): Boolean {
            lock.lock()
            Log.d(TAG, "PadOn Value  " + padOn.get())
            if (padOn.get() == false) {
                padOn.set(true)
                lock.unlock()
                return true
            } else {
                lock.unlock()
                return false
            }
        }
    }

    init {
        for (pad in pads) {
            this.uuids.add(pad.uuid)
        }
        for (player in players) {
            player.reset()
        }
    }

    // secondary constructor
    constructor(
        gameMode: GameMode,
        gameType: GameType,
        pads: LinkedHashSet<Pad>,
        players: LinkedHashSet<Player>,
        sequence: ArrayList<Int>
    ) : this(gameMode, gameType, pads, players) {
        this.sequence = ArrayDeque<Int>(sequence)

//        for (pad in pads) {
//            this.uuids.add(pad.uuid)
//        }
    }

    fun sendSeqToPads(seq: PadSequence) {
        for (pad in pads) {
            pad.setSeq(seq)
        }
    }

    fun next() {
        if (gameMode == GameMode.Random) {
            if (pads.size > 2) {
                // check to make sure the same pad doesn't light up twice in a row
                t1@ while (true) {
                    currentPadLitIndex = (Math.random() * pads.size).toInt()
                    if (currentPadLitIndex == lastPadLit) continue@t1
                    Log.d(TAG, "Last Light up #" + lastPadLit)
                    Log.d(TAG, "Light up #" + currentPadLitIndex)
                    lastPadLit = currentPadLitIndex
                    break@t1
                }
            } else {
                currentPadLitIndex = (Math.random() * pads.size).toInt()
                Log.d(TAG, "Light up #" + currentPadLitIndex)
            }

            Log.d(TAG, "Next light up #" + nextPadLit)
        } else if (gameMode == GameMode.Sequence) {
            // remove from queue then add it back to the end for reuse
            currentPadLitIndex = sequence.poll()
            sequence.add(currentPadLitIndex)
            currentPadLitIndex-- // so no index out of bounds exceptions when getting from the list
            Log.d(TAG, "Light up #" + currentPadLitIndex)
        } else if (gameMode == GameMode.Memory) {
            // remove the first from the queue then the second
            currentPadLitIndex = sequence.poll()
            nextPadLit = sequence.poll()
            // add the first removed to the end but add the second removed
            // to the front of the queue because it's suppose to be the next pad to be lit
            sequence.add(currentPadLitIndex)
            sequence.addFirst(nextPadLit)
            currentPadLitIndex-- // so no index out of bounds exceptions when getting from the list
            nextPadLit-- // so no index out of bounds exceptions when getting from the list

            Log.d(TAG, "Light up #" + currentPadLitIndex)
            Log.d(TAG, "Next light up #" + nextPadLit)
        }

        // if the first removed and second removed are not the same number then we need to
        // light the nextPad otherwise it's the same pad to be lit next so no need to try and light it twice
        if (nextPadLit != -1 && currentPadLitIndex != nextPadLit) {
            nextPad = pads.elementAt(nextPadLit)
        } else {
            nextPad = null
        }

        try {
            currentLitPad = pads.elementAt(currentPadLitIndex)
        } catch (e: Exception) {
            t2@ for (pad in pads) {
                try {
                    currentLitPad = pad
                    break@t2
                } catch (ex: Exception) {
                    Log.d(TAG, "Index out of bounds ", ex)
                }
            }
        }

        Log.d(TAG, "Game Type: " + gameType)

        for (pad in pads) {
            if (pad.activePad.get() == true) {
                pad.activePad.set(false)
            }

            if (currentLitPad?.padOff?.get() != true) { // MTS: Pad
                Log.d(TAG, "currentActiveUUID from Sequence " + currentActiveUUID)
                Log.d(TAG, "currentLitPad.getUuid() from Secuence " + currentLitPad?.uuid)
                currentActiveUUID = currentLitPad?.uuid

                if (currentActiveUUID == currentLitPad?.uuid) { // MTS: Pad
                    try {
                        Thread.sleep(300)
                    } catch (e: Exception) {
                        Log.e(TAG, "An error occured: ", e)
                    }

                    padOn.set(false)

                    currentLitPad?.turnOn()
                } else {
                    currentLitPad?.turnOn()
                }
            } else {
                try {
                    Thread.sleep(300)
                } catch (e: Exception) {
                    Log.e(TAG, "An error occured: ", e)
                } finally {
                    Log.d(TAG, "currentActiveUUID from Secuence in the catch " + currentActiveUUID)
                    Log.d(TAG, "currentLitPad.getUuid() from Secuence in the catch " + currentLitPad?.uuid)
                    currentActiveUUID = currentLitPad?.uuid
                    this.next() // so the function is recursive
                }
            }
        }
    }

    public fun reset() {
        lastPadLit = -1
        currentPadLitIndex = -1
        nextPadLit = -1
    }

    public fun getTheCurrentLitPad(): Pad? {
        var temp: Pad? = null
        for (pad in pads) {
            Log.d(TAG, " from Sequence pad uudi " + pad.uuid + " is active = " + pad.activePad.get())
            if (pad.activePad.get() == true) {
                temp = pad
            }
        }
        if (temp != null) {
            return temp
        } else {
            turnAllOff(currentLitPad?.uuid)
            currentLitPad?.turnOn()
            return currentLitPad
        }
    }

    public fun turnAllOff(uuid: String?) {
        for (pad in pads) {
            if (pad.uuid != uuid) {
                pad.turnOff()
            }
        }
    }
}
