import Foundation
import OSLog
import Atomics
import Network
import SocketIO


public class Pad{
    public let tag: String = "Pad"
    public var logger = Logger()
    public var socket: NWConnection
    //private OutputStreamWriter mOutput; same as mInput, not needed; so far
    public var uuid: String!
    public let hitColor: String = ""
    public var alive: Bool = true
    public var connectionService: ConnectionService //Object from service/ConnectionService.java
    public var currentStatus: Int?
    public var order: Int?
    public var main: Thread?
    public var confirmTimer = ManagedAtomic<Int>(1)
    public var gameStarted = ManagedAtomic<Int>(1)
    public var offConfirmed = ManagedAtomic<Int>(0)
    public var offpad: Thread!
    public var mainT: Thread!
    public let padOff = ManagedAtomic<Bool>(false)
    public let padOffBool: Bool = false
    public let activePad = ManagedAtomic<Bool>(false)
    public let localActivePad = ManagedAtomic<Bool>(false)
    public var sequence = Sequence()
    public var data: String = ""

    init (socket: NWConnection, connectionService: ConnectionService) {
        self.socket = socket
        self.connectionService = connectionService
        uuid = nil
        currentStatus = nil
        order = nil
        main = nil
        offpad = nil
        mainT = nil
    }

    public enum COMMANDS: String {
        case ALIVE = "-1"
        case CONNECT = "1"
        case STARTGAME = "2"
        case LIGHTUP = "3"
        case LIGHTOFF = "4"
        case ENDGAME = "5"
        case PLAYER1ON = "7"
        case PLAYER2ON = "8"
        public func getCommand() -> String {
            return self.rawValue
        }
    }

    public func stateReport() {
        socket.stateUpdateHandler = { report in switch (report) {
        case .cancelled:
            self.logger.debug("\(self.tag), getting buffer reader, cancelled")
        case .ready:
            self.logger.debug("\(self.tag), getting buffer reader, ready")
        case .waiting(let error):
            self.logger.debug("\(self.tag), getting buffer reader, waiting with error = \(error.localizedDescription)")
        case .failed(let error):
            self.logger.debug("\(self.tag), getting buffer reader, failed, with error = \(error.localizedDescription)")
        default:
            self.logger.debug("\(self.tag), getting buffer reader, defaults")
        }
        }
    }

    @objc func offPadMain() {
        while !offpad.isCancelled {
            confirmTimer.wrappingIncrement(by: 1, ordering: AtomicUpdateOrdering.relaxed)
            Thread.sleep(forTimeInterval: 250)
            if confirmTimer.load(ordering: AtomicLoadOrdering.relaxed) > 4 {
                if gameStarted.load(ordering: AtomicLoadOrdering.relaxed) == 10 {
                    Sequence.padOn.store(false, ordering: AtomicStoreOrdering.relaxed)
                    if activePad.load(ordering: AtomicLoadOrdering.relaxed) == true {
                        connectionService.broadcast(pad: self, intent: "PAD_HIT", UUID: self.uuid)
                    }
                    gameStarted.store(0, ordering: AtomicStoreOrdering.relaxed)
                    padOff.store(true, ordering: AtomicStoreOrdering.relaxed)
                }
            }
        }
    }

    @objc func mainTMain(){
        sendMessage(message: COMMANDS.CONNECT.getCommand())
        stateReport()
        while ((socket.state == .ready) && !mainT.isCancelled) {
            receiveMessage()
            logger.debug("MESSAGE: \(self.data)")
            confirmTimer.store(0, ordering: AtomicStoreOrdering.relaxed)
            padOff.store(false, ordering: AtomicStoreOrdering.relaxed)
            if data.count > 2 {
                uuid = data
                logger.debug("UUID received \(self.uuid)")
            }
            if data.lowercased() == "-1" {
                alive = true
            } else if data.lowercased() == "0" {
                logger.debug("Shutdown")
                shutDown()
                Thread.current.cancel()
            } else if data.lowercased() == "1" {
                logger.debug("Connected received")
                connectionService.broadcast(pad: self, intent: "PAD_CONNECTION_CHANGE", UUID: uuid)
            } else if data.lowercased() == "9" {
                offConfirmed.store(1, ordering: AtomicStoreOrdering.relaxed)
            } else if data.lowercased() == "7" {

            } else if (data.lowercased() == "3") || (data.lowercased() == hitColor.lowercased()) {
                gameStarted.store(10, ordering: AtomicStoreOrdering.relaxed)
                Sequence.padOn.store(false, ordering: AtomicStoreOrdering.relaxed)
                logger.debug("Hit received")
                if localActivePad.load(ordering: AtomicLoadOrdering.relaxed) == true {
                    logger.debug("Hit is being send abroad")
                    connectionService.broadcast(pad: self, intent: "PAD_HIT", UUID: uuid)
                    localActivePad.store(false, ordering: AtomicStoreOrdering.relaxed)
                }
                else {
                    logger.debug("MISS is being send abroad")
                    connectionService.broadcast(pad: self, intent: "PAD_MISS", UUID: uuid)
                }
            }
        }
    }

