import 'dart:io';

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';

import 'package:cmbsdk_flutter/cmbsdk_flutter.dart';
import 'package:cmbsdk_flutter/cmbsdk_flutter_constants.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  runApp(
    new MaterialApp(
      home: new MyApp(),
    ),
  );
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  //----------------------------------------------------------------------------
  // 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.
  //----------------------------------------------------------------------------
  List<dynamic> _resultsArray = [];

  bool _isScanning = false;
  String _cmbSDKVersion = 'N/A';
  String _connectionStatusText = 'Disconnected';
  Color _connectionStatusBackground = Colors.redAccent;
  String _scanButtonText = '(NOT CONNECTED)';
  bool _scanButtonEnabled = false;
  late SharedPreferences prefs;
  final String pairedPeripheralUUIDKey = 'pairedPeripheralUUID';
  BuildContext? deviceDialogContext;

  //----------------------------------------------------------------------------
  // If USE_PRECONFIGURED_DEVICE is YES, then the app will create a reader
  // using the values of device/cameraMode. Otherwise, the app presents
  // a pick list for the user to select either MX-1xxx or the built-in camera.
  //----------------------------------------------------------------------------
  final bool _usePreconfiguredDevice = false;
  int _deviceType = cmbDeviceType.MXReader;
  int _cameraMode = cmbCameraMode.NoAimer;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);

    initCmbSDK();
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState appLifecycleState) {
    switch (appLifecycleState) {
      case AppLifecycleState.resumed:
        // dismiss the device selector dialog
        if (deviceDialogContext != null) {
          Navigator.pop(deviceDialogContext!);
        }

        cmb
            .connect()
            .catchError((error, stackTrace) => print('${error.message}'));
        break;
      case AppLifecycleState.paused:
        cmb
            .disconnect()
            .catchError((error, stackTrace) => print('${error.message}'));
        break;
    }
  }

  Future<void> savePairedPeripheral() async {
    final String? pairedperipheralUUID = await cmb
        .getPairedBluetoothDevice()
        .catchError((error, stackTrace) => print(error.message));

    if (pairedperipheralUUID != null)
      prefs.setString(pairedPeripheralUUIDKey, pairedperipheralUUID);
  }

  Future<void> initCmbSDK() async {
    String cmbSDKVersion = 'N/A';

    prefs = await SharedPreferences.getInstance();

    // 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)
    cmb.setAvailabilityChangedListener((availability) {
      if (availability == cmbAvailability.Available.index) {
        cmb
            .connect()
            .catchError((error, stackTrace) => print('${error.message}'));
      } else {
        showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              content: Text('Device became unavailable'),
              actions: [
                TextButton(
                  child: Text('OK'),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              ],
            );
          },
        );
      }
    });

    // This is called when a connection with the reader has been changed.
    // The reader is usable only in the "Connected" state
    cmb.setConnectionStateChangedListener((state) {
      if (state == cmbConnectionState.Connected.index) {
        if (Platform.isIOS && _deviceType == cmbDeviceType.Bluetooth) {
          savePairedPeripheral();
        }

        _configureReaderDevice();

        _updateUIByConnectionState(cmbConnectionState.Connected);
      } else {
        _updateUIByConnectionState(cmbConnectionState.Disconnected);
      }

      if (!mounted) return;

      setState(() {
        _isScanning = false;
        _resultsArray = [];
      });
    });

    // 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
    cmb.setReadResultReceivedListener((resultJSON) {
      final Map<String, dynamic> resultMap = jsonDecode(resultJSON);
      final List<dynamic> resultsArray = resultMap['results'];

      if (!mounted) return;

      setState(() {
        _resultsArray = resultsArray;
      });
    });

    // It will return TRUE in the result if the scanning process is STARTED and false if it's STOPPED
    cmb.setScanningStateChangedListener((scanningState) {
      if (!mounted) return;

      setState(() {
        _isScanning = scanningState;
        _scanButtonText = _isScanning ? 'STOP SCANNING' : 'START SCANNING';
      });
    });

    // Get cmbSDK version number
    cmbSDKVersion = await cmb.sdkVersion;

    //initialize and connect to MX/Phone Camera here
    if (_usePreconfiguredDevice) {
      _createReaderDevice();
    } else {
      _selectDeviceFromPicker();
    }

    if (!mounted) return;

    setState(() {
      _cmbSDKVersion = cmbSDKVersion;
    });
  }

  // Update the UI of the app (scan button, connection state label) depending on the current connection state
  void _updateUIByConnectionState(cmbConnectionState state) {
    String connectionStatusText = _connectionStatusText;
    Color connectionStatusBackground = _connectionStatusBackground;
    String scanButtonText = _scanButtonText;
    bool scanButtonEnabled = _scanButtonEnabled;

    if (state == cmbConnectionState.Connected) {
      connectionStatusText = 'Connected';
      connectionStatusBackground = Colors.lightGreen;

      scanButtonText = 'START SCANNING';
      scanButtonEnabled = true;
    } else {
      connectionStatusText = 'Disconnected';
      connectionStatusBackground = Colors.redAccent;

      scanButtonText = '(NOT CONNECTED)';
      scanButtonEnabled = false;
    }

    if (!mounted) return;

    setState(() {
      _connectionStatusText = connectionStatusText;
      _connectionStatusBackground = connectionStatusBackground;

      _scanButtonText = scanButtonText;
      _scanButtonEnabled = scanButtonEnabled;
    });
  }

  //----------------------------------------------------------------------------
  // This is the pick list for choosing the type of reader connection
  //----------------------------------------------------------------------------
  Future<void> _selectDeviceFromPicker() async {
    int deviceType = _deviceType;
    int cameraMode = _cameraMode;

    List<Widget> deviceOptions = <Widget>[
      SimpleDialogOption(
        onPressed: () {
          Navigator.pop(context, cmbDeviceType.Bluetooth);
        },
        child: const Text('Paired Bluetooth Scanner'),
      ),
      SimpleDialogOption(
        onPressed: () {
          Navigator.pop(context, cmbDeviceType.MXReader);
        },
        child: const Text('MX Scanner (MX-1xxx)'),
      ),
      SimpleDialogOption(
        onPressed: () {
          Navigator.pop(context, cmbDeviceType.Camera);
        },
        child: const Text('Phone Camera'),
      ),
      SimpleDialogOption(
        onPressed: () {
          Navigator.pop(context, null);
        },
        child: const Text('Cancel'),
      ),
    ];

    if (Platform.isIOS) {
      deviceOptions.insert(
          0,
          SimpleDialogOption(
            onPressed: () {
              prefs.remove(pairedPeripheralUUIDKey);
              Navigator.pop(context, cmbDeviceType.Bluetooth);
            },
            child: const Text('New Bluetooth Scanner'),
          ));
    }

    bool dismissed = false;

    switch (await showDialog<int>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          deviceDialogContext = context;
          return SimpleDialog(
            title: const Text('Select device'),
            children: deviceOptions,
          );
        })) {
      case cmbDeviceType.Bluetooth:
        deviceType = cmbDeviceType.Bluetooth;
        break;
      case cmbDeviceType.MXReader:
        deviceType = cmbDeviceType.MXReader;
        break;
      case cmbDeviceType.Camera:
        deviceType = cmbDeviceType.Camera;
        cameraMode = cmbCameraMode.NoAimer;
        // ...
        break;
      default:
        // dialog dismissed
        dismissed = true;
        break;
    }

    deviceDialogContext = null;

    if (!mounted) return;

    setState(() {
      _deviceType = deviceType;
      _cameraMode = cameraMode;
    });

    if (dismissed == false) {
      _createReaderDevice();
    }
  }

  // Create a reader using the selected option from "selectDeviceFromPicker"
  void _createReaderDevice() {
    if (_deviceType == cmbDeviceType.Camera) {
      cmb.setCameraMode(_cameraMode);
      cmb.setPreviewOptions(cmbPrevewiOption.Defaults);

      cmb.registerSDK("SDK_KEY");
    } else if (_deviceType == cmbDeviceType.Bluetooth) {
      if (Platform.isIOS) {
        final String? pairedPeripheralUUID =
            prefs.getString(pairedPeripheralUUIDKey);

        if (pairedPeripheralUUID != null) {
          cmb.setPairedBluetoothDevice(pairedPeripheralUUID);
        } else {
          cmb.setPairedBluetoothDevice(""); // clear it
        }
      } else
        cmb.setPairedBluetoothDevice("BT_Device_MAC_Address");
    }

    cmb.loadScanner(_deviceType).then((value) {
      cmb.connect().then((value) {
        _updateUIByConnectionState(cmbConnectionState.Connected);
      }).catchError((error, stackTrace) {
        _updateUIByConnectionState(cmbConnectionState.Disconnected);
      });
    }).catchError((error, stackTrace) => print('${error.message}'));
  }

  //----------------------------------------------------------------------------
  // 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 ConnectionStateChanged event), 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 scanner 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 scanner 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
    //----------------------------------------------
    cmb
        .setSymbologyEnabled(cmbSymbology.DataMatrix, true)
        .then((value) => print('DataMatrix enabled'))
        .catchError((error, stackTrace) =>
            print('DataMatrix NOT enabled. ${error.message}'));

    cmb
        .setSymbologyEnabled(cmbSymbology.C128, true)
        .catchError((error, stackTrace) => print('${error.message}'));
    cmb
        .setSymbologyEnabled(cmbSymbology.UpcEan, true)
        .catchError((error, stackTrace) => print('${error.message}'));

    //-------------------------------------------------------
    // Explicitly disable symbologies we know we don't need
    //-------------------------------------------------------
    cmb
        .setSymbologyEnabled(cmbSymbology.CodaBar, false)
        .then((value) => print('CodaBar disabled'))
        .catchError((error, stackTrace) =>
            print('CodaBar NOT disabled. ${error.message}'));

    cmb
        .setSymbologyEnabled(cmbSymbology.C93, false)
        .catchError((error, stackTrace) => print('${error.message}'));

    //---------------------------------------------------------------------------
    // Below are examples of sending DMCC commands and getting the response
    //---------------------------------------------------------------------------
    cmb
        .sendCommand('GET DEVICE.TYPE')
        .then((value) => print('$value'))
        .catchError((error, stackTrace) => print('${error.message}'));

    cmb
        .sendCommand('GET DEVICE.FIRMWARE-VER')
        .then((value) => print('$value'))
        .catchError((error, stackTrace) => print('${error.message}'));

    //---------------------------------------------------------------------------
    // 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.
    //---------------------------------------------------------------------------
    cmb
        .enableImage(false)
        .catchError((error, stackTrace) => print('${error.message}'));
    cmb
        .enableImageGraphics(false)
        .catchError((error, stackTrace) => print('${error.message}'));

    //---------------------------------------------------------------------------
    // Device specific configuration examples
    //---------------------------------------------------------------------------
    if (_deviceType == cmbDeviceType.Camera) {
      //---------------------------------------------------------------------------
      // Phone/tablet
      //---------------------------------------------------------------------------

      // Set the SDK's decoding effort to level 3
      cmb
          .sendCommand("SET DECODER.EFFORT 3")
          .catchError((error, stackTrace) => print('${error.message}'));
    } else if (_deviceType == cmbDeviceType.MXReader) {
      //---------------------------------------------------------------------------
      // MX-1xxx
      //---------------------------------------------------------------------------

      //---------------------------------------------------------------------------
      // Save our configuration to non-volatile memory
      // If the MX hibernates or is rebooted, our settings will be retained.
      //---------------------------------------------------------------------------
      cmb
          .sendCommand("CONFIG.SAVE")
          .catchError((error, stackTrace) => print('${error.message}'));
    } else if (_deviceType == cmbDeviceType.Bluetooth) {
      //---------------------------------------------------------------------------
      // DM-8700
      //---------------------------------------------------------------------------

      //---------------------------------------------------------------------------
      // Save our configuration to non-volatile memory
      // If the Reader hibernates or is rebooted, our settings will be retained.
      //---------------------------------------------------------------------------
      cmb
          .sendCommand("CONFIG.SAVE")
          .catchError((error, stackTrace) => print('${error.message}'));
    }
  }

  void _toggleScanner() {
    if (_isScanning) {
      cmb
          .stopScanning()
          .catchError((error, stackTrace) => print('${error.message}'));
    } else {
    
      cmb
          .startScanning()
          .catchError((error, stackTrace) => print('${error.message}'));
          
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: Color(0xff333333),
      appBar: AppBar(
        title: const Text('CMBCameraDemo'),
        actions: <Widget>[
          Padding(
              padding: const EdgeInsets.all(20.0),
              child: GestureDetector(
                onTap: () {
                  _selectDeviceFromPicker();
                },
                child: const Text('DEVICE'),
              )),
        ],
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Expanded(
                child: ListView.separated(
                    padding: const EdgeInsets.all(10),
                    itemCount: _resultsArray.length,
                    itemBuilder: (BuildContext context, int index) {
                      return Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Text('${_resultsArray[index]['readString']}',
                                style: TextStyle(
                                    color: Colors.white, fontSize: 18)),
                            Text('${_resultsArray[index]['symbologyString']}',
                                style: TextStyle(color: Colors.grey)),
                          ]);
                    },
                    separatorBuilder: (BuildContext context, int index) =>
                        const Divider(thickness: 1))),
            Padding(
                padding: const EdgeInsets.all(10),
                child: Container(
                    width: double.infinity,
                    height: 50,
                    child: ElevatedButton(
                      onPressed: _scanButtonEnabled
                          ? () {
                              _toggleScanner();
                            }
                          : null,
                      child: Text(_scanButtonText),
                      style: ElevatedButton.styleFrom(
                          backgroundColor: Color(0xfffadb04),
                          foregroundColor: Colors.black,
                          disabledBackgroundColor: Color(0xfffadb04)),
                    ))),
            Padding(
                padding: const EdgeInsets.all(5),
                child: Row(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text(_cmbSDKVersion,
                        style: TextStyle(
                          color: Colors.white,
                        )),
                    Container(
                      color: _connectionStatusBackground,
                      child: Padding(
                          padding: EdgeInsets.all(2),
                          child: Text(_connectionStatusText,
                              style: TextStyle(
                                color: Colors.white,
                              ))),
                    )
                  ],
                ))
          ],
        ),
      ),
    );
  }
}
