/***********************************************************************************
 *  File name   : ScanController.m
 *  Description : View controller for cmbSDK sample application
 *  Comments    :
 *
 *    This sample application has been designed to show how simple it is to write
 *    a single application with the cmbSDK that will work with any its supported
 *    devices: an MX-1xxx mobile terminal or just the built-in camera of a phone or tablet.
 *
 *    It implements a single view with a pick list for choosing which type of
 *    device to connection to. In our example, we are using the lifecycle of
 *    the viewControl to connect and disconnect to the device (since this is a
 *    single view application); however, this is not the only way to use the SDK;
 *    in a multiple view application you may not want to tie connecting and
 *    disconnecting to a viewController, but to a helper class or otherwise.
 *
 *  Copyright(C) : 2017-present, Cognex Corporation. All rights reserved.
 ***********************************************************************************/

#import "ScannerController.h"
#import <cmbSDK/cmbSDK.h>

@interface ScannerController () <CMBReaderDeviceDelegate, UITableViewDelegate, UITableViewDataSource>

@property CMBReaderDevice *readerDevice;
@property bool isScanning;

#pragma mark UI OUTLETS
@property (weak, nonatomic) IBOutlet UIButton *btnScan;
@property (weak, nonatomic) IBOutlet UILabel *lblConnection;
@property (strong, nonatomic) IBOutlet UITableView *tvResults;
@property (weak, nonatomic) IBOutlet UILabel *lblVersion;

@end

@implementation ScannerController


//----------------------------------------------------------------------------
// If usePreconfiguredDeviceType is YES, then the app will create a reader
// using the values of deviceClass/cameraMode. Otherwise, the app presents
// a pick list for the user to select either MX-1xxx or the built-in camera.
//----------------------------------------------------------------------------
const BOOL usePreconfiguredDeviceType = NO;
DataManDeviceClass deviceClass = DataManDeviceClass_MX;
CDMCameraMode cameraMode = kCDMCameraModeNoAimer;

//----------------------------------------------------------------------------
// The cmbSDK supports multi-code scanning (scanning multiple barcodes at 
// one time); thus scan results are returned as an array. Note that 
// this sample app does not demonstrate the use of these multi-code features.
//----------------------------------------------------------------------------
NSArray<CMBReadResult*>* scanResults;

#pragma mark OBSERVER METHODS

//----------------------------------------------------------------------------
// When an applicaiton is suspended, the connection to the scanning device is
// automatically closed by iOS; thus when we are resumed (become active) we 
// have to restore the connection (assuming we had one). This is the observer
// we will use to do this.
//----------------------------------------------------------------------------
-(void)appBecameActive {

    if (self.readerDevice != nil &&
        self.readerDevice.availability == CMBReaderAvailibilityAvailable &&
        self.readerDevice.connectionState != CMBConnectionStateConnecting &&
		self.readerDevice.connectionState != CMBConnectionStateConnected)
    {
        [self.readerDevice connectWithCompletion:^(NSError *error) {
            if (error) {
                // handle connection error
            }
        }];
    }
}

#pragma mark VIEWCONTROLLER METHODS

-(void)viewDidLoad {
    [super viewDidLoad];
    
    scanResults = @[];
    [self.tvResults setDelegate:self];
    [self.tvResults setDataSource:self];
    
    // Get cmbSDK version number
    self.lblVersion.text = [CDMDataManSystem getVersion];
    
	// Add our observer for when the app becomes active (to reconnect if necessary)
	[[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appBecameActive)
                                                 name:UIApplicationDidBecomeActiveNotification object:nil];
}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    [self updateUIByConnectionState];
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.readerDevice == nil) {
        if (usePreconfiguredDeviceType) {
            [self createReaderDevice];
        } else {
            [self selectDeviceFromPicker];
        }
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    // As the view is closed, stop any active scanning
    if (self.readerDevice != nil &&
        self.readerDevice.connectionState == CMBConnectionStateConnected)
        [self.readerDevice stopScanning];
}

-(void)viewDidDisappear:(BOOL)animated
{
    // If we have connection to a reader, disconnect
    if (self.readerDevice != nil &&
        self.readerDevice.connectionState == CMBConnectionStateConnected)
        [self.readerDevice disconnect];
}

#pragma mark UPDATE UI

// Update the UI of the app (scan button, connection state label) depending on the current self.readerDevice connection state
-(void)updateUIByConnectionState
{
    if (self.readerDevice != nil && 
	    self.readerDevice.connectionState == CMBConnectionStateConnected)
    {
        self.lblConnection.text = @"  Connected  ";
        self.lblConnection.backgroundColor = [UIColor colorWithRed: 0.00 green: 0.39 blue: 0.00 alpha: 1.0];
        
        self.btnScan.enabled = YES;
    }
    else
    {
        self.lblConnection.text = @"  Disconnected  ";
        self.lblConnection.backgroundColor = [UIColor redColor];
        
        self.btnScan.enabled = NO;
    }
    
    [self.btnScan setSelected:self.isScanning];
}

