//
//  DataAdapterController.m
//  DataAdapter
//
//  Created by Brian Burley on 2025-02-24.
//

#import "DataAdapterController.h"

@implementation DataAdapterController
#pragma mark Form load/shutdown

- (void)viewWillDisappear {
    [super viewWillDisappear];
    if(ch){
        //Safely close example
        Phidget_setOnAttachHandler((PhidgetHandle)ch, NULL, NULL);
        Phidget_setOnDetachHandler((PhidgetHandle)ch, NULL, NULL);
        Phidget_setOnErrorHandler((PhidgetHandle)ch, NULL, NULL);
        Phidget_close((PhidgetHandle)ch);
        PhidgetDataAdapter_delete(&ch);
        ch = nil;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [addRow setAction:@selector(addRowClick)];
    
    PhidgetReturnCode result;

    // Load the separate storyboard that contains PhidgetInfoBoxN
    NSStoryboard *phidgetStoryboard = [NSStoryboard storyboardWithName:@"PhidgetInfoBoxN" bundle:nil];

    // Instantiate the view controller
     self.infoBoxVC = [phidgetStoryboard instantiateControllerWithIdentifier:@"PhidgetInfoBoxN"];
    
    // Add the view to your placeholder container
    NSView *infoBoxView = self.infoBoxVC.view;
    infoBoxView.translatesAutoresizingMaskIntoConstraints = NO;

    [self->phidgetInfoView addSubview:infoBoxView];

    // Add constraints to make it fill the container
    [NSLayoutConstraint activateConstraints:@[
        [infoBoxView.topAnchor constraintEqualToAnchor:self->phidgetInfoView.topAnchor],
        [infoBoxView.bottomAnchor constraintEqualToAnchor:self->phidgetInfoView.bottomAnchor],
        [infoBoxView.leadingAnchor constraintEqualToAnchor:self->phidgetInfoView.leadingAnchor],
        [infoBoxView.trailingAnchor constraintEqualToAnchor:self->phidgetInfoView.trailingAnchor]
    ]];
    
    
    [self.infoBoxVC fillPhidgetInfo:nil];
    [phidgetInfoBox setHidden:NO];
    
    txCommands = [NSMutableArray array];
    rxCommands = [NSMutableArray array];
    
    txTable.dataSource = self;
    txTable.delegate = self;
    
    rxTable.dataSource = self;
    rxTable.delegate = self;
    
    result = PhidgetDataAdapter_create(&ch);
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
    
    result = [self initChannel:(PhidgetHandle)ch];
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
    
    
    [PhidgetInfoBoxN openCmdLine:(PhidgetHandle)ch];
    
    

}

-(PhidgetReturnCode)initChannel:(PhidgetHandle) channel{
    PhidgetReturnCode result;
    
    result = Phidget_setOnAttachHandler(channel, gotAttach, (__bridge void*)self);
    if(result != EPHIDGET_OK){
        return result;
    }
    
    result = Phidget_setOnDetachHandler(channel, gotDetach, (__bridge void*)self);
    if(result != EPHIDGET_OK){
        return result;
    }
    
    result = Phidget_setOnErrorHandler(channel, gotError, (__bridge void*)self);
    if(result != EPHIDGET_OK){
        return result;
    }
    return EPHIDGET_OK;
}


#pragma mark Device Event Callbacks

static void gotAttach(PhidgetHandle phid, void *context){
    [(__bridge id)context performSelectorOnMainThread:@selector(onAttachHandler)
                                           withObject:nil
                                        waitUntilDone:NO];
}

static void gotDetach(PhidgetHandle phid, void *context){
    [(__bridge id)context performSelectorOnMainThread:@selector(onDetachHandler)
                                           withObject:nil
                                        waitUntilDone:NO];
}

static void gotError(PhidgetHandle phid, void *context, Phidget_ErrorEventCode errcode, const char *error){
    [(__bridge id)context performSelectorOnMainThread:@selector(errorHandler:)
                                           withObject:[NSArray arrayWithObjects:[NSNumber numberWithInt:errcode], [NSString stringWithUTF8String:error], nil]
                                        waitUntilDone:NO];
}


#pragma mark Attach, detach, and error events
- (void)onAttachHandler{
    NSWindow *window = self.view.window;
    NSRect newFrame = window.frame;
    newFrame.size = NSMakeSize(814, 804);
    [window setFrame:newFrame display:YES];
    
    Phidget_DeviceID deviceID;
    NSArray *supportedVoltages;
    NSArray *supportedFrequencies;
    
    //Get information from channel which will allow us to configure the GUI properly
    Phidget_getDeviceID((PhidgetHandle)ch, &deviceID);
    
    //Adjust GUI based on information from channel
    [self.infoBoxVC fillPhidgetInfo:(PhidgetHandle)ch];
    [configBox setHidden:NO];
    [controlsBox setHidden:NO];
    [receivedBox setHidden:NO];
    
    
    if(deviceID == PHIDID_ADP0001){ //i2c
        [spiPanel setHidden:YES];
        [endiannessPanel setHidden:YES];
        [addressPanel setHidden:NO];
        supportedVoltages = [[NSArray alloc] initWithObjects:@"External", @"2.5 V", @"3.3 V",@"5 V", nil];
        supportedFrequencies = [[NSArray alloc] initWithObjects:@"10kHz", @"100kHz", @"400kHz",  nil];
        [voltageCombo removeAllItems];
        [frequencyCombo removeAllItems];
        [voltageCombo addItemsWithTitles: supportedVoltages];
        [frequencyCombo addItemsWithTitles: supportedFrequencies];
        
        [self initializeCommandTable:@"i2c"];
    }
    else if(deviceID == PHIDID_ADP0002){ //spi
        [spiPanel setHidden:NO];
        [endiannessPanel setHidden:NO];
        [addressPanel setHidden:YES];
        supportedVoltages = [[NSArray alloc] initWithObjects:@"External", @"2.5 V", @"3.3 V",@"5 V", nil];
        supportedFrequencies = [[NSArray alloc] initWithObjects:@"188kHz", @"375kHz", @"750kHz", @"1500kHz", @"3MHz", @"6MHz",  nil];
        
        [voltageCombo removeAllItems];
        [frequencyCombo removeAllItems];
        [voltageCombo addItemsWithTitles: supportedVoltages];
        [frequencyCombo addItemsWithTitles: supportedFrequencies];
        
        [self initializeCommandTable:@"spi"];
    }
    
    [voltageCombo selectLine:0];
    [chipselectCombo selectLine:0];
    [modeCombo selectLine:0];
}

- (void)onDetachHandler{
    //Taking items out of view
    [configBox setHidden:YES];
    [controlsBox setHidden:YES];
    [receivedBox setHidden:YES];
    [self.infoBoxVC fillPhidgetInfo:nil];
    
    NSWindow *window = self.view.window;
    NSRect newFrame = window.frame;
    newFrame.size = NSMakeSize(814, 150);
    [window setFrame:newFrame display:YES];
    
    //clear tables
    [txCommands removeAllObjects];
    [txTable reloadData];
    
    [rxCommands removeAllObjects];
    [rxTable reloadData];
}

static int errorCounter = 0;
-(void) outputError:(const char *)errorString{
    errorCounter++;
    NSAttributedString *outputString = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%@\n",[NSString stringWithUTF8String:errorString]]
        attributes:@{NSForegroundColorAttributeName: NSColor.controlTextColor}];
    [[errorEventLog textStorage] beginEditing];
    [[errorEventLog textStorage] appendAttributedString:outputString];
    [[errorEventLog textStorage] endEditing];
    
    [errorEventLogCounter setIntValue:errorCounter];
    /*if(![errorEventLogWindow isHidden])
        [errorEventLogWindow setHidden:YES];*/
    
    if (!self.errorEventLogWindow) {
        NSStoryboard *storyboard = [NSStoryboard storyboardWithName:@"Main" bundle:nil];
        self.errorEventLogWindow = [storyboard instantiateControllerWithIdentifier:@"errorEventLogWindow"];
    }

    [self.errorEventLogWindow showWindow:self];
}


