//
//  RNStarPrnt.mm
//


#import "RNStarPrnt.h"
#import <StarIO/SMPort.h>
#import <StarIO_Extension/StarIoExt.h>
#import <StarIO_Extension/StarIoExtManager.h>
#import "Communication.h"

@implementation RNStarPrnt

bool RNStarPrnt_hasListeners;

- (dispatch_queue_t)methodQueue {
    return dispatch_queue_create("net.axelmarciano.rnstarprnt", DISPATCH_QUEUE_SERIAL);
}

+ (BOOL)requiresMainQueueSetup {
    return YES;
}

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents {
    return @[@"starPrntData"];
}

// Called when the first listener is added.
- (void)startObserving {
    RNStarPrnt_hasListeners = YES;
    // Set up any upstream listeners or background tasks as needed
}

// Called when the last listener is removed or on dealloc.
- (void)stopObserving {
    RNStarPrnt_hasListeners = NO;
    // Remove upstream listeners or stop background tasks as needed
}

#pragma mark - React Native Methods

RCT_REMAP_METHOD(portDiscovery,
                 portType:(NSString *)portType
                 findPrintersWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    NSMutableArray *info = [[NSMutableArray alloc] init];
    NSError *error = nil;
    
    // Discover printers using Bluetooth
    if ([portType isEqualToString:@"All"] || [portType isEqualToString:@"Bluetooth"]) {
        NSArray *btPortInfoArray = [SMPort searchPrinter:@"BT:" :&error];
        if (error) {
            NSLog(@"Error searching for Bluetooth printers: %@", error);
        }
        for (PortInfo *p in btPortInfoArray) {
            [info addObject:[self portInfoToDictionary:p]];
        }
    }
    
    // Discover printers on LAN
    if ([portType isEqualToString:@"All"] || [portType isEqualToString:@"LAN"]) {
        error = nil;
        NSArray *lanPortInfoArray = [SMPort searchPrinter:@"TCP:" :&error];
        if (error) {
            NSLog(@"Error searching for LAN printers: %@", error);
        }
        for (PortInfo *p in lanPortInfoArray) {
            [info addObject:[self portInfoToDictionary:p]];
        }
    }
    
    // Discover printers using BluetoothLE
    if ([portType isEqualToString:@"All"] || [portType isEqualToString:@"BluetoothLE"]) {
        error = nil;
        NSArray *blePortInfoArray = [SMPort searchPrinter:@"BLE:" :&error];
        if (error) {
            NSLog(@"Error searching for BluetoothLE printers: %@", error);
        }
        for (PortInfo *p in blePortInfoArray) {
            [info addObject:[self portInfoToDictionary:p]];
        }
    }
    
    // Discover printers on USB
    if ([portType isEqualToString:@"All"] || [portType isEqualToString:@"USB"]) {
        error = nil;
        NSArray *usbPortInfoArray = [SMPort searchPrinter:@"USB:" :&error];
        if (error) {
            NSLog(@"Error searching for USB printers: %@", error);
        }
        for (PortInfo *p in usbPortInfoArray) {
            [info addObject:[self portInfoToDictionary:p]];
        }
    }
    
    resolve(info);
}

RCT_REMAP_METHOD(checkStatus,
                 portName:(NSString *)portName
                 emulation:(NSString *)emulation
                 checkStatusWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    SMPort *port = nil;
    StarPrinterStatus_2 status;
    NSString *portSettings = [self getPortSettingsOption:emulation];
    NSError *error = nil;
    
    @try {
        // Open the port using the modern API with an error pointer
        port = [SMPort getPort:portName :portSettings :10000 :&error];
        if (port == nil || error) {
            NSLog(@"Error opening port %@: %@", portName, error);
            @throw error;
        }
        
        // Pause to avoid Bluetooth communication issues on iOS 11+
        NSOperatingSystemVersion version = {11, 0, 0};
        BOOL isOSVer11OrLater = [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:version];
        if (isOSVer11OrLater && [portName.uppercaseString hasPrefix:@"BT:"]) {
            [NSThread sleepForTimeInterval:0.2];
        }
        
        // Retrieve the printer status using the modern API with an error pointer
        if (![port getParsedStatus:&status :2 :&error]) {
            NSLog(@"Error retrieving status for port %@: %@", portName, error);
            @throw error;
        }
        
        // Get firmware information
        NSDictionary *firmwareInformation = nil;
        @try {
            firmwareInformation = [port getFirmwareInformation:&error];
            if (firmwareInformation == nil) {
                firmwareInformation = @{};
            }
        }
        @catch (NSException *exception) {
            NSLog(@"Unable to retrieve firmware information: %@", exception);
            firmwareInformation = @{};
        }
        
        NSDictionary *statusDictionary = [self portStatusToDictionary:status :firmwareInformation];
        NSLog(@"Printer status successfully retrieved");
        resolve(statusDictionary);
    }
    @catch (NSError *exception) {
        NSLog(@"Exception while retrieving port status: %@", exception);
        reject(@"status_error", @"Error getting printer status", exception);
    }
    @finally {
        if (port != nil) {
            [SMPort releasePort:port];
            NSLog(@"Port released");
        }
    }
}