-(void)clearResult
{
    scanResults = @[];
    [self.tvResults reloadData];
}

#pragma mark SELECT DEVICE

-(IBAction)deviceButtonTapped:(id)sender
{
    [self selectDeviceFromPicker];
}

//----------------------------------------------------------------------------
// This is the pick list for choosing the type of reader connection
//----------------------------------------------------------------------------
-(void)selectDeviceFromPicker
{
    UIAlertController *devicePicker = [UIAlertController alertControllerWithTitle:@"Select device" message:nil preferredStyle:UIAlertControllerStyleActionSheet];

	//
	// Option for scanning with an MX-1000 or MX-1502
	//
    [devicePicker addAction:[UIAlertAction actionWithTitle:@"MX Scanner (MX-1xxx)" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        deviceClass = DataManDeviceClass_MX;
        [self createReaderDevice];
    }]];
    
	//
	// Option for scanning with the phone/tablet's builtin camera
	//
    [devicePicker addAction:[UIAlertAction actionWithTitle:@"Phone Camera" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
	    deviceClass = DataManDeviceClass_PhoneCamera;
        cameraMode = kCDMCameraModeNoAimer;
        [self createReaderDevice];
    }]];
    
    [devicePicker addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
    
    if (devicePicker.popoverPresentationController) {
        devicePicker.popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItem;
    }
    
    [self presentViewController:devicePicker animated:YES completion:nil];
}

// Create a self.readerDevice using the selected option from "selectDeviceFromPicker"
// Optionally, if you don't want to use multiple device types, you can remove the switch statement and keep only the one type that you need
-(void)createReaderDevice
{
	if (self.readerDevice != nil) {
		[self.readerDevice disconnect];
    }
	
    switch (deviceClass) {
            //***************************************************************************************
            // Create an MX-1xxx reader  (note that no license key in needed)
            //***************************************************************************************
        case DataManDeviceClass_MX:
            self.readerDevice = [CMBReaderDevice readerOfMXDevice];
            break;

            //***************************************************************************************
            // Create a camera reader
            //
            // NOTE: if we're scanning using the built-in camera
            //       of the mobile phone or tablet, then the SDK requires a license key. Refer to
            //       the SDK's documentation on obtaining a license key as well as the methods for
            //       passing the key to the SDK (in this example, we're relying on an entry in
            //       plist.info--there are also readerOfDeviceCamera methods where it can be passed
            //       as a parameter).
            //***************************************************************************************
        case DataManDeviceClass_PhoneCamera:
        default:
            self.readerDevice = [CMBReaderDevice readerOfDeviceCameraWithCameraMode:cameraMode previewOptions:0 previewView:nil];
            break;
    }
    
    self.readerDevice.delegate = self;
    [self connectToReaderDevice];
    [self updateUIByConnectionState];
}

#pragma mark CONNECT - DISCONNECT

// Before the self.readerDevice can be configured or used, a connection needs to be established
-(void)connectToReaderDevice
{
    if (self.readerDevice.availability == CMBReaderAvailibilityAvailable && self.readerDevice.connectionState == CMBConnectionStateDisconnected)
    {
        [self.readerDevice connectWithCompletion:^(NSError *error) {
            if (error)
            {
                [self showAlertWithTitle:@"Failed to connect" message:nil];
            }
        }];
    }
}