-(void)errorHandler:(NSArray *)errorEventData{
    const char* errorString = [[errorEventData objectAtIndex:1] UTF8String];
    [self outputError:errorString];
}

-(void)outputLastError {
    const char *errorString;
    char *errorDetailString = NULL;
    size_t errorDetailStringLen = 0;
    PhidgetReturnCode lastErr;
    
    if (!Phidget_getLastError(&lastErr, &errorString, NULL, &errorDetailStringLen)) {
        errorDetailString = malloc(errorDetailStringLen);
        Phidget_getLastError(&lastErr, &errorString, errorDetailString, &errorDetailStringLen);
        [self outputError:errorDetailString];
        free(errorDetailString);
    }
}

- (IBAction)clearErrorLog:(id)sender{
    [[errorEventLog textStorage] setAttributedString:[[NSAttributedString alloc] initWithString:@""]];
    [errorEventLogCounter setIntValue:0];
    errorCounter = 0;
}


#pragma mark UI Events
- (void)addRowClick{
    // Create a new row
    if(isSPI){
        NSDictionary *newRow = @{
            @"command": [NSString stringWithFormat:@"Command %lu", txCommands.count + 1],
            @"waitForReply": @(NO)  // Default checkbox state is NO (unchecked)
        };
        
        // Add the new row to the data array
        [txCommands addObject:newRow];
        
        // Reload the table view to reflect the changes
        [txTable reloadData];
    }
    
    else if(!isSPI){
        NSDictionary *newRow = @{
            @"command": [NSString stringWithFormat:@"Command %lu", txCommands.count + 1],
            @"waitForReply": [NSString stringWithFormat:@"0"]
        };
        
        // Add the new row to the data array
        [txCommands addObject:newRow];
        
        // Reload the table view to reflect the changes
        [txTable reloadData];
    }
}
- (IBAction)removeRowClick:(id)sender {
    if(txCommands.count>0)
        [txCommands removeLastObject];
    [txTable reloadData];
}

