//
//  Sequence.swift
//  Sequence
//
//  Created by Ziad Malik on 9/15/21.
//
import Foundation
import Collections
import Atomics
import OSLog

struct Sequence {
    private static var TAG: String = "Sequence"
    private var sequence: Deque<Int>
    public var pads = [Pad]()
    private var currentLitPad: Pad
    public var nextPad: Pad = nil
    public var gameMode, gameType: String
    public var currentActiveUUID: String
    private var lastUsedColor: String
    private var currentPadLit: Int = -1
    private var lastPadLit: Int = -1
    private var nextPadLit: Int = -1
    public var padOn = ManagedAtomic<Bool>(false)
    public var lock: NSLock
    public var uuids = [String]()
    public var byHits = false
    public var logger = Logger()

    init(
        sequence: Deque<Int>,
        gameMode: String,
        gameType: String,
        pads: [Pad],
        players: [Player]) {
        self.sequence = Deque<>(sequence)
        self.pads = pads
        self.gameMode = gameMode
        self.gameType = gameType
        uuids = [String]()
        players.forEach { player in player.resetStats() }
        pads.forEach { uuids.append(pad.getUuid()) }
    }

    public mutating func sendSeqToPads(seq: Sequence) {
        pads.forEach { pad in pad.setSeq(seq) }
    }

    // For Random Game Mode since we do not need to pass in a Sequence List
    init(gameMode: String, gameType: String, pads: [Pad], players: [Player]) {
        self.pads = pads
        self.gameMode = gameMode
        self.gameType = gameType
        uuids = [String]()
        players.forEach { player in player.resetStats() }
        pads.forEach { uuids.append(pad.getUuid()) }
    }

    public static func offFlag() -> Bool {
        lock.lock()
        logger.debug(\(TAG, privacy: .public), "PadOn Value ", \(padOn.get(), privacy: .public))
        if padon.get() == false {
            padOn.set(true)
            lock.unlock()
            return true
        } else {
            lock.unlock()
            return false
        }
    }

    public mutating func next() {
        switch gameMode {
        case "Random":
            if pads.size() > 2 {
                while true {
                    CurrentPadLit = Int.random(in: 1...pads.size())
                    if CurrentPadLit == LastPadLit {
                        continue
                    }
                    logger.debug(\(TAG), "Last Light Up #", \(LastPadLit))
                    logger.debug(\(TAG), "Light Up #", \(CurrentPadLit))
                    LastPadLit = CurrentPadLit
                    break
                }
            } else {
                CurrentPadLit = Int.random(in: 1...pads.size())
                logger.debug(\(TAG), "Light up #", \(CurrentPadLit))
            }
            logger.debug(\(TAG), "Next Light Up #", \(NextPadLit))
        case "Sequence":
            CurrentPadLit = sequence.poll()
            addToSequence(CurrentPadLit)
            CurrentPadLit -= 1
            logger.debug(\(TAG), "Light up #", \(CurrentPadLit))
        case "Memory":
            CurrentPadLit = sequence.poll()
            NextPadLit = sequence.poll()

            addToSequence(CurrentPadLit)
            addToFront(NextPadLit)
            CurrentPadLit -= 1
            NextPadLit -= 1

            logger.debug(\(TAG), "Light Up #", \(CurrentPadLit))
            logger.debug(\(TAG), "Next light up #", \(NextPadLit))
        default:
            <#code#>
        }

        if nextPadLit != -1 && currentPadLit != nextPadLit {
            nextPad = pads[nextPadLit]
        } else {
            nextPad = null
        }

        do {
            currentLitPad = pads[currentLitPad]
        } catch {
            for pad in pads.size() {
                do {
                    currentLitPad = pads[counter]
                    break
                } catch {
                    logger.debug(\(TAG), "Index out of bounce ")
                }
            }
        }
        logger.debug(\(TAG), "Game Type: ", \(gameType))

        pads.forEach { pad in if pad.isPadActive() == true {
                pad.setActivePad(false)
            }
        }
        if currentLitPad.get_pad_off() != true {
            logger.debug(\(TAG), "currentActiveUUID from Sequence ", \(currentActiveUUID))
            logger.debug(\(TAG), "currentLitPad.getUuid() from Sequence ", \(currentLitPad.getUuid()))
            currentActiveUUID = currentLitPad.getUuid()

            if currentActiveUUID == currentLitPad.getUuid() {
                do {
                    Thread.sleep(300)
                } catch {}
                // padOn.set(false);

                currentLitPad.turnOn()
            } else {
                // padOn.set(false);
                currentLitPad.turnOn()
            }
        } else {
            do {
                Thread.sleep(300)
            }
            logger.debug((TAG), "currentActiveUUID from Sequence in the catch ", \(currentActiveUUID))
            logger.debug((TAG), "currentLitPad.getUuid() from Sequence in the catch ", \(currentLitPad.getUuid()))
            currentActiveUUID = currentLitPad.getUuid()
            self.next()
        }
    }

    public func reset() {
        lastPadLit = -1
        currentPadLit = -1
        nextPadLit = -1
    }

    public func getCurrentLitPad() -> Pad {
        var temp: Pad = nil
        pads.forEach { pad in
        logger.debug(\(TAG), " from Sequence pad uudi " + pad.getUuid() + " is active = " + pad.isPadActive())
            if pad.isPadActive() == true {
                temp = pad
            }
        }
        if temp != nil {
            return temp
        } else {
            turnAllOff(currentLitPad.getUuid())
            currentLitPad.turnOn()
            return currentLitPad
        }
    }

    public func turnAllOff(uuid: String) {
        pads.forEach { pad in if pad.getUuid() != uuid {
            pad.turnOff()
        }}
    }

    public func addToSequence(num: Int) {
        sequence.append(num)
    }

    public func removeFromSequence(num: Int) {
        sequence.popFirst(num)
    }

    public func addToFront(num: Int) {
        sequence.prepend(num)
    }
}