//----------------------------------------------------------------------------
// This is an example of configuring the device. In this sample application, we
// configure the device every time the connection state changes to connected (see
// the connectionStateDidChangeOfReader delegate below), as this is the best
// way to garentee it is setup the way we want it. Not only does this garentee
// that the device is configured when we initially connect, but also covers the
// case where an MX reader has hibernated (and we're reconnecting)--unless 
// setting changes are explicitly saved to non-volatile memory, they can be lost
// when the MX hibernates or reboots.
//
// These are just example settings; in your own application you will want to 
// consider which setting changes are optimal for your application. It is 
// important to note that the different supported devices have different, out
// of the box defaults:
//
//    * MX-1xxx Mobile Terminals have the following symbologies enabled by default:
//        - Data Matrix
//        - UPC/EAN
//        - Code 39
//        - Code 93
//        - Code 128
//        - Interleaved 2 of 5
//        - Codabar
//    * camera reader has NO symbologies enabled by default
//
// For the best scanning performance, it is recommended to only have the barcode
// symbologies enabled that your application actually needs to scan. If scanning
// with an MX-1xxx, that may mean disabling some of the defaults (or enabling 
// symbologies that are off by default).
// 
// Keep in mind that this sample application works with all three types of devices,
// so in our example below we show explicitly enabling symbologies as well as
// explicitly disabling symbologies (even if those symbologies may already be on/off
// for the device being used). 
//
// We also show how to send configuration commands that may be device type
// specific--again, primarily for demonstration purposes.
//----------------------------------------------------------------------------
-(void)configureReaderDevice
{
    //----------------------------------------------
    // Explicitly enable the symbologies we need
    //----------------------------------------------
    [self.readerDevice setSymbology:CMBSymbologyDataMatrix enabled:YES completion:^(NSError *error){
        if (error)
        {
            NSLog(@"FALIED TO ENABLE [CMBSymbologyDataMatrix], %@", error.description);
        }
    }];
    [self.readerDevice setSymbology:CMBSymbologyC128 enabled:YES completion:^(NSError *error){
        if (error)
        {
            NSLog(@"FALIED TO ENABLE [CMBSymbologyC128], %@", error.description);
        }
    }];
    [self.readerDevice setSymbology:CMBSymbologyUpcEan enabled:YES completion:^(NSError *error){
        if (error)
        {
            NSLog(@"FALIED TO ENABLE [CMBSymbologyUpcEan], %@", error.description);
        }
    }];
    
    //-------------------------------------------------------
    // Explicitly disable symbologies we know we don't need
    //-------------------------------------------------------
    [self.readerDevice setSymbology:CMBSymbologyCodaBar enabled:NO completion:^(NSError *error){
        if (error)
        {
            NSLog(@"FALIED TO DISABLE [CMBSymbologyCodaBar], %@", error.description);
        }
    }];
    [self.readerDevice setSymbology:CMBSymbologyC93 enabled:NO completion:^(NSError *error){
        if (error)
        {
            NSLog(@"FALIED TO DISABLE [CMBSymbologyC93], %@", error.description);
        }
    }];
    
	//---------------------------------------------------------------------------
    // Below are examples of sending DMCC commands and getting the response
	//---------------------------------------------------------------------------
    [self.readerDevice.dataManSystem sendCommand:@"GET DEVICE.TYPE" withCallback:^(CDMResponse *response) {
        if (response.status == DMCC_STATUS_NO_ERROR) {
            NSLog(@"Device type: %@", response.payload);
        }
    }];
    [self.readerDevice.dataManSystem sendCommand:@"GET DEVICE.FIRMWARE-VER" withCallback:^(CDMResponse *response) {
        if (response.status == DMCC_STATUS_NO_ERROR) {
            NSLog(@"Firmware version: %@", response.payload);
        }
    }];
    
    //---------------------------------------------------------------------------
    // We are going to explicitly turn off image results (although this is the
    // default). The reason is that enabling image results with an MX-1xxx
    // scanner is not recommended unless your application needs the scanned
    // image--otherwise scanning performance can be impacted.
    //---------------------------------------------------------------------------
    self.readerDevice.imageResultEnabled = NO;
    self.readerDevice.SVGResultEnabled = NO;
    
    //---------------------------------------------------------------------------
    // Device specific configuration examples
    //---------------------------------------------------------------------------

	//---------------------------------------------------------------------------
	// Phone/tablet
	//---------------------------------------------------------------------------
    if (self.readerDevice.deviceClass == DataManDeviceClass_PhoneCamera) {
		// Set the SDK's decoding effort to level 3
        [self.readerDevice.dataManSystem sendCommand:@"SET DECODER.EFFORT 3" withCallback:nil];

	//---------------------------------------------------------------------------
	// MX-1xxx
	//---------------------------------------------------------------------------
    } else if (self.readerDevice.deviceClass == DataManDeviceClass_MX) {
        //---------------------------------------------------------------------------
        // Save our configuration to non-volatile memory (on an MX-1xxx; for the
        // phone camera, this has no effect). However, if the MX hibernates or is
        // rebooted, our settings will be retained.
        //---------------------------------------------------------------------------
        [self.readerDevice.dataManSystem sendCommand:@"CONFIG.SAVE" withCallback:nil];
	}
}

- (IBAction)toggleScanner:(UIButton*)sender
{
    if (self.isScanning)
    {
        [self.readerDevice stopScanning];
    }
    else
    {
        [self.readerDevice startScanning];
    }
    
    self.isScanning = !self.isScanning;
    [self.btnScan setSelected:self.isScanning];
}

#pragma mark MX Delegate methods