- (IBAction)clearRXTableClick:(id)sender {
    [rxCommands removeAllObjects];
    [rxTable reloadData];
}

- (IBAction)voltageComboChange:(id)sender{
    PhidgetReturnCode result = EPHIDGET_OK;
    if([voltageCombo.titleOfSelectedItem  isEqual: @"External"])
        result = PhidgetDataAdapter_setDataAdapterVoltage(ch, DATAADAPTER_VOLTAGE_EXTERN);
    else if([voltageCombo.titleOfSelectedItem  isEqual: @"2.5 V"])
        result = PhidgetDataAdapter_setDataAdapterVoltage(ch, DATAADAPTER_VOLTAGE_2_5V);
    else if([voltageCombo.titleOfSelectedItem  isEqual: @"3.3 V"])
        result = PhidgetDataAdapter_setDataAdapterVoltage(ch, DATAADAPTER_VOLTAGE_3_3V);
    else if([voltageCombo.titleOfSelectedItem  isEqual: @"5 V"])
        result = PhidgetDataAdapter_setDataAdapterVoltage(ch, DATAADAPTER_VOLTAGE_5_0V);
    
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
}

- (IBAction)freqComboChange:(id)sender{
    PhidgetReturnCode result = EPHIDGET_OK;
    if([frequencyCombo.titleOfSelectedItem  isEqual: @"10kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_10kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"100kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_100kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"188kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_188kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"375kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_375kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"400kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_400kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"750kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_750kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"1500kHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_1500kHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"3MHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_3MHz);
    else if([frequencyCombo.titleOfSelectedItem  isEqual: @"6MHz"])
        result = PhidgetDataAdapter_setFrequency(ch, FREQUENCY_6MHz);
    
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
}

- (IBAction)modeComboChange:(id)sender{
    PhidgetReturnCode result = EPHIDGET_OK;
    if([modeCombo.titleOfSelectedItem  isEqual: @"Mode_0"])
        result = PhidgetDataAdapter_setSPIMode(ch, SPI_MODE_0);
    else if([modeCombo.titleOfSelectedItem  isEqual: @"Mode_1"])
        result = PhidgetDataAdapter_setSPIMode(ch, SPI_MODE_1);
    else if([modeCombo.titleOfSelectedItem  isEqual: @"Mode_2"])
        result = PhidgetDataAdapter_setSPIMode(ch, SPI_MODE_2);
    else if([modeCombo.titleOfSelectedItem  isEqual: @"Mode_3"])
        result = PhidgetDataAdapter_setSPIMode(ch, SPI_MODE_3);
    
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
}

- (IBAction)chipSelectComboChange:(id)sender{
    PhidgetReturnCode result = EPHIDGET_OK;
    if([chipselectCombo.titleOfSelectedItem  isEqual: @"Low"])
        result = PhidgetDataAdapter_setSPIChipSelect(ch, SPI_CHIP_SELECT_LOW);
    else if([chipselectCombo.titleOfSelectedItem  isEqual: @"High"])
        result = PhidgetDataAdapter_setSPIChipSelect(ch, SPI_CHIP_SELECT_HIGH);
    else if([chipselectCombo.titleOfSelectedItem  isEqual: @"ActiveLow"])
        result = PhidgetDataAdapter_setSPIChipSelect(ch, SPI_CHIP_SELECT_ACTIVE_LOW);
    else if([chipselectCombo.titleOfSelectedItem  isEqual: @"ActiveHigh"])
        result = PhidgetDataAdapter_setSPIChipSelect(ch, SPI_CHIP_SELECT_ACTIVE_HIGH);
    
    if(result != EPHIDGET_OK){
        [self outputLastError];
    }
}

- (IBAction)endiannessChanged:(id)sender{
    NSButton *selectedButton = (NSButton *)sender;
    NSString *name = selectedButton.title;
    PhidgetReturnCode result = EPHIDGET_OK;
    
    if([name isEqualToString:@"MSB First"])
        result = PhidgetDataAdapter_setEndianness(ch, ENDIANNESS_MSB_FIRST);
    else if ([name isEqualToString:@"LSB First"])
        result = PhidgetDataAdapter_setEndianness(ch, ENDIANNESS_LSB_FIRST);
    
    if(result != EPHIDGET_OK)
        [self outputLastError];
}

#pragma mark NSTableViewDataSource
-(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
    if(tableView.tag == 0)
        return txCommands.count;
    else
        return rxCommands.count;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    if(tableView.tag == 0){
        NSDictionary *rowData = txCommands[row];
        
        NSString *identifier = tableColumn.identifier;
        
        if(isSPI){
            if ([identifier isEqualToString:@"commandColumn"]) {
                return rowData[@"command"];
            }
            else if ([identifier isEqualToString:@"replyColumn"]) {
                return rowData[@"waitForReply"];
            }
        }
        
        else if(!isSPI){
            if([identifier isEqualToString:@"commandColumn"]){
                return rowData[@"command"];
            }
            else if([identifier isEqualToString:@"replyColumn"]){
                return rowData[@"waitForReply"];
            }
        }
    }
    else if(tableView.tag == 1){
        NSDictionary *rowData = rxCommands[row];
        
        NSString *identifier = tableColumn.identifier;
        
        if ([identifier isEqualToString:@"sentColumn"]) {
            return rowData[@"sent"];
        }
        else if ([identifier isEqualToString:@"responseColumn"]) {
            return rowData[@"reply"];
        }
    }
    return nil;
}

#pragma mark NSTableViewDelegate
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    if(tableView.tag == 0){
        if(isSPI){
            NSDictionary *rowData = txCommands[row];
            NSString *identifier = tableColumn.identifier;
            
            if ([identifier isEqualToString:@"commandColumn"]) {
                NSTextField *textField = [tableView makeViewWithIdentifier:@"commandText" owner:self];
                if (!textField) {
                    textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 24)];
                    textField.identifier = @"commandText";
                    textField.editable = YES;
                    textField.bezeled = NO;
                    textField.backgroundColor = NSColor.clearColor;
                    textField.delegate = (id<NSTextFieldDelegate>)self;
                }
                textField.stringValue = rowData[@"command"];
                return textField;
            }
            
            if ([identifier isEqualToString:@"waitForReply"]) {
                // Create a checkbox for the second column
                NSButton *checkbox = [tableView makeViewWithIdentifier:@"checkbox" owner:self];
                if (!checkbox) {
                    checkbox = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 20, 20)];
                    checkbox.buttonType = NSButtonTypeSwitch;
                    checkbox.target = self;
                    checkbox.action = @selector(checkboxChanged:);
                    checkbox.identifier = @"checkbox";
                    checkbox.title = @"";
                    checkbox.alignment = NSTextAlignmentCenter;
                }
                
                checkbox.state = [rowData[@"waitForReply"] boolValue] ? NSControlStateValueOn : NSControlStateValueOff;
                return checkbox;
            }
            else if ([identifier isEqualToString:@"sendColumn"]) {
                // Create a button for the third column
                NSButton *button = [tableView makeViewWithIdentifier:@"button" owner:self];
                if (!button) {
                    button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 30)];
                    [button setTitle:@"Send"];
                    button.target = self;
                    button.action = @selector(buttonClicked:);
                    button.identifier = @"button";
                }
                return button;
            }
        }
        else if(!isSPI){
            NSDictionary *rowData = txCommands[row];
            NSString *identifier = tableColumn.identifier;
            
            if ([identifier isEqualToString:@"commandColumn"]) {
                NSTextField *textField = [tableView makeViewWithIdentifier:@"commandText" owner:self];
                if (!textField) {
                    textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 24)];
                    textField.identifier = @"commandText";
                    textField.editable = YES;
                    textField.bezeled = NO;
                    textField.backgroundColor = NSColor.clearColor;
                    textField.delegate = (id<NSTextFieldDelegate>)self;
                }
                textField.stringValue = rowData[@"command"];
                return textField;
            }
            
            if ([identifier isEqualToString:@"waitForReply"]) {
                NSTextField *textField = [tableView makeViewWithIdentifier:@"replyText" owner:self];
                if (!textField) {
                    textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 24)];
                    textField.identifier = @"replyText";
                    textField.editable = YES;
                    textField.bezeled = NO;
                    textField.backgroundColor = NSColor.clearColor;
                    textField.delegate = (id<NSTextFieldDelegate>)self;
                }
                textField.stringValue = rowData[@"waitForReply"];
                return textField;
            }
            else if ([identifier isEqualToString:@"sendColumn"]) {
                // Create a button for the third column
                NSButton *button = [tableView makeViewWithIdentifier:@"button" owner:self];
                if (!button) {
                    button = [[NSButton alloc] initWithFrame:NSMakeRect(0, 0, 100, 30)];
                    [button setTitle:@"Send"];
                    button.target = self;
                    button.action = @selector(buttonClicked:);
                    button.identifier = @"button";
                }
                return button;
            }
        }
    }
    else if(tableView.tag == 1){
        NSDictionary *rowData = rxCommands[row];
        NSString *identifier = tableColumn.identifier;
        
        if ([identifier isEqualToString:@"sentColumn"]) {
            NSTextField *textField = [tableView makeViewWithIdentifier:@"sentText" owner:self];
            if (!textField) {
                textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 24)];
                textField.identifier = @"sentText";
                textField.editable = NO;
                textField.bezeled = NO;
                textField.backgroundColor = NSColor.clearColor;
                textField.delegate = (id<NSTextFieldDelegate>)self;
            }
            textField.stringValue = rowData[@"sent"];
            return textField;
        }
        if ([identifier isEqualToString:@"responseColumn"]) {
            NSTextField *textField = [tableView makeViewWithIdentifier:@"replyText" owner:self];
            if (!textField) {
                textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, tableColumn.width, 24)];
                textField.identifier = @"replyText";
                textField.editable = NO;
                textField.bezeled = NO;
                textField.backgroundColor = NSColor.clearColor;
                textField.delegate = (id<NSTextFieldDelegate>)self;
            }
            textField.stringValue = rowData[@"reply"];
            return textField;
        }
        
    }
    
    return nil;
}