    public func connect() {
        logger.debug("\(self.tag), calling connect")
        stateReport()
        offpad = Thread(target: self, selector: #selector(offPadMain), object: nil)
        offpad.start()
        mainT = Thread(target: self, selector: #selector(mainTMain), object: nil)
        mainT.start()
        if offpad.isExecuting == false || mainT.isExecuting == false {
            logger.debug("\(self.tag), Couldn't start thread Pad.java")
        }
    }

    @objc func pollingServiceMain(){
        while(alive){
            sendMessage(COMMANDS.ALIVE.getCommand())
            let startTime: Double = CACurrentMediaTime()
            alive = false
            while (10 * 1000 < CACurrentMediaTime() - startTime){

            }
        }
        shutDown()
        Thread.current.cancel()
    }

    public func handshakePollingService(){
        newThread = Thread(target: self, selector: #selector(pollingServiceMain), object: nil)
        newThread.start()
    }

    @objc func sendMessageMain(message: String){
        socket.send(content: message, completion: .contentProcessed){ error in
            if let error = error {
                logger.debug("\(self.tag), send message error")
                return
            }
        logger.debug("\(self.tag), sending message: \(message) Pad: \(UUID: self.uuid)")
        }
    }

    public func sendMessage(message: String){
        send = Thread(target: self, selector: #selector(sendMessageMain), object: message)
        send.start()

    }

    public func shutDown() {
        if socket.state == .ready {
            socket.cancel()
        }
        if connectionService.getActivePads().contains(self) {
            connectionService.removeActive(self)
        }
    }

    @objc func receiveMessageMain(){
        socket.receive(minimumIncompleteLength: 1, maximumLength: 65536) { (message, _, complete, error) in
            if let message = message, !message.isEmpty {
                self.data = String(data: message, encoding: .utf8)!
            }
            if complete {
                self.shutDown()
            } else if let error = error {
                self.logger.debug("There was an error when receiving a message, error: \(error.localizedDescription)")
            } else {
                self.receiveMessage()
            }
        }
    }

    public func receiveMessage() {
        let receive = Thread(target: self, selector: #selector(receiveMessageMain), object: nil)
        receive.start()
    }

    public func turnOn() {
        if Game.gameOver.load(ordering: AtomicLoadOrdering.relaxed) == false && Sequence.offFlag() == true {
            if sequence.getByHits() == false {
                sequence.turnAllOff(self.uuid)
                Thread.sleep(forTimeInterval: 100)
            }
            turnOn(sequence.getPlayers().get(0).getHitColor())
        } else {
            logger.debug("Could not turn on pd \(uuid)")
            logger.debug("Sequence pad on = \(Sequence.padOn.load(ordering: AtomicLoadOrdering.relaxed))")
        }
    }

    public func turnOn(color: String) {
        activePad.store(true, ordering: AtomicStoreOrdering.relaxed)
        localActivePad.store(true, ordering: AtomicStoreOrdering.relaxed)
        switch color {
        case "orange":
            sendMessage(message: COMMANDS.PLAYER1ON.getCommand())
            offConfirmed.store(0, ordering: AtomicStoreOrdering.relaxed)
            break
        case "blue":
            sendMessage(message: COMMANDS.PLAYER2ON.getCommand())
            offConfirmed.store(0, ordering: AtomicStoreOrdering.relaxed)
            break
        default:
            sendMessage(message: COMMANDS.LIGHTUP.getCommand())
            offConfirmed.store(0, ordering: AtomicStoreOrdering.relaxed)
            break
        }
    }

    public func turnOff(){
        sendMessage(message: COMMANDS.LIGHTOFF.getCommand())
        localActivePad.store(false, ordering: AtomicStoreOrdering.relaxed)
        Sequence.padOn.store(false, ordering: AtomicStoreOrdering.relaxed)
    }

    public func change_Message(msg: String){
        sendMessage(message: "change \(msg)")
    }
}