RCT_REMAP_METHOD(connect,
                 portName:(NSString *)portName
                 emulation:(NSString *)emulation
                 hasBarcodeReader:(nonnull NSNumber *)hasBarcodeReader
                 connectWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    NSString *portSettings = [self getPortSettingsOption:emulation];
    
    if (portName != nil && portName != (id)[NSNull null]) {
        if ([hasBarcodeReader isEqual:@(YES)]) {
            _printerManager = [[StarIoExtManager alloc] initWithType:StarIoExtManagerTypeWithBarcodeReader
                                                            portName:portName
                                                        portSettings:portSettings
                                                     ioTimeoutMillis:10000];
        } else {
            _printerManager = [[StarIoExtManager alloc] initWithType:StarIoExtManagerTypeStandard
                                                            portName:portName
                                                        portSettings:portSettings
                                                     ioTimeoutMillis:10000];
        }
        _printerManager.delegate = self;
    }
    
    if ([_printerManager connect] == NO) {
        NSDictionary *userInfo = @{
            NSLocalizedDescriptionKey: NSLocalizedString(@"Failed to open port", nil),
            NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"", nil),
            NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Check that the printer is online", nil)
        };
        NSError *error = [[NSError alloc] initWithDomain:@"StarPrntErrorDomain" code:-1 userInfo:userInfo];
        NSLog(@"Failed to connect to printer");
        reject(@"Fail to Open Port", @"", error);
    } else {
        BOOL result = YES;
        NSMutableDictionary *resultMessage = [[NSMutableDictionary alloc] init];
        [resultMessage setObject:@(result == SM_TRUE) forKey:@"result"];
        [resultMessage setObject:@"Send Commands" forKey:@"title"];
        [resultMessage setObject:@"Printer Connected!" forKey:@"message"];
        NSLog(@"Printer connected successfully");
        resolve(resultMessage);
    }
}

RCT_REMAP_METHOD(disconnect,
                 disconnectWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    if (_printerManager != nil && _printerManager.port != nil) {
        if ([_printerManager disconnect] == NO) {
            NSDictionary *userInfo = @{
                NSLocalizedDescriptionKey: NSLocalizedString(@"Communication Error", nil),
                NSLocalizedFailureReasonErrorKey: NSLocalizedString(@"", nil),
                NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Check that the printer is online", nil)
            };
            NSError *error = [[NSError alloc] initWithDomain:@"StarPrntErrorDomain" code:-1 userInfo:userInfo];
            NSLog(@"Error disconnecting printer");
            reject(@"Communication Error", @"", error);
        } else {
            BOOL result = YES;
            NSMutableDictionary *resultMessage = [[NSMutableDictionary alloc] init];
            [resultMessage setObject:@(result == SM_TRUE) forKey:@"result"];
            [resultMessage setObject:@"Send Commands" forKey:@"title"];
            [resultMessage setObject:@"Printer Disconnected!" forKey:@"message"];
            NSLog(@"Printer disconnected successfully");
            resolve(resultMessage);
        }
    }
}

RCT_REMAP_METHOD(print,
                 portName:(NSString *)portName
                 emulation:(NSString *)emulation
                 printCommands:(NSArray *)printCommands
                 sendPrintCommandWithResolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
    NSString *portSettings = [self getPortSettingsOption:emulation];
    StarIoExtEmulation Emulation = [self getEmulation:emulation];
    ISCBBuilder *builder = [StarIoExt createCommandBuilder:Emulation];
    
    [builder beginDocument];
    [self appendCommands:builder printCommands:printCommands];
    [builder endDocument];
    // Document ready for printing
    
    if (portName != nil && portName != (id)[NSNull null]) {
        [Communication sendCommands:[builder.commands copy]
                           portName:portName
                       portSettings:portSettings
                            timeout:10000
                  completionHandler:^(BOOL result, NSString *title, NSString *message) {
                      if (result == YES) {
                          NSMutableDictionary *resultMessage = [[NSMutableDictionary alloc] init];
                          [resultMessage setObject:@(result == SM_TRUE) forKey:@"result"];
                          [resultMessage setObject:title forKey:@"title"];
                          [resultMessage setObject:message forKey:@"message"];
                          NSLog(@"Print command sent successfully (direct connection)");
                          resolve(resultMessage);
                      } else {
                          NSDictionary *userInfo = @{
                              NSLocalizedDescriptionKey: NSLocalizedString(title, nil),
                              NSLocalizedFailureReasonErrorKey: NSLocalizedString(message, nil),
                              NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Check that the printer is online and connected", nil)
                          };
                          NSError *error = [[NSError alloc] initWithDomain:@"StarPrntErrorDomain" code:-1 userInfo:userInfo];
                          NSLog(@"Error sending print command: %@", message);
                          reject(title, message, error);
                      }
                  }];
    } else { // Use StarIOExtManager to send commands to the connected printer
        [Communication sendCommands:[builder.commands copy]
                               port:_printerManager.port
                  completionHandler:^(BOOL result, NSString *title, NSString *message) {
                      if (result == YES) {
                          NSMutableDictionary *resultMessage = [[NSMutableDictionary alloc] init];
                          [resultMessage setObject:@(result == SM_TRUE) forKey:@"result"];
                          [resultMessage setObject:title forKey:@"title"];
                          [resultMessage setObject:message forKey:@"message"];
                          NSLog(@"Print command sent successfully (via StarIOExtManager)");
                          resolve(resultMessage);
                      } else {
                          NSDictionary *userInfo = @{
                              NSLocalizedDescriptionKey: NSLocalizedString(title, nil),
                              NSLocalizedFailureReasonErrorKey: NSLocalizedString(message, nil),
                              NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Check that the printer is online and connected", nil)
                          };
                          NSError *error = [[NSError alloc] initWithDomain:@"StarPrntErrorDomain" code:-1 userInfo:userInfo];
                          NSLog(@"Error sending print command via StarIOExtManager: %@", message);
                          reject(title, message, error);
                      }
                  }];
    }
}

#pragma mark - Printer Events

- (void)didPrinterCoverOpen {
    [self sendData:@"printerCoverOpen" data:nil];
}

- (void)didPrinterCoverClose {
    [self sendData:@"printerCoverClose" data:nil];
}

- (void)didPrinterImpossible {
    [self sendData:@"printerImpossible" data:nil];
}

- (void)didPrinterOnline {
    [self sendData:@"printerOnline" data:nil];
}