// Handle checkbox state changes
- (void)checkboxChanged:(NSButton *)sender {
    NSInteger row = [txTable rowForView:sender];
    NSMutableDictionary *rowData = [txCommands[row] mutableCopy];
    rowData[@"waitForReply"] = @(sender.state == NSControlStateValueOn);
    txCommands[row] = rowData;
}

// Handle push button clicks
- (void)buttonClicked:(NSButton *)sender {
    NSInteger row = [txTable rowForView:sender];
    
    [self sendCommand:txCommands[row][@"command"] at:&row withReply:txCommands[row][@"waitForReply"]];
}

#pragma mark - NSTextFieldDelegate

- (void)controlTextDidEndEditing:(NSNotification *)notification {
    NSTextField *textField = notification.object;
    NSInteger row = [txTable rowForView:textField];
    NSInteger column = [txTable columnForView:textField];
    
    if (row >= 0) {
        NSMutableDictionary *rowData = [txCommands[row] mutableCopy];
        if(column == 0)
            rowData[@"command"] = textField.stringValue;
        else if(column == 1)
            rowData[@"waitForReply"] = textField.stringValue;
        txCommands[row] = rowData;
    }
}

#pragma mark Helper Functions
-(void) initializeCommandTable:(NSString*) style{
    // Clear any existing columns
    while (txTable.tableColumns.count > 0) {
        [txTable removeTableColumn:txTable.tableColumns[0]];
    }
    
    if([style isEqualToString:@"spi"]){
        isSPI = true;
        
        NSTableColumn *column1 = [[NSTableColumn alloc] initWithIdentifier:@"commandColumn"];
        column1.title = @"Data";
        [txTable addTableColumn:column1];

        NSTableColumn *column2 = [[NSTableColumn alloc] initWithIdentifier:@"waitForReply"];
        column2.title = @"Wait for Response?";
        [txTable addTableColumn:column2];
        
        NSTableColumn *column3 = [[NSTableColumn alloc] initWithIdentifier:@"sendColumn"];
        column3.title = @"Send";
        [txTable addTableColumn:column3];
    }
    else if([style isEqualToString:@"i2c"]){
        isSPI = false;
        
        NSTableColumn *column1 = [[NSTableColumn alloc] initWithIdentifier:@"commandColumn"];
        column1.title = @"Data";
        [txTable addTableColumn:column1];

        NSTableColumn *column2 = [[NSTableColumn alloc] initWithIdentifier:@"waitForReply"];
        column2.title = @"Reply Bytes";
        [txTable addTableColumn:column2];
        
        NSTableColumn *column3 = [[NSTableColumn alloc] initWithIdentifier:@"sendColumn"];
        column3.title = @"Send";
        [txTable addTableColumn:column3];
    }
}


