package com.agineek.agineek_pad_plugin

// import com.agineek.agineek_pad_plugin.Pad
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

// Debug for Unit Testing:
// OTS: Object to Stub
// MTS: Method to Stub
// Kotlin Playground doesn't know what Log.d(), import android, or Exception getMessage()

// TODO: need Unit Testing for this class
// TODO: need Player and Pad classes/Stubs in kotlin in order to test class.

// Code originally created by Joshua Mcledon on 2/21/18 in Java
// Code altered to fit Kotlin structures by Karanveer Sandhu on 9/17/21
// getters and setters are commented out.

class Sequence(
  var gameMode: String,
  var gameType: String,
  var pads: ArrayList<Pad>
  // var players: List<Player>    :commenting out player portion
) {
  // val for final properties
  // var for actual instance variables
  var sequence: Deque<Int> = ArrayDeque<Int>()
  var currentLitPad: Pad? = null
  var nextPad: Pad? = null
  var currentActiveUUID: String? = null
  var lastUsedColor: String? = null
  var currentPadLitIndex: Int = -1
  var lastPadLit: Int = -1
  var nextPadLit: Int = -1
  var UUIDs: ArrayList<String> = ArrayList<String>()

  init {
//    this.UUIDs = ArrayList<String>()
    /* commenting out player portion
    for (player in players) {
      player.resetStats() // MTS: Player
    }
    */

    for (pad in pads) {
      this.UUIDs.add(pad.getUuid()) // MTS: Pad I have a feeling this code might not work
    }
  }

  // static variables and methods must be within this block
  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
      }
    }
  }

//  var UUIDs: List<String> = ArrayList<String>()
  var byHits: Boolean = false

  // secondary constructor
  constructor(
    sequence: ArrayList<Int>,
    gameMode: String,
    gameType: String,
    pads: ArrayList<Pad> // OTS: Pad
    // players: List<Player>
  ) : this(gameMode, gameType, pads) // OTS: Player - removed player parameter
  {
    this.sequence = ArrayDeque<Int>(sequence)
//    this.UUIDs = ArrayList<String>()
    /*
    for (player in players) {
      player.resetStats() // MTS: Player
    }
    */
    for (pad in pads) {
      this.UUIDs.add(pad.getUuid()) // MTS I have a feeling this code might not work
    }
  }

  fun sendSeqToPads(seq: Sequence) {
    for (pad in pads) {
      pad.setSeq(seq) // MTS: Pad
    }
  }

//
//  fun setByHits(value: Boolean)
//  {
//    byHits = value
//  }
//
//  fun getByHits()
//  {
//    return byHits
//  }

  fun next() {
    if (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)
      }

      // nextPadLit = (int) (Math.random() * pads.size());
      Log.d(TAG, "Next light up #" + nextPadLit)

      // no need for break statement as it is an if else clause
    } else if (gameMode == "Sequence") {
      // remove from queue then add it back to the end for reuse
      currentPadLitIndex = sequence.poll()
      addToSequence(currentPadLitIndex)
      currentPadLitIndex-- // so no index out of bounds exceptions when getting from the list
      Log.d(TAG, "Light up #" + currentPadLitIndex)
    } else if (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
      addToSequence(currentPadLitIndex)
      addToFront(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.get(nextPadLit)
    } else {
      nextPad = null
    }

    try {
      currentLitPad = pads.get(currentPadLitIndex)
    } catch (e: Exception) {
      t2@ for (pad in pads) {
        try {
          currentLitPad = pad
          break@t2
        } catch (ex: Exception) {
          // Log.d(TAG, "Index out of bounce " + ex.getMessage()) // TODO: Might throw error (no I mean.)
        }
      }
    }

    // if we are in duo game type then alternate the lighting of the player's colors
    // for example player 1 can have red while player 2 can have green
  /* if (gameType.equalsIgnoreCase("Duo")) {
       if (lastUsedColor.equalsIgnoreCase(players.get(0).getHitColor())) {
           lastUsedColor = players.get(1).getHitColor();
           currentLitPad.turnOn();
           // this is for is its duo game type but memory game mode because we will also have a
           // specific color for "next pad to be lit" that's different from the normal
           // player lighting colors
           if (nextPad != null) {
               long startTime = System.currentTimeMillis();
               nextPad.turnOn();
           }
       } else {
           lastUsedColor = players.get(0).getHitColor();
           currentLitPad.turnOn();
           // this is for is its duo game type but memory game mode because we will also have a
           // specific color for "next pad to be lit" that's different from the normal
           // player lighting colors
           if (nextPad != null) {
               nextPad.turnOn(players.get(1).getMemoryHitColor());
           }
       }
     } else {*/
    // if not duo game type then just turn the pad on normally with no alternating colors
    // since it'll either be a single player mode (one color) or multiplayer where everyone
    // has their own color therefore they'll have their own game thread as well

    // no active pad at this point
    // Log.d(TAG, "GGGGAAAMMEEE TYPEeeeeeeeeeeeeeeeeeeee: " + gameType)

    for (pad in pads) {
      if (pad.isPadActive() == true) { // MTS: Pad
        pad.setActivePad(false) // MTS: Pad
      }
    }

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

      if (currentActiveUUID == currentLitPad?.getUuid()) { // MTS: Pad
        try {
          Thread.sleep(300)
        } catch (e: Exception) {} // I hope this doesn't generate an error
        // padOn.set(false);

        currentLitPad?.turnOn() // MTS: Pad
      } else {
        // padOn.set(false);
        currentLitPad?.turnOn()
      }
    } else {
      try {

        Thread.sleep(300)
      } catch (e: Exception) {} // Same thing here
      finally {
        Log.d(TAG, "currentActiveUUID from Secuence in the catch " + currentActiveUUID)
        Log.d(TAG, "currentLitPad.getUuid() from Secuence in the catch " + currentLitPad?.getUuid())
        currentActiveUUID = currentLitPad?.getUuid()
        this.next() // so the function is recursive
      }
    }
  }

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

//  Maybe some MTSs for these two functions to actualy detect the pads.
//  fun getPads()
//  {
//    return pads
//  }
//
//  fun getPlayers()
//  {
//    return players
//  }

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

//
//  fun getNextLitPad()
//  {
//    return nextPad
//  }
//
//  fun getGameMode()
//  {
//    return gameMode
//  }

  fun turnAllOff(uuid: String?) {
    for (pad in pads) {
      if (pad.getUuid() != uuid) { // MTS: Pad
        pad.turnOff()
      }
    }
  }

//  fun getUUIDs()
//  {
//    return UUIDs
//  }
//
//  fun getCurrentActiveUUID()
//  {
//    return currentActiveUUID;
//  }

  fun addToSequence(num: Int) {
    sequence.add(num)
  }

  fun removeFromSequence(num: Int) {
    sequence.remove(num)
  }

  fun addToFront(num: Int) {
    sequence.addFirst(num)
  }
}