- (void)didPrinterOffline {
    [self sendData:@"printerOffline" data:nil];
}

- (void)didPrinterPaperEmpty {
    [self sendData:@"printerPaperEmpty" data:nil];
}

- (void)didPrinterPaperNearEmpty {
    [self sendData:@"printerPaperNearEmpty" data:nil];
}

- (void)didPrinterPaperReady {
    [self sendData:@"printerPaperReady" data:nil];
}

#pragma mark - Cash Drawer Events

- (void)didCashDrawerOpen {
    [self sendData:@"cashDrawerOpen" data:nil];
}

- (void)didCashDrawerClose {
    [self sendData:@"cashDrawerClose" data:nil];
}

- (void)didBarcodeReaderImpossible {
    [self sendData:@"barcodeReaderImpossible" data:nil];
}

- (void)didBarcodeReaderConnect {
    [self sendData:@"barcodeReaderConnect" data:nil];
}

- (void)didBarcodeReaderDisconnect {
    [self sendData:@"barcodeReaderDisconnect" data:nil];
}

- (void)didBarcodeDataReceive:(NSData *)data {
    NSMutableString *text = [NSMutableString stringWithString:@""];
    const uint8_t *p = (const uint8_t *)[data bytes];
    for (int i = 0; i < data.length; i++) {
        uint8_t ch = *(p + i);
        if (ch >= 0x20 && ch <= 0x7f) {
            [text appendFormat:@"%c", (char)ch];
        }
        // Ignore carriage return (0x0d)
    }
    [self sendData:@"barcodeDataReceive" data:text];
}

- (void)sendData:(NSString *)dataType data:(NSString *)data {
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:dataType forKey:@"dataType"];
    if (data != nil) {
        [dict setObject:data forKey:@"data"];
    }
    if (RNStarPrnt_hasListeners) {
        [self sendEventWithName:@"starPrntData" body:dict];
    }
}

#pragma mark - Utilities

- (NSMutableDictionary *)portInfoToDictionary:(PortInfo *)portInfo {
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:portInfo.portName forKey:@"portName"];
    [dict setObject:portInfo.macAddress forKey:@"macAddress"];
    [dict setObject:portInfo.modelName forKey:@"modelName"];
    return dict;
}

- (NSString *)getPortSettingsOption:(NSString *)emulation {
    NSString *portSettings = [NSString string];
    
    if ([emulation isEqualToString:@"EscPosMobile"]) {
        portSettings = [@"mini" stringByAppendingString:portSettings];
    } else if ([emulation isEqualToString:@"EscPos"]) {
        portSettings = [@"escpos" stringByAppendingString:portSettings];
    } else if ([emulation isEqualToString:@"StarPRNT"] || [emulation isEqualToString:@"StarPRNTL"]) {
        portSettings = [@"Portable;l" stringByAppendingString:portSettings];
    }
    return portSettings;
}

- (NSMutableDictionary *)portStatusToDictionary:(StarPrinterStatus_2)status :(NSDictionary *)firmwareInformation {
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:@(status.coverOpen == SM_TRUE) forKey:@"coverOpen"];
    [dict setObject:@(status.offline == SM_TRUE) forKey:@"offline"];
    [dict setObject:@(status.overTemp == SM_TRUE) forKey:@"overTemp"];
    [dict setObject:@(status.cutterError == SM_TRUE) forKey:@"cutterError"];
    [dict setObject:@(status.receiptPaperEmpty == SM_TRUE) forKey:@"receiptPaperEmpty"];
    [dict addEntriesFromDictionary:firmwareInformation];
    return dict;
}

- (StarIoExtEmulation)getEmulation:(NSString *)emulation {
    if ([emulation isEqualToString:@"StarPRNT"]) return StarIoExtEmulationStarPRNT;
    else if ([emulation isEqualToString:@"StarPRNTL"]) return StarIoExtEmulationStarPRNTL;
    else if ([emulation isEqualToString:@"StarLine"]) return StarIoExtEmulationStarLine;
    else if ([emulation isEqualToString:@"StarGraphic"]) return StarIoExtEmulationStarGraphic;
    else if ([emulation isEqualToString:@"EscPos"]) return StarIoExtEmulationEscPos;
    else if ([emulation isEqualToString:@"EscPosMobile"]) return StarIoExtEmulationEscPosMobile;
    else if ([emulation isEqualToString:@"StarDotImpact"]) return StarIoExtEmulationStarDotImpact;
    else return StarIoExtEmulationStarLine;
}