// This is called when a MX-1xxx device has became available (USB cable was plugged, or MX device was turned on),
// or when a MX-1xxx that was previously available has become unavailable (USB cable was unplugged, turned off due to inactivity or battery drained)
- (void)availabilityDidChangeOfReader:(CMBReaderDevice *)reader
{
    [self clearResult];
    
    if (reader.availability != CMBReaderAvailibilityAvailable)
    {
        [self showAlertWithTitle:@"Device became unavailable" message:nil];
    }
    else if (self.readerDevice.availability == CMBReaderAvailibilityAvailable) {
        [self connectToReaderDevice];
    }
}

// This is called when a connection with the self.readerDevice has been changed.
// The self.readerDevice is usable only in the "CMBConnectionStateConnected" state
- (void)connectionStateDidChangeOfReader:(CMBReaderDevice *)reader
{
    self.isScanning = NO;
    [self clearResult];
    
    if (self.readerDevice.connectionState == CMBConnectionStateConnected){
		// We just connected, so now configure the device how we want it
        [self configureReaderDevice];
    }
    
    [self updateUIByConnectionState];
}

// This is called after scanning has completed, either by detecting a barcode, canceling the scan by using the on-screen button or a hardware trigger button, or if the scanning timed-out
- (void)didReceiveReadResultFromReader:(CMBReaderDevice *)reader results:(CMBReadResults *)readResults
{
    self.isScanning = false;
    [self.btnScan setSelected:self.isScanning];
    
    if (readResults.subReadResults && readResults.subReadResults.count > 0) {
        scanResults = readResults.subReadResults;
        [self.tvResults reloadData];
    } else if (readResults.readResults.count > 0){
        CMBReadResult *result = readResults.readResults.firstObject;
        scanResults = @[result];
        [self.tvResults reloadData];
    }
}

#pragma mark RESULTS TABLE

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"resultCell" forIndexPath:indexPath];
    if (scanResults.count == 0) {
        cell.textLabel.text = @"";
        cell.detailTextLabel.text = @"";
    }else{
        CMBReadResult *result = scanResults[indexPath.row];
        if (result.goodRead) {
            cell.textLabel.text = result.readString;
            cell.detailTextLabel.text = [self displayStringForSymbology:result.symbology];
        }else{
            cell.textLabel.text = @"NO READ";
            cell.detailTextLabel.text = @"";
        }
    }

    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return scanResults.count;
}

- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath {
    return NO;
}

#pragma mark UTILITY

// Get a readable string from a CMBSymbology value
- (NSString *)displayStringForSymbology:(CMBSymbology)symbology
{
    switch (symbology) {
        case CMBSymbologyDataMatrix: return @"DATAMATRIX";
        case CMBSymbologyQR: return @"QR";
        case CMBSymbologyC128: return @"C128";
        case CMBSymbologyUpcEan: return @"UPC-EAN";
        case CMBSymbologyC39: return @"C39";
        case CMBSymbologyC93: return @"C93";
        case CMBSymbologyC11: return @"C11";
        case CMBSymbologyI2o5: return @"I2O5";
        case CMBSymbologyCodaBar: return @"CODABAR";
        case CMBSymbologyEanUcc: return @"EAN-UCC";
        case CMBSymbologyPharmaCode: return @"PHARMACODE";
        case CMBSymbologyMaxicode: return @"MAXICODE";
        case CMBSymbologyPdf417: return @"PDF417";
        case CMBSymbologyMicropdf417: return @"MICROPDF417";
        case CMBSymbologyDatabar: return @"DATABAR";
        case CMBSymbologyPostnet: return @"POSTNET";
        case CMBSymbologyPlanet: return @"PLANET";
        case CMBSymbologyFourStateJap: return @"4STATE-JAP";
        case CMBSymbologyFourStateAus: return @"4STATE-AUS";
        case CMBSymbologyFourStateUpu: return @"4STATE-UPU";
        case CMBSymbologyFourStateImb: return @"4STATE-IMB";
        case CMBSymbologyVericode: return @"VERICODE";
        case CMBSymbologyRpc: return @"RPC";
        case CMBSymbologyMsi: return @"MSI";
        case CMBSymbologyAzteccode: return @"AZTECCODE";
        case CMBSymbologyDotcode: return @"DOTCODE";
        case CMBSymbologyC25: return @"C25";
        case CMBSymbologyC39ConvertToC32: return @"C39-CONVERT-TO-C32";
        case CMBSymbologyOcr: return @"OCR";
        case CMBSymbologyFourStateRmc: return @"4STATE-RMC";
        case CMBSymbologyTelepen: return @"TELEPEN";
        default: return @"UNKNOWN";
    }
}

-(void)showAlertWithTitle:(NSString*)title message:(NSString*)message{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

@end
