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

#import "SessionController.h"

@interface SessionController ()

@property (nonatomic, strong) EASession *session;
@property (nonatomic, strong) NSMutableData *outData;
@property (nonatomic, strong) NSMutableData *inData;

@end

NSString *WSSessionDataReceivedNotification = @"WSSessionDataReceivedNotification";

@implementation SessionController

+ (SessionController *)sharedController
{
    static SessionController *sessionController = nil;
    if (sessionController == nil) {
        sessionController = [[SessionController alloc] init];
    }
    return sessionController;
}

// open the session with the accessory.
- (BOOL)openSessionForAccessory:(EAAccessory *)accessory withProtocolString:(NSString *)protocolString
{
    [accessory setDelegate:self];
    _session = [[EASession alloc] initWithAccessory:accessory forProtocol:protocolString];
    if (_session)
    {
        _accessory = accessory;
        [[_session inputStream] setDelegate:self];
        [[_session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [[_session inputStream] open];
        [[_session outputStream] setDelegate:self];
        [[_session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [[_session outputStream] open];
    }
    return (_session != nil);
}

// close the session with the accessory.
- (void)closeSession
{
    [[_session inputStream] close];
    [[_session inputStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[_session inputStream] setDelegate:nil];
    [[_session outputStream] close];
    [[_session outputStream] removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [[_session outputStream] setDelegate:nil];
    _session = nil;
    _outData = nil;
    _inData = nil;
    _accessory = nil;
}

// write data
- (void)writeData:(NSData *)data
{
    if (_session == nil) {
        return;
    }
    if (_outData == nil) {
        _outData = [[NSMutableData alloc] init];
    }
    [_outData appendData:data];
    [self writeStream];
}

// write data to the accessory while there is space available and data to write
- (void)writeStream
{
    while (([[_session outputStream] hasSpaceAvailable]) && ([_outData length] > 0)) {
        NSInteger bytesWritten = [[_session outputStream] write:[_outData bytes] maxLength:[_outData length]];
        if (bytesWritten == -1) {
            NSLog(@"write error");
            break;
        }
        else if (bytesWritten > 0) {
            [_outData replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
        }
    }
}

// read data
- (NSData *)readData:(NSUInteger)bytesToRead
{
    NSData *data = nil;
    if ([_inData length] >= bytesToRead) {
        NSRange range = NSMakeRange(0, bytesToRead);
        data = [_inData subdataWithRange:range];
        [_inData replaceBytesInRange:range withBytes:NULL length:0];
    }
    return data;
}

// get number of bytes read into local buffer
- (NSUInteger)readBytesAvailable
{
    return [_inData length];
}

#pragma mark EAAccessoryDelegate
- (void)accessoryDidDisconnect:(EAAccessory *)accessory
{
    if (_accessory && [accessory connectionID] == [_accessory connectionID]) {
        [self closeSession];
    }
}

#pragma mark NSStreamDelegateEventExtensions
// asynchronous NSStream handleEvent method
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    switch (eventCode) {
        case NSStreamEventNone:
            break;
        case NSStreamEventOpenCompleted:
            break;
        case NSStreamEventHasBytesAvailable:
            [self readStream];
            break;
        case NSStreamEventHasSpaceAvailable:
            [self writeStream];
            break;
        case NSStreamEventErrorOccurred:
            break;
        case NSStreamEventEndEncountered:
            break;
        default:
            break;
    }
}

// read data from input stream and send notification
- (void)readStream
{
    NSUInteger bufferSize = 512;
    uint8_t buf[bufferSize];
    while ([[_session inputStream] hasBytesAvailable]) {
        NSInteger bytesRead = [[_session inputStream] read:buf maxLength:bufferSize];
        if (_inData == nil) {
            _inData = [[NSMutableData alloc] init];
        }
        [_inData appendBytes:(void *)buf length:bytesRead];
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:WSSessionDataReceivedNotification
                                                        object:self
                                                      userInfo:nil];
}

@end