- (void)appendCommands:(ISCBBuilder *)builder printCommands:(NSArray *)printCommands {
    NSStringEncoding encoding = NSASCIIStringEncoding;
    
    for (id command in printCommands) {
        if ([command valueForKey:@"appendInternational"])
            [builder appendInternational:[self getInternational:[command valueForKey:@"appendInternational"]]];
        else if ([command valueForKey:@"appendCharacterSpace"])
            [builder appendCharacterSpace:[[command valueForKey:@"appendCharacterSpace"] intValue]];
        else if ([command valueForKey:@"appendEncoding"])
            encoding = [self getEncoding:[command valueForKey:@"appendEncoding"]];
        else if ([command valueForKey:@"appendCodePage"])
            [builder appendCodePage:[self getCodePageType:[command valueForKey:@"appendCodePage"]]];
        else if ([command valueForKey:@"append"])
            [builder appendData:[[command valueForKey:@"append"] dataUsingEncoding:encoding]];
        else if ([command valueForKey:@"appendRaw"])
            [builder appendRawData:[[command valueForKey:@"appendRaw"] dataUsingEncoding:encoding]];
        else if ([command valueForKey:@"appendEmphasis"])
            [builder appendDataWithEmphasis:[[command valueForKey:@"appendEmphasis"] dataUsingEncoding:encoding]];
        else if ([command valueForKey:@"enableEmphasis"])
            [builder appendEmphasis:[[command valueForKey:@"enableEmphasis"] boolValue]];
        else if ([command valueForKey:@"appendInvert"])
            [builder appendDataWithInvert:[[command valueForKey:@"appendInvert"] dataUsingEncoding:encoding]];
        else if ([command valueForKey:@"enableInvert"])
            [builder appendInvert:[[command valueForKey:@"enableInvert"] boolValue]];
        else if ([command valueForKey:@"appendUnderline"])
            [builder appendDataWithUnderLine:[[command valueForKey:@"appendUnderline"] dataUsingEncoding:encoding]];
        else if ([command valueForKey:@"enableUnderline"])
            [builder appendUnderLine:[[command valueForKey:@"enableUnderline"] boolValue]];
        else if ([command valueForKey:@"appendLineFeed"])
            [builder appendLineFeed:[[command valueForKey:@"appendLineFeed"] intValue]];
        else if ([command valueForKey:@"appendUnitFeed"])
            [builder appendUnitFeed:[[command valueForKey:@"appendUnitFeed"] intValue]];
        else if ([command valueForKey:@"appendLineSpace"])
            [builder appendLineSpace:[[command valueForKey:@"appendLineSpace"] intValue]];
        else if ([command valueForKey:@"appendFontStyle"])
            [builder appendFontStyle:[self getFont:[command valueForKey:@"appendFontStyle"]]];
        else if ([command valueForKey:@"appendCutPaper"])
            [builder appendCutPaper:[self getCutPaperAction:[command valueForKey:@"appendCutPaper"]]];
        else if ([command valueForKey:@"openCashDrawer"])
            [builder appendPeripheral:[self getPeripheralChannel:[command valueForKey:@"openCashDrawer"]]];
        else if ([command valueForKey:@"appendBlackMark"])
            [builder appendBlackMark:[self getBlackMarkType:[command valueForKey:@"appendBlackMark"]]];
        else if ([command valueForKey:@"appendBytes"]) {
            NSMutableArray *byteArray = [command valueForKey:@"appendBytes"];
            int count = (int)[byteArray count];
            unsigned char buffer[count + 1];
            for (int i = 0; i < count; i++) {
                buffer[i] = [[byteArray objectAtIndex:i] unsignedCharValue];
            }
            [builder appendBytes:buffer length:sizeof(buffer)-1];
        }
        else if ([command valueForKey:@"appendRawBytes"]) {
            NSMutableArray *rawByteArray = [command valueForKey:@"appendRawBytes"];
            int rawCount = (int)[rawByteArray count];
            unsigned char rawBuffer[rawCount + 1];
            for (int i = 0; i < rawCount; i++) {
                rawBuffer[i] = [[rawByteArray objectAtIndex:i] unsignedCharValue];
            }
            [builder appendRawBytes:rawBuffer length:sizeof(rawBuffer)-1];
        }
        else if ([command valueForKey:@"appendAbsolutePosition"]) {
            if ([command valueForKey:@"data"])
                [builder appendDataWithAbsolutePosition:[[command valueForKey:@"data"] dataUsingEncoding:encoding]
                                               position:[[command valueForKey:@"appendAbsolutePosition"] intValue]];
            else
                [builder appendAbsolutePosition:[[command valueForKey:@"appendAbsolutePosition"] intValue]];
        }
        else if ([command valueForKey:@"appendAlignment"]) {
            if ([command valueForKey:@"data"])
                [builder appendDataWithAlignment:[[command valueForKey:@"data"] dataUsingEncoding:encoding]
                                          position:[self getAlignment:[command valueForKey:@"appendAlignment"]]];
            else
                [builder appendAlignment:[self getAlignment:[command valueForKey:@"appendAlignment"]]];
        }
        else if ([command valueForKey:@"appendHorizontalTabPosition"]) {
            NSArray<NSNumber *> *tabPositionArray = [command valueForKey:@"appendHorizontalTabPosition"];
            if (tabPositionArray != nil && tabPositionArray != (id)[NSNull null])
                [builder appendHorizontalTabPosition:tabPositionArray];
        }
        else if ([command valueForKey:@"appendMultiple"]) {
            int width = ([[command valueForKey:@"width"] intValue]) ? [[command valueForKey:@"width"] intValue] : 2;
            int height = ([[command valueForKey:@"height"] intValue]) ? [[command valueForKey:@"height"] intValue] : 2;
            [builder appendDataWithMultiple:[[command valueForKey:@"appendMultiple"] dataUsingEncoding:encoding] width:width height:height];
        }
        else if ([command valueForKey:@"enableMultiple"]) {
            int width = ([[command valueForKey:@"width"] intValue]) ? [[command valueForKey:@"width"] intValue] : 1;
            int height = ([[command valueForKey:@"height"] intValue]) ? [[command valueForKey:@"height"] intValue] : 1;
            if ([[command valueForKey:@"enableMultiple"] boolValue] == YES) {
                [builder appendMultiple:width height:height];
            } else {
                [builder appendMultiple:1 height:1];
            }
        }
        else if ([command valueForKey:@"appendLogo"]) {
            if ([command valueForKey:@"logoSize"])
                [builder appendLogo:[self getLogoSize:[command valueForKey:@"logoSize"]]
                           number:[[command valueForKey:@"appendLogo"] intValue]];
            else
                [builder appendLogo:SCBLogoSizeNormal number:[[command valueForKey:@"appendLogo"] intValue]];
        }
        else if ([command valueForKey:@"appendBarcode"]) {
            SCBBarcodeSymbology barcodeSymbology = [self getBarcodeSymbology:[command valueForKey:@"BarcodeSymbology"]];
            SCBBarcodeWidth barcodeWidth = [self getBarcodeWidth:[command valueForKey:@"BarcodeWidth"]];
            int height = ([command valueForKey:@"height"]) ? [[command valueForKey:@"height"] intValue] : 40;
            BOOL hri = ([[command valueForKey:@"hri"] boolValue] == NO) ? NO : YES;
            
            if ([command valueForKey:@"absolutePosition"]) {
                int position = ([[command valueForKey:@"absolutePosition"] intValue]) ? [[command valueForKey:@"absolutePosition"] intValue] : 40;
                [builder appendBarcodeDataWithAbsolutePosition:[[command valueForKey:@"appendBarcode"] dataUsingEncoding:encoding]
                                                     symbology:barcodeSymbology width:barcodeWidth height:height hri:hri position:position];
            }
            else if ([command valueForKey:@"alignment"]) {
                SCBAlignmentPosition alignment = [self getAlignment:[command valueForKey:@"alignment"]];
                [builder appendBarcodeDataWithAlignment:[[command valueForKey:@"appendBarcode"] dataUsingEncoding:encoding]
                                              symbology:barcodeSymbology width:barcodeWidth height:height hri:hri position:alignment];
            }
            else {
                [builder appendBarcodeData:[[command valueForKey:@"appendBarcode"] dataUsingEncoding:encoding]
                                 symbology:barcodeSymbology width:barcodeWidth height:height hri:hri];
            }
        }
        else if ([command valueForKey:@"appendQrCode"]) {
            SCBQrCodeModel qrCodeModel = [self getQrCodeModel:[command valueForKey:@"QrCodeModel"]];
            SCBQrCodeLevel qrCodeLevel = [self getQrCodeLevel:[command valueForKey:@"QrCodeLevel"]];
            int cell = ([[command valueForKey:@"cell"] intValue]) ? [[command valueForKey:@"cell"] intValue] : 4;
            
            if ([command valueForKey:@"absolutePosition"]) {
                int position = ([[command valueForKey:@"absolutePosition"] intValue]) ? [[command valueForKey:@"absolutePosition"] intValue] : 40;
                [builder appendQrCodeDataWithAbsolutePosition:[[command valueForKey:@"appendQrCode"] dataUsingEncoding:encoding]
                                                        model:qrCodeModel level:qrCodeLevel cell:cell position:position];
            }
            else if ([command valueForKey:@"alignment"]) {
                SCBAlignmentPosition alignment = [self getAlignment:[command valueForKey:@"alignment"]];
                [builder appendQrCodeDataWithAlignment:[[command valueForKey:@"appendQrCode"] dataUsingEncoding:encoding]
                                                 model:qrCodeModel level:qrCodeLevel cell:cell position:alignment];
            }
            else {
                [builder appendQrCodeData:[[command valueForKey:@"appendQrCode"] dataUsingEncoding:encoding]
                                     model:qrCodeModel level:qrCodeLevel cell:cell];
            }
        }
        else if ([command valueForKey:@"appendBitmap"]) {
            NSString *urlString = [command valueForKey:@"appendBitmap"];
            NSInteger width = ([command valueForKey:@"width"]) ? [[command valueForKey:@"width"] intValue] : 576;
            BOOL diffusion = ([[command valueForKey:@"diffusion"] boolValue] == NO) ? NO : YES;
            BOOL bothScale = ([[command valueForKey:@"bothScale"] boolValue] == NO) ? NO : YES;
            SCBBitmapConverterRotation rotation = [self getBitmapConverterRotation:[command valueForKey:@"rotation"]];
            NSError *error = nil;
            NSURL *imageURL = [NSURL URLWithString:urlString];
            NSData *imageData = [NSData dataWithContentsOfURL:imageURL options:NSDataReadingUncached error:&error];
            
            if (error != nil) {
                NSURL *fileImageURL = [NSURL fileURLWithPath:urlString];
                imageData = [NSData dataWithContentsOfURL:fileImageURL];
            }
            
            UIImage *image = [UIImage imageWithData:imageData];
            
            if ([command valueForKey:@"absolutePosition"]) {
                int position = ([[command valueForKey:@"absolutePosition"] intValue]) ? [[command valueForKey:@"absolutePosition"] intValue] : 40;
                [builder appendBitmapWithAbsolutePosition:image diffusion:diffusion width:width bothScale:bothScale rotation:rotation position:position];
            }
            else if ([command valueForKey:@"alignment"]) {
                SCBAlignmentPosition alignment = [self getAlignment:[command valueForKey:@"alignment"]];
                [builder appendBitmapWithAlignment:image diffusion:diffusion width:width bothScale:bothScale rotation:rotation position:alignment];
            }
            else {
                [builder appendBitmap:image diffusion:diffusion width:width bothScale:bothScale rotation:rotation];
            }
        }
        else if ([command valueForKey:@"appendBitmapText"]) {
            NSString *text = [command valueForKey:@"appendBitmapText"];
            NSInteger width = ([command valueForKey:@"width"]) ? [[command valueForKey:@"width"] intValue] : 576;
            NSString *fontName = ([command valueForKey:@"font"]) ? [command valueForKey:@"font"] : @"Menlo";
            NSInteger fontSize = ([command valueForKey:@"fontSize"]) ? [[command valueForKey:@"fontSize"] intValue] : 12;
            BOOL bothScale = ([[command valueForKey:@"bothScale"] boolValue] == NO) ? NO : YES;
            SCBBitmapConverterRotation rotation = SCBBitmapConverterRotationNormal;
            
            UIFont *font = [UIFont fontWithName:fontName size:fontSize * 2];
            UIImage *image = [self imageWithString:text font:font width:width];
            
            if ([command valueForKey:@"alignment"]) {
                SCBAlignmentPosition alignment = [self getAlignment:[command valueForKey:@"alignment"]];
                [builder appendBitmapWithAlignment:image diffusion:NO width:width bothScale:bothScale rotation:rotation position:alignment];
            }
            else {
                [builder appendBitmap:image diffusion:NO];
            }
        }
    }
}