-(void) sendCommand:(NSString*) commandTxt at:(NSInteger*) rowIndex withReply:(NSString*) wait{
    //NSLog(@"command %@ sent from row %ld with return bytes %@", commandTxt, (long)*rowIndex, wait);
    
    bool waitFlag = false;
    if(isSPI)
    {
        waitFlag = [wait boolValue];
    }
    
    
    Phidget_DeviceID deviceID;
    PhidgetReturnCode ret;
    Phidget_getDeviceID((PhidgetHandle)ch, &deviceID);
    if(deviceID == PHIDID_ADP0002)
    {
        uint8_t received[2];
        size_t recLength = sizeof(&received);
        size_t sendLength = 0;
        uint8_t *toSend = parseByteCommand(commandTxt, &sendLength);

        if (toSend != NULL) { //check if parsing failed
            // Don't forget to free the memory
            free(toSend);
        } else { //and output error message
            [self outputError:"Failed to parse command."];
        }

        //otherwise, send it
        if(!waitFlag){
            ret = PhidgetDataAdapter_sendPacket(ch, toSend, sendLength);
            if(ret != EPHIDGET_OK){
                [self outputLastError];
                return;
            }
            
            NSDictionary *newRow = @{
                @"sent": [NSString stringWithFormat:@"%@", commandTxt],
                @"reply": @"N/A"
            };
            
            // Add the new row to the data array
            [rxCommands addObject:newRow];
            
            // Reload the table view to reflect the changes
            [rxTable reloadData];
        } else if(waitFlag){
            ret = PhidgetDataAdapter_sendPacketWaitResponse(ch, toSend, sendLength, received, &recLength);
            if(ret != EPHIDGET_OK){
                [self outputLastError];
                return;
            }
            
            NSMutableString *hexString = [NSMutableString stringWithCapacity:recLength * 2];
            for (size_t i = 0; i < recLength; i++) {
                [hexString appendFormat:@"%02X", received[i]];
            }
            
            NSDictionary *newRow = @{
                @"sent": [NSString stringWithFormat:@"%@", commandTxt],
                @"reply": [NSString stringWithFormat:@"%@", hexString]
            };
            
            
            // Add the new row to the data array
            [rxCommands addObject:newRow];
            
            // Reload the table view to reflect the changes
            [rxTable reloadData];
        }
    }
    if(deviceID == PHIDID_ADP0001){
        uint8_t received[wait.intValue];
        size_t recLength = sizeof(&received);
        size_t sendLength;
        uint8_t *toSend = parseByteCommand(commandTxt, &sendLength);
        
        if (toSend != NULL) { //check if parsing failed
            // Don't forget to free the memory
            free(toSend);
        } else { //and output error message
            [self outputError:"Failed to parse command."];
        }
        uint addr;
        NSScanner *scanner = [NSScanner scannerWithString:addressTxt.stringValue];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"0x"]];
        [scanner scanHexInt:&addr];

        ret = PhidgetDataAdapter_i2cSendReceive(ch, addr, toSend, sendLength, received, recLength);
        if(ret != EPHIDGET_OK){
            [self outputLastError];
            return;
        }

        
        NSDictionary *newRow = @{
            @"sent": [NSString stringWithFormat:@"%@", commandTxt],
            @"reply": [NSString stringWithFormat:@"%02X", *received]
        };
        
        
        // Add the new row to the data array
        [rxCommands addObject:newRow];
        
        // Reload the table view to reflect the changes
        [rxTable reloadData];
        
    }
}


