//
//  SessionController.swift
//  btprint
//
//  Created by woosimsystems on 2021/05/12.
//  Copyright © 2021 Woosim Systems. All rights reserved.
//

import Foundation
import ExternalAccessory

let WSSessionDataReceivedNotification = "WSDataReceivedNotification";

class SessionController: NSObject, EAAccessoryDelegate, StreamDelegate {
    
    static let shared = SessionController()
    var session: EASession?
    var accessory: EAAccessory?
    var outData: NSMutableData?
    var inData: NSMutableData?
    
    // open a session with the accessory and set up the input and output stream
    func openSession(_ accessory: EAAccessory, _ protocolString: String) -> Bool {
        accessory.delegate = self
        session = EASession(accessory: accessory, forProtocol: protocolString)
        if session != nil {
            self.accessory = accessory;
            session!.inputStream?.delegate = self
            session!.inputStream?.schedule(in: .current, forMode: .common)
            session!.inputStream?.open()
            session!.outputStream?.delegate = self
            session!.outputStream?.schedule(in: .current, forMode: .common)
            session!.outputStream?.open()
        }
        return session != nil
    }
    
    // close the session with the accessory
    func closeSession() {
        session?.inputStream?.close()
        session?.inputStream?.remove(from: .current, forMode: .common)
        session?.inputStream?.delegate = nil
        session?.outputStream?.close()
        session?.outputStream?.remove(from: .current, forMode: .common)
        session?.outputStream?.delegate = nil
        session = nil
        outData = nil
        inData = nil
        accessory = nil
    }
    
    // write data to the output stream
    func writeData(_ data: Data) {
        guard session != nil else { return }
        if outData == nil {
            outData = NSMutableData()
        }
        outData!.append(data)
        writeStream()
    }
    
    private func writeStream() {
        while (session?.outputStream?.hasSpaceAvailable ?? false) && (outData?.length ?? 0) > 0 {
            let bytePointer = outData!.bytes.bindMemory(to: UInt8.self, capacity: outData!.length)
            let bytesWritten = session!.outputStream!.write(bytePointer, maxLength: outData!.length)
            if bytesWritten == -1 {
                print("Write Error")
                break
            } else if bytesWritten > 0 {
                outData!.replaceBytes(in: NSMakeRange(0, bytesWritten), withBytes: nil, length: 0)
            }
        }
    }
    
    // read data from inData
    func readData(bytesToRead: Int) -> Data {
        var data = Data()
        if (inData?.length ?? 0) >= bytesToRead {
            let range = NSMakeRange(0, bytesToRead)
            data = inData!.subdata(with: range)
            inData?.replaceBytes(in: range, withBytes: nil, length: 0)
        }
        return data;
    }
    
    // get number of bytes read into local buffer
    func readBytesAvailable() -> Int {
        return inData?.length ?? 0
    }
    
    // MARK: - EAAcessoryDelegate
    func accessoryDidDisconnect(_ accessory: EAAccessory) {
        if (self.accessory?.connectionID ?? 0) == accessory.connectionID {
            closeSession()
        }
    }
    
    // MARK: - StreamDelegate
    func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
        switch eventCode {
        case Stream.Event.openCompleted:
            break
        case Stream.Event.hasBytesAvailable:
            readStream()
        case Stream.Event.hasSpaceAvailable:
            writeStream()
        case Stream.Event.errorOccurred:
            break
        case Stream.Event.endEncountered:
            break
        default:
            break
        }
    }
    
    // read data from input stream and send notification
    private func readStream() {
        let bufferSize = 512
        let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
        while session?.inputStream?.hasBytesAvailable ?? false {
            let bytesRead = session!.inputStream!.read(buffer, maxLength: bufferSize)
            if inData == nil {
                inData = NSMutableData()
            }
            inData!.append(buffer, length: bytesRead)
        }
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: WSSessionDataReceivedNotification), object: nil)
    }
}