#pragma mark - ISCBBuilder Constants

- (NSStringEncoding)getEncoding:(NSString *)encoding {
    if (encoding != nil && encoding != (id)[NSNull null]) {
        if ([encoding isEqualToString:@"US-ASCII"]) return NSASCIIStringEncoding; // English
        else if ([encoding isEqualToString:@"Windows-1252"]) return NSWindowsCP1252StringEncoding; // French, German, Portuguese, Spanish
        else if ([encoding isEqualToString:@"Shift-JIS"]) return NSShiftJISStringEncoding; // Japanese
        else if ([encoding isEqualToString:@"Windows-1251"]) return NSWindowsCP1251StringEncoding; // Russian
        else if ([encoding isEqualToString:@"GB2312"]) return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000); // Simplified Chinese
        else if ([encoding isEqualToString:@"Big5"]) return CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingBig5); // Traditional Chinese
        else if ([encoding isEqualToString:@"UTF-8"]) return NSUTF8StringEncoding; // UTF-8
        return NSWindowsCP1252StringEncoding;
    } else {
        return NSWindowsCP1252StringEncoding;
    }
}

- (SCBAlignmentPosition)getAlignment:(NSString *)alignment {
    if (alignment != nil && alignment != (id)[NSNull null]) {
        if ([alignment caseInsensitiveCompare:@"left"] == NSOrderedSame) return SCBAlignmentPositionLeft;
        else if ([alignment caseInsensitiveCompare:@"center"] == NSOrderedSame) return SCBAlignmentPositionCenter;
        else if ([alignment caseInsensitiveCompare:@"right"] == NSOrderedSame) return SCBAlignmentPositionRight;
        else return SCBAlignmentPositionLeft;
    } else {
        return SCBAlignmentPositionLeft;
    }
}