uint8_t *parseByteCommand(NSString *input, size_t *outLength) {
    if (!input || input.length == 0) return NULL;

    // Normalize delimiters
    NSString *normalized = [[input stringByReplacingOccurrencesOfString:@"," withString:@" "]
                            stringByReplacingOccurrencesOfString:@";" withString:@" "];

    // Collapse multiple spaces
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\s+" options:0 error:nil];
    normalized = [regex stringByReplacingMatchesInString:normalized
                                                 options:0
                                                   range:NSMakeRange(0, normalized.length)
                                            withTemplate:@" "];
    
    // Trim whitespace
    normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
    
    NSMutableArray<NSNumber *> *byteArray = [NSMutableArray array];
    
    NSArray<NSString *> *tokens = [normalized componentsSeparatedByString:@" "];

    for (NSString *token in tokens) {
        NSString *cleanToken = [token lowercaseString];

        // Strip 0x prefix
        if ([cleanToken hasPrefix:@"0x"]) {
            cleanToken = [cleanToken substringFromIndex:2];
        }

        if (cleanToken.length == 0) continue;

        // Packed hex
        if (cleanToken.length > 2 && cleanToken.length % 2 == 0) {
            for (NSUInteger i = 0; i < cleanToken.length; i += 2) {
                NSString *byteStr = [cleanToken substringWithRange:NSMakeRange(i, 2)];
                unsigned int byteVal = 0;
                NSScanner *scanner = [NSScanner scannerWithString:byteStr];
                BOOL success = [scanner scanHexInt:&byteVal];
                if (!success || !scanner.isAtEnd) {
                    return NULL;
                }
                [byteArray addObject:@(byteVal & 0xFF)];
            }
        } else if (cleanToken.length <= 2) {
            // Single byte
            unsigned int byteVal = 0;
            NSScanner *scanner = [NSScanner scannerWithString:cleanToken];
            BOOL success = [scanner scanHexInt:&byteVal];
            if (!success || !scanner.isAtEnd) {
                return NULL;
            }
            [byteArray addObject:@(byteVal & 0xFF)];
        } else {
            // Invalid hex length (e.g. odd-length packed hex)
            return NULL;
        }
    }

    NSUInteger count = byteArray.count;
    if (outLength) *outLength = count;

    if (count == 0) return NULL;

    // Allocate raw byte array
    uint8_t *bytes = malloc(count * sizeof(uint8_t));
    if (!bytes) return NULL; // allocation failed

    for (NSUInteger i = 0; i < count; i++) {
        bytes[i] = byteArray[i].unsignedCharValue;
    }

    return bytes;
}



#pragma mark Extra Stuff
- (void)setRepresentedObject:(id)representedObject {
    [super setRepresentedObject:representedObject];

    // Update the view, if already loaded.
}


@end