- (SCBInternationalType)getInternational:(NSString *)internationl {
    if (internationl != nil && internationl != (id)[NSNull null]) {
        if ([internationl isEqualToString:@"US"] || [internationl isEqualToString:@"USA"]) return SCBInternationalTypeUSA;
        else if ([internationl isEqualToString:@"FR"] || [internationl isEqualToString:@"France"]) return SCBInternationalTypeFrance;
        else if ([internationl isEqualToString:@"UK"]) return SCBInternationalTypeUK;
        else if ([internationl isEqualToString:@"Germany"]) return SCBInternationalTypeGermany;
        else if ([internationl isEqualToString:@"Denmark"]) return SCBInternationalTypeDenmark;
        else if ([internationl isEqualToString:@"Sweden"]) return SCBInternationalTypeSweden;
        else if ([internationl isEqualToString:@"Italy"]) return SCBInternationalTypeItaly;
        else if ([internationl isEqualToString:@"Spain"]) return SCBInternationalTypeSpain;
        else if ([internationl isEqualToString:@"Japan"]) return SCBInternationalTypeJapan;
        else if ([internationl isEqualToString:@"Norway"]) return SCBInternationalTypeNorway;
        else if ([internationl isEqualToString:@"Denmark2"]) return SCBInternationalTypeDenmark2;
        else if ([internationl isEqualToString:@"Spain2"]) return SCBInternationalTypeSpain2;
        else if ([internationl isEqualToString:@"LatinAmerica"]) return SCBInternationalTypeLatinAmerica;
        else if ([internationl isEqualToString:@"Korea"]) return SCBInternationalTypeKorea;
        else if ([internationl isEqualToString:@"Ireland"]) return SCBInternationalTypeIreland;
        else if ([internationl isEqualToString:@"Legal"]) return SCBInternationalTypeLegal;
        else return SCBInternationalTypeUSA;
    } else {
        return SCBInternationalTypeUSA;
    }
}

- (SCBFontStyleType)getFont:(NSString *)font {
    if (font != nil && font != (id)[NSNull null]) {
        if ([font isEqualToString:@"A"]) return SCBFontStyleTypeA;
        else if ([font isEqualToString:@"B"]) return SCBFontStyleTypeB;
        else return SCBFontStyleTypeA;
    } else {
        return SCBFontStyleTypeA;
    }
}

- (SCBCodePageType)getCodePageType:(NSString *)codePageType {
    if (codePageType != nil && codePageType != (id)[NSNull null]) {
        if ([codePageType isEqualToString:@"CP437"]) return SCBCodePageTypeCP437;
        else if ([codePageType isEqualToString:@"CP737"]) return SCBCodePageTypeCP737;
        else if ([codePageType isEqualToString:@"CP772"]) return SCBCodePageTypeCP772;
        else if ([codePageType isEqualToString:@"CP774"]) return SCBCodePageTypeCP774;
        else if ([codePageType isEqualToString:@"CP851"]) return SCBCodePageTypeCP851;
        else if ([codePageType isEqualToString:@"CP852"]) return SCBCodePageTypeCP852;
        else if ([codePageType isEqualToString:@"CP855"]) return SCBCodePageTypeCP855;
        else if ([codePageType isEqualToString:@"CP857"]) return SCBCodePageTypeCP857;
        else if ([codePageType isEqualToString:@"CP858"]) return SCBCodePageTypeCP858;
        else if ([codePageType isEqualToString:@"CP860"]) return SCBCodePageTypeCP860;
        else if ([codePageType isEqualToString:@"CP861"]) return SCBCodePageTypeCP861;
        else if ([codePageType isEqualToString:@"CP862"]) return SCBCodePageTypeCP862;
        else if ([codePageType isEqualToString:@"CP863"]) return SCBCodePageTypeCP863;
        else if ([codePageType isEqualToString:@"CP864"]) return SCBCodePageTypeCP864;
        else if ([codePageType isEqualToString:@"CP865"]) return SCBCodePageTypeCP1250; // Note: Adjust as needed
        else if ([codePageType isEqualToString:@"CP869"]) return SCBCodePageTypeCP869;
        else if ([codePageType isEqualToString:@"CP874"]) return SCBCodePageTypeCP874;
        else if ([codePageType isEqualToString:@"CP928"]) return SCBCodePageTypeCP928;
        else if ([codePageType isEqualToString:@"CP932"]) return SCBCodePageTypeCP932;
        else if ([codePageType isEqualToString:@"CP999"]) return SCBCodePageTypeCP999;
        else if ([codePageType isEqualToString:@"CP1001"]) return SCBCodePageTypeCP1001;
        else if ([codePageType isEqualToString:@"CP1250"]) return SCBCodePageTypeCP1250;
        else if ([codePageType isEqualToString:@"CP1251"]) return SCBCodePageTypeCP1251;
        else if ([codePageType isEqualToString:@"CP1252"]) return SCBCodePageTypeCP1252;
        else if ([codePageType isEqualToString:@"CP2001"]) return SCBCodePageTypeCP2001;
        else if ([codePageType isEqualToString:@"CP3001"]) return SCBCodePageTypeCP3001;
        else if ([codePageType isEqualToString:@"CP3002"]) return SCBCodePageTypeCP3002;
        else if ([codePageType isEqualToString:@"CP3011"]) return SCBCodePageTypeCP3011;
        else if ([codePageType isEqualToString:@"CP3012"]) return SCBCodePageTypeCP3012;
        else if ([codePageType isEqualToString:@"CP3021"]) return SCBCodePageTypeCP3021;
        else if ([codePageType isEqualToString:@"CP3041"]) return SCBCodePageTypeCP3041;
        else if ([codePageType isEqualToString:@"CP3840"]) return SCBCodePageTypeCP3840;
        else if ([codePageType isEqualToString:@"CP3841"]) return SCBCodePageTypeCP3841;
        else if ([codePageType isEqualToString:@"CP3843"]) return SCBCodePageTypeCP3843;
        else if ([codePageType isEqualToString:@"CP3845"]) return SCBCodePageTypeCP3845;
        else if ([codePageType isEqualToString:@"CP3846"]) return SCBCodePageTypeCP3846;
        else if ([codePageType isEqualToString:@"CP3847"]) return SCBCodePageTypeCP3847;
        else if ([codePageType isEqualToString:@"CP3848"]) return SCBCodePageTypeCP3848;
        else if ([codePageType isEqualToString:@"UTF8"]) return SCBCodePageTypeUTF8;
        else if ([codePageType isEqualToString:@"Blank"]) return SCBCodePageTypeBlank;
        else return SCBCodePageTypeCP998;
    } else {
        return SCBCodePageTypeCP998;
    }
}

- (SCBCutPaperAction)getCutPaperAction:(NSString *)cutPaperAction {
    if (cutPaperAction != nil && cutPaperAction != (id)[NSNull null]) {
        if ([cutPaperAction isEqualToString:@"FullCut"]) return SCBCutPaperActionFullCut;
        else if ([cutPaperAction isEqualToString:@"FullCutWithFeed"]) return SCBCutPaperActionFullCutWithFeed;
        else if ([cutPaperAction isEqualToString:@"PartialCut"]) return SCBCutPaperActionPartialCut;
        else if ([cutPaperAction isEqualToString:@"PartialCutWithFeed"]) return SCBCutPaperActionPartialCutWithFeed;
        else return SCBCutPaperActionPartialCutWithFeed;
    } else {
        return SCBCutPaperActionPartialCutWithFeed;
    }
}

- (SCBPeripheralChannel)getPeripheralChannel:(NSNumber *)peripheralChannel {
    if (peripheralChannel != nil) {
        if ([peripheralChannel intValue] == 1) return SCBPeripheralChannelNo1;
        else if ([peripheralChannel intValue] == 2) return SCBPeripheralChannelNo2;
        else return SCBPeripheralChannelNo1;
    } else {
        return SCBPeripheralChannelNo1;
    }
}

- (SCBBlackMarkType)getBlackMarkType:(NSString *)blackMarkType {
    if (blackMarkType != nil && blackMarkType != (id)[NSNull null]) {
        if ([blackMarkType isEqualToString:@"Valid"]) return SCBBlackMarkTypeValid;
        else if ([blackMarkType isEqualToString:@"Invalid"]) return SCBBlackMarkTypeInvalid;
        else if ([blackMarkType isEqualToString:@"ValidWithDetection"]) return SCBBlackMarkTypeValidWithDetection;
        else return SCBBlackMarkTypeValid;
    } else {
        return SCBBlackMarkTypeValid;
    }
}

- (SCBLogoSize)getLogoSize:(NSString *)logoSize {
    if (logoSize != nil && logoSize != (id)[NSNull null]) {
        if ([logoSize isEqualToString:@"Normal"]) return SCBLogoSizeNormal;
        else if ([logoSize isEqualToString:@"DoubleWidth"]) return SCBLogoSizeDoubleWidth;
        else if ([logoSize isEqualToString:@"DoubleHeight"]) return SCBLogoSizeDoubleHeight;
        else if ([logoSize isEqualToString:@"DoubleWidthDoubleHeight"]) return SCBLogoSizeDoubleWidthDoubleHeight;
        else return SCBLogoSizeNormal;
    } else {
        return SCBLogoSizeNormal;
    }
}

- (SCBBarcodeSymbology)getBarcodeSymbology:(NSString *)barcodeSymbology {
    if (barcodeSymbology != nil && barcodeSymbology != (id)[NSNull null]) {
        if ([barcodeSymbology isEqualToString:@"Code128"]) return SCBBarcodeSymbologyCode128;
        else if ([barcodeSymbology isEqualToString:@"Code39"]) return SCBBarcodeSymbologyCode39;
        else if ([barcodeSymbology isEqualToString:@"Code93"]) return SCBBarcodeSymbologyCode128;
        else if ([barcodeSymbology isEqualToString:@"ITF"]) return SCBBarcodeSymbologyITF;
        else if ([barcodeSymbology isEqualToString:@"JAN8"]) return SCBBarcodeSymbologyJAN8;
        else if ([barcodeSymbology isEqualToString:@"JAN13"]) return SCBBarcodeSymbologyJAN13;
        else if ([barcodeSymbology isEqualToString:@"NW7"]) return SCBBarcodeSymbologyNW7;
        else if ([barcodeSymbology isEqualToString:@"UPCA"]) return SCBBarcodeSymbologyUPCA;
        else if ([barcodeSymbology isEqualToString:@"UPCE"]) return SCBBarcodeSymbologyUPCE;
        else return SCBBarcodeSymbologyCode128;
    } else {
        return SCBBarcodeSymbologyCode128;
    }
}

- (SCBBarcodeWidth)getBarcodeWidth:(NSString *)barcodeWidth {
    if (barcodeWidth != nil && barcodeWidth != (id)[NSNull null]) {
        if ([barcodeWidth isEqualToString:@"Mode1"]) return SCBBarcodeWidthMode1;
        else if ([barcodeWidth isEqualToString:@"Mode2"]) return SCBBarcodeWidthMode2;
        else if ([barcodeWidth isEqualToString:@"Mode3"]) return SCBBarcodeWidthMode3;
        else if ([barcodeWidth isEqualToString:@"Mode4"]) return SCBBarcodeWidthMode4;
        else if ([barcodeWidth isEqualToString:@"Mode5"]) return SCBBarcodeWidthMode5;
        else if ([barcodeWidth isEqualToString:@"Mode6"]) return SCBBarcodeWidthMode6;
        else if ([barcodeWidth isEqualToString:@"Mode7"]) return SCBBarcodeWidthMode7;
        else if ([barcodeWidth isEqualToString:@"Mode8"]) return SCBBarcodeWidthMode8;
        else if ([barcodeWidth isEqualToString:@"Mode9"]) return SCBBarcodeWidthMode9;
        else return SCBBarcodeWidthMode2;
    } else {
        return SCBBarcodeWidthMode2;
    }
}

- (SCBQrCodeModel)getQrCodeModel:(NSString *)qrCodeModel {
    if (qrCodeModel != nil && qrCodeModel != (id)[NSNull null]) {
        if ([qrCodeModel isEqualToString:@"No1"]) return SCBQrCodeModelNo1;
        else if ([qrCodeModel isEqualToString:@"No2"]) return SCBQrCodeModelNo2;
        else return SCBQrCodeModelNo1;
    } else {
        return SCBQrCodeModelNo1;
    }
}

- (SCBQrCodeLevel)getQrCodeLevel:(NSString *)qrCodeLevel {
    if (qrCodeLevel != nil && qrCodeLevel != (id)[NSNull null]) {
        if ([qrCodeLevel isEqualToString:@"H"]) return SCBQrCodeLevelH;
        else if ([qrCodeLevel isEqualToString:@"L"]) return SCBQrCodeLevelL;
        else if ([qrCodeLevel isEqualToString:@"M"]) return SCBQrCodeLevelM;
        else if ([qrCodeLevel isEqualToString:@"Q"]) return SCBQrCodeLevelQ;
        else return SCBQrCodeLevelH;
    } else {
        return SCBQrCodeLevelH;
    }
}

- (SCBBitmapConverterRotation)getBitmapConverterRotation:(NSString *)rotation {
    if (rotation != nil && rotation != (id)[NSNull null]) {
        if ([rotation isEqualToString:@"Normal"]) return SCBBitmapConverterRotationNormal;
        else if ([rotation isEqualToString:@"Left90"]) return SCBBitmapConverterRotationLeft90;
        else if ([rotation isEqualToString:@"Right90"]) return SCBBitmapConverterRotationRight90;
        else if ([rotation isEqualToString:@"Rotate180"]) return SCBBitmapConverterRotationRotate180;
        else return SCBBitmapConverterRotationNormal;
    } else {
        return SCBBitmapConverterRotationNormal;
    }
}

- (NSDictionary *)constantsToExport {
    return @{ @"StarPRNT" : @(StarIoExtEmulationStarPRNT),
              @"StarPRNTL" : @(StarIoExtEmulationStarPRNTL),
              @"StarLine" : @(StarIoExtEmulationStarLine) };
}

- (UIImage *)imageWithString:(NSString *)string font:(UIFont *)font width:(CGFloat)width {
    NSDictionary *attributeDic = @{ NSFontAttributeName : font };
    
    CGSize size = [string boundingRectWithSize:CGSizeMake(width, 10000)
                                       options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine
                                    attributes:attributeDic
                                       context:nil].size;
    
    if ([UIScreen.mainScreen respondsToSelector:@selector(scale)]) {
        if (UIScreen.mainScreen.scale == 2.0) {
            UIGraphicsBeginImageContextWithOptions(size, NO, 1.0);
        } else {
            UIGraphicsBeginImageContext(size);
        }
    } else {
        UIGraphicsBeginImageContext(size);
    }
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    [[UIColor whiteColor] set];
    
    CGRect rect = CGRectMake(0, 0, size.width + 1, size.height + 1);
    CGContextFillRect(context, rect);
    
    NSDictionary *attributes = @{ NSForegroundColorAttributeName : [UIColor blackColor],
                                  NSFontAttributeName : font };
    
    [string drawInRect:rect withAttributes:attributes];
    
    UIImage *imageToPrint = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return imageToPrint;
}

@end
