using System;
//using System.Linq;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using WinRT.BxlComponent.Plugin.BxlMpXamarinSDK.Abstractions;

using Windows.Devices.Enumeration;
using System.Collections.ObjectModel;
using Windows.Foundation;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Rfcomm;
using System.Threading;
using System.Collections.Generic;
using Windows.Storage;
using Windows.ApplicationModel.Core;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Linq;
using System.Collections;

namespace WinRT.BxlComponent.Plugin.BxlMpXamarinSDK
{
    internal partial class MPosConnectionImplementation : IMPosConnection
    {
        private MPosDeviceType _DeviceType { get; set; }
        public MPosDataParser _DataParser { get; private set; }
        public MPosInterfaceType _ConnectionType { get; private set; }
        public MPosCommandMode _CommandMode { get; set; }
        private string _HostAddress { get; set; }
        private string _ServiceName { get; set; }
        public string _ConnectionInfo { get; set; }
        private StreamSocket _Streamsocket { get; set; }
        private bool _IsConnected { get; set; } = false;
        private bool _WritingInProgress { get; set; } = false;
        public bool _PageModeInOut { get; set; }
        public bool _TransactionModeInOut { get; set; }
        // Instantiate a Singleton of the Semaphore with a value of 1. This means that only 1 thread can be granted access at a time
        private static SemaphoreSlim _SocketSemaphore = new SemaphoreSlim(1, 1);
        private string _DebugId;
        public MPosConnectionImplementation(MPosInterfaceType connectionType, string connectionInfo, int commandMode)
        {
            _DebugId = DebugHelper.GetId(this);
            _ConnectionType = connectionType;
            _CommandMode = (MPosCommandMode)commandMode;
            if (!string.IsNullOrEmpty(connectionInfo))
                _ConnectionInfo = connectionInfo;
            switch ((MPosCommandMode)commandMode)
            {
                case MPosCommandMode.MPOS_COMMAND_MODE_DIRECT:
                    _DataParser = new MPosDataParser(onStatusDataQueueUpdateEvent, onNormalDataQueueUpdateEvent);
                    break;
                case MPosCommandMode.MPOS_COMMAND_MODE_BGATE:
                    _DataParser = new MPosBgateDataParser(onStatusDataQueueUpdateEvent, onNormalDataQueueUpdateEvent);
                    break;
            }
        }
        public bool isConnected()
        {
            return _IsConnected;
        }
        public async Task<bool> openAsync(MPosDeviceType deviceType, uint timeout = 3 /*second*/)
        {
            int result = (int)MPosResult.MPOS_FAIL;
            Task<int> taskResult = null;
            _DeviceType = deviceType;

            MPosDebug.WriteLine("ConnectionImplementation openAsync : START", _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation openAsync : _IsConnected = " + _IsConnected, _DebugId);

            if (_IsConnected)
            {
                MPosDebug.WriteLine("ConnectionImplementation openAsync : END", _DebugId);
                return _IsConnected;
            }

            switch (_ConnectionType)
            {
                case MPosInterfaceType.MPOS_INTERFACE_WIFI:
                case MPosInterfaceType.MPOS_INTERFACE_ETHERNET:
                    _HostAddress = _ConnectionInfo;
                    _ServiceName = "9100";
                    if (_ConnectionInfo.Contains(":"))
                    {
                        string[] networkConnectionInfo = _ConnectionInfo.Split(':');
                        _HostAddress = networkConnectionInfo[0];
                        _ServiceName = networkConnectionInfo[1];
                    }
                    break;
                case MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH:
                    _HostAddress = _ConnectionInfo;
                    _ServiceName = "1";
                    break;
            }

            MPosDebug.WriteLine("ConnectionImplementation openAsync : _HostAddress = " + _HostAddress, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation openAsync : _ServiceName = " + _ServiceName, _DebugId);

            try
            {
                await _SocketSemaphore.WaitAsync();
                taskResult = connectSocketAsync(_HostAddress, _ServiceName, timeout);
                result = await taskResult;
                if (result == (int)MPosResult.MPOS_SUCCESS)
                {
                    if (await startReadTask(deviceType))
                        _IsConnected = true;
                }
                else
                {
                    MPosDebug.WriteLine("ConnectionImplementation openAsync : connectSocketAsync failed. result = " + result, _DebugId);
                }
            }
            finally
            {
                _SocketSemaphore.Release();
            }

            if (!_IsConnected)
            {
                cancelAllTasks();
                await closeSocket();
            }

            MPosDebug.WriteLine("ConnectionImplementation openAsync : _IsConnected = " + _IsConnected, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation openAsync : END", _DebugId);
            return _IsConnected;
        }

        private async Task<int> connectSocketAsync(string hostName, string serviceName, uint timeout)
        {
            int result = (int)MPosResult.MPOS_FAIL;
            MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : START");
            MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : hostName = " + hostName + ", serviceName = " + serviceName);

            try
            {
                bool hasPermission = true;
                switch (_ConnectionType)
                {
                    case MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH:
                        hasPermission = await MPosRfcommBluetoothHelper.checkBluetoothPermission(hostName);
                        break;
                    default:
                        break;
                }
                if (hasPermission == false)
                {
                    MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : NO PERMISSION", _DebugId);
                    MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : END", _DebugId);
                    return (int)MPosResult.MPOS_FAIL_NOT_PERMISSION_ALLOWD;
                }
            }
            catch (Exception ex)
            {
                // do nothing
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : ex = " + ex.Message, _DebugId);
            }

            try
            {
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : SocketSemaphore BEFORE ENTER", _DebugId);
                //await _SocketSemaphore.WaitAsync();
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : SocketSemaphore ENTERED", _DebugId);
                if (_Streamsocket != null)
                    _Streamsocket.Dispose();

                _Streamsocket = new StreamSocket();
                //_Streamsocket.Control.KeepAlive = true;
                //_Streamsocket.Control.NoDelay = true;
                _Streamsocket.Control.OutboundBufferSizeInBytes = 1024;

                RfcommDeviceService RFService = null;
                if (_ConnectionType == MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH)
                {
                    string rfcommId = await MPosRfcommBluetoothHelper.getRfcommId(hostName);
                    if (rfcommId != null)
                    {
                        var bluetoothDevice = await BluetoothDevice.FromIdAsync(rfcommId);

                        var rfcommServices = await bluetoothDevice.GetRfcommServicesForIdAsync(
                                    RfcommServiceId.FromUuid(RfcommServiceId.SerialPort.Uuid),
                                    BluetoothCacheMode.Uncached);
                        RFService = rfcommServices.Services[0];
                    }
                }

                using (var cts = new CancellationTokenSource())
                {
                    cts.CancelAfter((int)(timeout * 1000));
                    MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : ConnectAsync-1", _DebugId);
                    //await _Streamsocket.ConnectAsync(new HostName(hostName), serviceName, SocketProtectionLevel.PlainSocket).AsTask(cts.Token);
                    if (_ConnectionType == MPosInterfaceType.MPOS_INTERFACE_BLUETOOTH)
                    {
                        if (RFService == null)
                        {
                            await _Streamsocket.ConnectAsync(new HostName(hostName), serviceName).AsTask(cts.Token);
                        }
                        else
                        {
                            await _Streamsocket.ConnectAsync(RFService.ConnectionHostName, RFService.ConnectionServiceName).AsTask(cts.Token);
                        }
                    }
                    else
                    {
                        await _Streamsocket.ConnectAsync(new HostName(hostName), serviceName).AsTask(cts.Token);
                    }
                    MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : ConnectAsync-2", _DebugId);
                }

                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.LocalAddress = " + _Streamsocket.Information.LocalAddress, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.LocalPort = " + _Streamsocket.Information.LocalPort, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.RemoteHostName = " + _Streamsocket.Information.RemoteHostName, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.RemoteAddress = " + _Streamsocket.Information.RemoteAddress, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.RemotePort = " + _Streamsocket.Information.RemotePort, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : Information.RemoteServiceName = " + _Streamsocket.Information.RemoteServiceName, _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : OutboundBufferSizeInBytes = " + _Streamsocket.Control.OutboundBufferSizeInBytes.ToString(), _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : NoDelay = " + _Streamsocket.Control.NoDelay.ToString(), _DebugId);
                // need sleep time before reading/writing data from the device successfully.
                await Task.Delay(100);
                fireConnectedEvent();
                result = (int)MPosResult.MPOS_SUCCESS;
            }
            catch (Exception ex)
            {
                result = (int)MPosResult.MPOS_FAIL;
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : ex = " + ex.Message + ", ex.HResult = " + ex.HResult, _DebugId);
                if (_Streamsocket != null)
                    _Streamsocket.Dispose();
                _Streamsocket = null;
            }
            finally
            {
                MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : SocketSemaphore LEAVE", _DebugId);
                //_SocketSemaphore.Release();
            }

            MPosDebug.WriteLine("ConnectionImplementation connectSocketAsync : END", _DebugId);
            return result;
        }

        public async Task<bool> closeAsync(uint timeout = 3 /*second*/)
        {
            // 연결 가능 상태인지 확인하는 Task와 데이터 READ Task를 종료하고 나서, 연결 된 소켓 Dispose
            MPosDebug.WriteLine("ConnectionImplementation closeAsync : START");
            if (_CommandMode == MPosCommandMode.MPOS_COMMAND_MODE_BGATE)
            {
                if (MPosConnectionBuilderImplementation.GetDeviceCount() > 0)
                {
                    MPosDebug.WriteLine("ConnectionImplementation closeAsync : END");
                    return true;
                }
                else
                {
                    MPosDebug.WriteLine("ConnectionImplementation closeAsync : other device is using, count = " + MPosConnectionBuilderImplementation.GetDeviceCount());
                }
            }
            cancelAllTasks();
            var taskResult = closeSocket();
            MPosDebug.WriteLine("ConnectionImplementation closeAsync : END");
            return await taskResult;
        }
        private async Task<bool> closeSocket()
        {
            // 연결 된 소켓 Dispose
            MPosDebug.WriteLine("ConnectionImplementation closeSocket : START", _DebugId);

            try
            {
                await Task.Delay(250);
                await _SocketSemaphore.WaitAsync();

                _DataStored = null;
                _IsConnected = false;

                MPosDebug.WriteLine("ConnectionImplementation closeSocket : Semaphore ENTER", _DebugId);

                if (_Streamsocket != null)
                {
                    MPosDebug.WriteLine("ConnectionImplementation closeSocket : _streamsocket Dispose", _DebugId);
                    _Streamsocket.Dispose();
                    _Streamsocket = null;
                }
            }
            finally
            {
                MPosDebug.WriteLine("ConnectionImplementation closeSocket : Semaphore LEAVE", _DebugId);
                await Task.Delay(250);
                _SocketSemaphore.Release();
            }
            MPosDebug.WriteLine("ConnectionImplementation closeSocket : END", _DebugId);
            return true;
        }

        public async Task<byte[]> readAsync(uint timeoutInmilliseconds = 3000 /*milliseconds*/)
        {
            byte[] readResult = null;

            MPosDebug.WriteLine("ConnectionImplementation readAsync : START", _DebugId);

            if (_Streamsocket == null)
            {
                MPosDebug.WriteLine("ConnectionImplementation readAsync : Socket is null", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation readAsync : END", _DebugId);
                return null;
            }

            //await _socketSemaphore.WaitAsync();
            //MPosDebug.WriteLine("ConnectionImplementation readAsync : Semaphore ENTER", debugId);

            await readSocketAsync((readData) => {
                readResult = new byte[readData.Length];
                System.Buffer.BlockCopy(readData, 0, readResult, 0, readData.Length);

                bool isNormalData = false;
                if (_DeviceType == MPosDeviceType.MPOS_DEVICE_LABEL_PRINTER)
                    isNormalData = true; //라벨 프린터일 때는 ASB 데이터가 들어오지 않으므로..

                _DataParser.dataEnqueue(readResult, isNormalData);
            }, timeoutInmilliseconds);

            MPosDebug.WriteLine("ConnectionImplementation readAsync : END", _DebugId);
            return readResult;
        }
        private async Task<int> readSocketAsync(Action<byte[]> resultAction, uint timeoutInmilliseconds = 3000 /*milliseconds*/)
        {
            var readLength = 0;
            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : START", _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : timeoutInmilliseconds = " + timeoutInmilliseconds, _DebugId);

            if (_Streamsocket == null)
            {
                MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : _streamsocket is null", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : END", _DebugId);
                return 0;
            }

            await Task.Delay(100);

            using (var dataReader = new Windows.Storage.Streams.DataReader(_Streamsocket.InputStream))
            {
                dataReader.InputStreamOptions = InputStreamOptions.Partial;

                using (var cts = new CancellationTokenSource())
                {
                    cts.CancelAfter((int)(timeoutInmilliseconds));

                    try
                    {
                        MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : LoadAsync-1", _DebugId);
                        //var inBufferCnt = await dataReader.LoadAsync(4096);
                        uint inBufferCnt = 0;
                        if (timeoutInmilliseconds > 0)
                            inBufferCnt = (uint)await dataReader.LoadAsync(4096).AsTask(cts.Token);
                        else
                            inBufferCnt = (uint)await dataReader.LoadAsync(4096);
                        MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : LoadAsync-2", _DebugId);
                        MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : inBufferCnt = " + inBufferCnt, _DebugId);

                        if (inBufferCnt > 0)
                        {
                            var readData = new byte[inBufferCnt];
                            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : ReadBytes-1", _DebugId);
                            dataReader.ReadBytes(readData);
                            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : ReadBytes-2", _DebugId);
                            // Call the Action delegate for sending the data read. 
                            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : hexaString = " + MPosByteArrayHelper.hexaString(readData), _DebugId);
                            resultAction(readData);
                            readLength = readData.Length;
                        }
                    }
                    catch (Exception ex)
                    {
                        MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : onReadWriteErrorEvent");
                        onReadWriteErrorEvent((uint)ex.HResult, ex.Message);
                        //await _Streamsocket.CancelIOAsync();
                        dataReader.DetachStream();
                        //MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : ex = " + ex.Message + ", ex.HResult = " + ex.HResult.ToString("X2"), _DebugId);
                        if ((ex as System.Threading.Tasks.TaskCanceledException) != null)
                            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : System.Threading.Tasks.TaskCanceledException");
                    }
                }
                dataReader.DetachStream();
            }

            MPosDebug.WriteLine("ConnectionImplementation readSocketAsync : END", _DebugId);
            return readLength;
        }
        public async Task<int> writeAsync(int deviceId, byte[] data, bool storeData = true, bool addHeader = true, uint timeoutInmilliseconds = 3000 /*milliseconds*/)
        {
            MPosDebug.WriteLine("ConnectionImplementation writeAsync : START", _DebugId);

            if (_Streamsocket == null || data == null)
            {
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : Socket is null or data is null", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : END", _DebugId);
                return 0;
            }

            MPosDebug.WriteLine("ConnectionImplementation writeAsync : data.length = " + data.Length, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation writeAsync : _CommandMode = " + _CommandMode, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation writeAsync : _PageModeInOut = " + _PageModeInOut + " _TransactionModeInOut = " + _TransactionModeInOut, _DebugId);

#if DEBUG
            if (data.Length > 0)
            {
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : hexa data = " + MPosByteArrayHelper.hexaString(data), _DebugId);
            }
#endif
            // DLE EOT 또는 ASB 활성화 커맨드는 버퍼에 저장하지 않고 바로 전송한다.
            if ((_TransactionModeInOut && storeData) || (_PageModeInOut && storeData))
            {
                if (_DataStored != null)
                {
                    byte[] newBuffer = new byte[_DataStored.Length + data.Length];
                    System.Buffer.BlockCopy(_DataStored, 0, newBuffer, 0, _DataStored.Length);
                    System.Buffer.BlockCopy(data, 0, newBuffer, _DataStored.Length, data.Length);
                    _DataStored = newBuffer;
                }
                else
                {
                    _DataStored = data;
                }
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : Data size to add  (" + data.Length + ") bytes", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : stored data size to add  (" + _DataStored.Length + ") bytes", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : END", _DebugId);
                return data.Length;
            }

            //await _socketSemaphore.WaitAsync();
            //MPosDebug.WriteLine("ConnectionImplementation writeAsync : Semaphore ENTER", debugId);
#if DEBUG

            try
            {
                if (storeData)
                {
                    string hexadumpFileName = "HexaDump_";
                    //string time = DateTime.Now.ToString("HHmm_ss_fff");
                    string time = DateTime.Now.ToString("HHmm");
                    hexadumpFileName += time;
                    hexadumpFileName += ".bin";
                    StorageFolder localFolder = ApplicationData.Current.LocalFolder;
                    MPosDebug.WriteLine("ConnectionImplementation writeAsync : Path = " + localFolder.Path + ", hexadumpFileName = " + hexadumpFileName, _DebugId);
                    StorageFile sampleFile = await localFolder.CreateFileAsync(hexadumpFileName, CreationCollisionOption.ReplaceExisting);

                    if (sampleFile != null)
                    {
                        await FileIO.WriteBytesAsync(sampleFile, data);
                        /*
                        IBuffer buffer = await FileIO.ReadBufferAsync(sampleFile);
                        using (DataReader dataReader = DataReader.FromBuffer(buffer)){
                            var fileContent = dataReader.ReadBuffer(buffer.Length).ToArray();
                            MPosDebug.WriteLine("ConnectionImplementation writeAsync : fileContent.Length = " + fileContent.Length, _DebugId);
                        }
                        */
                    }
                }
            }
            catch (Exception ex)
            {
                // do nothing
                MPosDebug.WriteLine("ConnectionImplementation writeAsync : ex = " + ex.Message, _DebugId);
            }
#endif

            int bytesWritten = await writeSocketAsync(deviceId, data, addHeader, timeoutInmilliseconds);

            MPosDebug.WriteLine("ConnectionImplementation writeAsync : bytesWritten = " + bytesWritten, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation writeAsync : END", _DebugId);

            return bytesWritten;
        }
        private async Task<int> writeSocketAsync(int deviceId, byte[] data, bool addHeader, uint timeoutInmilliseconds)
        {
            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : START", _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : timeoutInmilliseconds = " + timeoutInmilliseconds, _DebugId);

            if (_Streamsocket == null || data == null)
            {
                MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : _Streamsocket is null or data is null", _DebugId);
                MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : END", _DebugId);
                return 0;
            }

            const int MAX_SIZE = 1024;
            int writtenBytes = 0;
            int totalWrittenBytes = 0;
            int dataIndex = 0;
            int nRestCount = data.Length;
            int mPosHeaderLength = 0;

            _WritingInProgress = true;

            while (nRestCount > 0)
            {
                byte[] partialBuffer = null;
                int partialBufferSize = 0;

                MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : nRestCount = " + nRestCount.ToString(), _DebugId);

                if (nRestCount > MAX_SIZE)
                    partialBufferSize = MAX_SIZE;
                else
                    partialBufferSize = nRestCount;

                partialBuffer = data.Skip(dataIndex).Take(partialBufferSize).ToArray();
                if (addHeader && deviceId < 0xff)
                {
                    if (_CommandMode == MPosCommandMode.MPOS_COMMAND_MODE_BGATE)
                    {
                        partialBuffer = MPosByteArrayHelper.concat(
                        MPosBgateCmdBuilder.getPageStartEndCmd((uint)deviceId, BgateMultiConnectionPage.START),
                        MPosBgateCmdBuilder.addMPosHeaderData((uint)deviceId, partialBuffer),
                        MPosBgateCmdBuilder.getPageStartEndCmd((uint)deviceId, BgateMultiConnectionPage.END));
                    }
                    else
                    {
                        int length = data.Length;
                        int p4 = length / (256 * 256 * 256);
                        length %= (256 * 256 * 256);
                        int p3 = length / (256 * 256);
                        length %= (256 * 256);
                        int p2 = length / 256;
                        length %= 256;
                        int p1 = length;

                        byte[] header = { 0x08, 0x7e, 0x64, (byte)deviceId, (byte)p1, (byte)p2, (byte)p3, (byte)p4 };
                        partialBuffer = MPosByteArrayHelper.concat(header, data);
                    }
                }

#if DEBUG
                if (partialBuffer != null)
                {
                    MPosDebug.WriteLine("ConnectionImplementation writeAsync : partialBuffer = " + MPosByteArrayHelper.hexaString(partialBuffer), _DebugId);
                }
#endif
                using (var dataWriter = new Windows.Storage.Streams.DataWriter(_Streamsocket.OutputStream))
                {
                    using (var cts = new CancellationTokenSource())
                    {
                        cts.CancelAfter((int)(timeoutInmilliseconds));

                        try
                        {
                            dataWriter.WriteBytes(partialBuffer);
                            writtenBytes = (int)await dataWriter.StoreAsync().AsTask(cts.Token);
                            await dataWriter.FlushAsync();
                            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : writtenBytes = " + writtenBytes, _DebugId);
                            if (addHeader && _CommandMode == MPosCommandMode.MPOS_COMMAND_MODE_BGATE)
                            {
                                // except for 22 bytes
                                if (writtenBytes == partialBuffer.Length)
                                    writtenBytes -= (8 + 8 + 6);
                            }
                        }
                        catch (Exception ex)
                        {
                            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : onReadWriteErrorEvent");
                            onReadWriteErrorEvent((uint)ex.HResult, ex.Message);
                            //await _Streamsocket.CancelIOAsync();
                            dataWriter.DetachStream();
                            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : ex = " + ex.Message + ", ex.HResult = " + ex.HResult.ToString("X2"), _DebugId);
                            nRestCount = 0;
                        }
                    }
                    dataWriter.DetachStream();
                }

                totalWrittenBytes += writtenBytes;
                dataIndex += writtenBytes;
                nRestCount -= writtenBytes;
            }

            _WritingInProgress = false;
            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : total writtenBytes = " + totalWrittenBytes + ", mPosHeaderLength = " + mPosHeaderLength, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation writeSocketAsync : END", _DebugId);
            return totalWrittenBytes;
        }
    }
    internal partial class MPosConnectionImplementation : IMPosConnection
    {
        List<int> _DeviceIdList = new List<int>();
        private async Task<int> waitForDeviceIdToBeAdded(uint timeout = 1000)
        {
            if (_DeviceIdList.Count == 0)
            {
                var stopwatch = new System.Diagnostics.Stopwatch();
                stopwatch.Start();
                while (_DeviceIdList.Count == 0)
                {
                    await Task.Delay(10);
                    stopwatch.Stop();
                    if (stopwatch.ElapsedMilliseconds > timeout)
                    {
                        MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : TIME-OUT", _DebugId);
                        return 0;
                    }
                    stopwatch.Start();
                }
            }
            return _DeviceIdList.Count;
        }
        public int[] getAllDeviceId()
        {
            return _DeviceIdList.ToArray();
        }
        public async Task<int> getLowestDeviceId(uint timeout = 1500, object deviceType = null)
        {
            MPosDebug.WriteLine("ConnectionImplementation getLowestDeviceId : START");
            if (deviceType == null)
            {
                if (await waitForDeviceIdToBeAdded(timeout) > 0)
                {
                    var id = _DeviceIdList.First();
                    MPosDebug.WriteLine("ConnectionImplementation getLowestDeviceId : id = " + id);
                    MPosDebug.WriteLine("ConnectionImplementation getLowestDeviceId : END");
                    return id;
                }
            }

            int devId = -1;
            int startIndex = 0;
            int endIndex = 0;

            if (await waitForDeviceIdToBeAdded(timeout) > 0)
            {
                if (deviceType as MPosDeviceLabelPrinterImplementation != null)
                {
                    startIndex = 1; endIndex = 10;
                }
                else if (deviceType as MPosDevicePrinterImplementation != null)
                {
                    startIndex = 10; endIndex = 20;
                }
                else if (deviceType as MPosControllerScannerImplementation != null)
                {
                    startIndex = 40; endIndex = 50;
                }
                //else if (deviceType as MPosControllerBCDImplementation != null)
                //{
                //    startIndex = 110; endIndex = 120;
                //}
                else if (deviceType as MPosControllerTTYUSBImplementation != null)
                {
                    startIndex = 120; endIndex = 130;
                }
            }

            var newList = _DeviceIdList.Where(x => x >= startIndex && x < endIndex);
            devId = newList.Count() > 0 ? newList.First() : -1;
            MPosDebug.WriteLine("ConnectionImplementation getLowestDeviceId : devId = " + devId);
            MPosDebug.WriteLine("ConnectionImplementation getLowestDeviceId : END");
            return devId;
        }
        public async Task<bool> isAvailableDeviceId(object deviceType, uint deviceId, uint timeout = 1000)
        {
            MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : START", _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : deviceId = " + deviceId, _DebugId);

            var result = false;

            if (deviceId == 0 || deviceId >= 255)
            {
                MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : END", _DebugId);
                return result;
            }

            if (await waitForDeviceIdToBeAdded(timeout) > 0)
            {
                if (deviceId >= 1 && deviceId <= 9)
                {
                    // Label Printer
                    MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : Label Printer, deviceId = " + deviceId, _DebugId);
                    var labelPrinterObj = deviceType as MPosDeviceLabelPrinterImplementation;
                    result = labelPrinterObj == null ? false : _DeviceIdList.Any(x => x == deviceId);
                }
                else if (deviceId >= 10 && deviceId <= 19)
                {
                    // Local POS Printer, POS Printer
                    MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : POS Printer, deviceId = " + deviceId, _DebugId);
                    var printerObj = deviceType as MPosDevicePrinterImplementation;
                    result = printerObj == null ? false : _DeviceIdList.Any(x => x == deviceId);
                }
                else if (deviceId >= 20 && deviceId <= 29) { } // Unregistered HID Input Device
                else if (deviceId >= 30 && deviceId <= 39) { } // MSR
                else if (deviceId >= 40 && deviceId <= 49)
                {
                    // Barcode Scanner
                    MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : Barcode Scanner, deviceId = " + deviceId, _DebugId);
                    var printerObj = deviceType as MPosControllerScannerImplementation;
                    result = printerObj == null ? false : _DeviceIdList.Any(x => x == deviceId);
                }
                else if (deviceId >= 50 && deviceId <= 59) { } // Reserved
                else if (deviceId >= 60 && deviceId <= 69) { } // RFID
                else if (deviceId >= 70 && deviceId <= 79) { } // Dallas Reader
                else if (deviceId >= 80 && deviceId <= 89) { } // NFC
                else if (deviceId >= 90 && deviceId <= 99) { } // Reserved
                else if (deviceId >= 100 && deviceId <= 109) { } // Unregistered USB Serial Device
                else if (deviceId >= 110 && deviceId <= 119)
                {
                    // BCD 
                    MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : BCD, deviceId = " + deviceId, _DebugId);
                    //var printerObj = deviceType as MPosControllerBCDImplementation;
                    //result = printerObj == null ? false : _DeviceIdList.Any(x => x == deviceId);
                }
                else if (deviceId >= 120 && deviceId <= 129) { } // USB to Serial
                else if (deviceId >= 130 && deviceId <= 139) { } // Scale
                else if (deviceId == 255) { } // Lookup & Config
            }

            MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : result = " + result, _DebugId);
            MPosDebug.WriteLine("ConnectionImplementation isAvailableDeviceId : END", _DebugId);
            return result;
        }
    }
    internal partial class MPosConnectionImplementation : IMPosConnection
    {
        private static event Action<int[]> _DevListUpdateEvent = null;
        private static event Action<int, byte[]> _StatusUpdateEvent = null;
        private static event Action<int, byte[]> _DataEvent = null;
        private static event Action _DisconnectedEvent = null;
        private static event Action _ConnectedEvent = null;
        private static Task _StatusEventTask = null;
        private static CancellationTokenSource _StatusEventCts = null;
        public void addDataReadEvent(Action<int, byte[]> dataDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation addDataReadEvent", _DebugId);
            removeDataReadEvent(dataDelegate);
            _DataEvent += dataDelegate;
        }
        public void removeDataReadEvent(Action<int, byte[]> dataDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation removeDataReadEvent", _DebugId);
            var dataEvent = _DataEvent;
            if (dataEvent != null)
            {
                _DataEvent -= dataDelegate;
            }
        }
        public void removeDisconnectedEvent(Action disconnectedDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation removeDisconnectedEvent", _DebugId);
            var disconnectedEvent = _DisconnectedEvent;
            if (disconnectedEvent != null)
                _DisconnectedEvent -= disconnectedDelegate;
        }
        public void addDisconnectedEvent(Action disconnectedDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation addDisconnectedEvent", _DebugId);
            removeDisconnectedEvent(disconnectedDelegate);
            _DisconnectedEvent += disconnectedDelegate;
        }
        public void addStatusUpdateEvent(Action<int, byte[]> statusDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation addStatusUpdateEvent", _DebugId);
            removeStatusUpdateEvent(statusDelegate);
            _StatusUpdateEvent += statusDelegate;
        }
        public void addDeviceListUpdateEvent(Action<int[]> devListDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation addDeviceListUpdateEvent");
            removeDeviceListUpdateEvent(devListDelegate);
            _DevListUpdateEvent += devListDelegate;
        }
        public void addConnectedEvent(Action connectedDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation addConnectedEvent");
            removeConnectedEvent(connectedDelegate);
            _ConnectedEvent += connectedDelegate;
        }
        public void removeConnectedEvent(Action connectedDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation removeStatusUpdateEvent");
            var connectedEvent = _ConnectedEvent;
            if (connectedEvent != null)
                _ConnectedEvent -= connectedDelegate;
        }
        public void removeStatusUpdateEvent(Action<int, byte[]> statusDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation removeStatusUpdateEvent");
            var statusUpdateEvent = _StatusUpdateEvent;
            if (statusUpdateEvent != null)
                _StatusUpdateEvent -= statusDelegate;
        }
        public void removeDeviceListUpdateEvent(Action<int[]> devListDelegate)
        {
            MPosDebug.WriteLine("ConnectionImplementation removeDeviceListUpdateEvent");
            var devListUpdateEvent = _DevListUpdateEvent;
            if (devListUpdateEvent != null)
                _DevListUpdateEvent -= devListDelegate;
        }
        private void fireConnectedEvent()
        {
            _IsConnected = true;
            MPosDebug.WriteLine("ConnectionImplementation fireConnectedEvent : _IsConnected = " + _IsConnected, _DebugId);
            _ConnectedEvent?.Invoke();
        }
        private void fireDisconnectedEvent()
        {
            _IsConnected = false;
            MPosDebug.WriteLine("ConnectionImplementation fireDisconnectedEvent : _IsConnected = " + _IsConnected, _DebugId);
            _DisconnectedEvent?.Invoke();
        }
        private void onReadWriteErrorEvent(uint exceptionResult, string message)
        {
            MPosDebug.WriteLine("ConnectionImplementation onReadWriteErrorEvent : message = " + message + " (0x" + exceptionResult.ToString("x2") + ")", _DebugId);
            // 0x800703e3 : The I/ O operation has been aborted because of either a thread exit or an application request.
            // 0x80072745 : An established connection was aborted by the software in your host machine.
            switch (exceptionResult)
            {
                case 0x800703e3:    // The I/ O operation has been aborted because of either a thread exit or an application request.
                case 0x80072745:    // An established connection was aborted by the software in your host machine.
                case 0x80000013:    // The object has been closed    
                    fireDisconnectedEvent();
                    break;
                default:
                    break;
            }
        }
        private void onNormalDataQueueUpdateEvent(int deviceId)
        {
            MPosDebug.WriteLine("ConnectionImplementation onNormalDataQueueUpdateEvent : _dataQueue.Count =" + _DataParser._dataQueue.Count, _DebugId);

            if (_DataParser._dataQueue.Count == 0)
                return;

            var normalData = new byte[_DataParser._dataQueue.Count];
            for (int j = 0; j < normalData.Length; j++)
                normalData[j] = _DataParser._dataQueue.Dequeue();

            switch (deviceId)
            {
                case (int)BgateDeviceId.COMMAND:
                    MPosDebug.WriteLine("ConnectionImplementation onNormalDataQueueUpdateEvent : BgateDeviceId.COMMAND");
                    if (_DataEvent == null)
                        MPosDebug.WriteLine("ConnectionImplementation onNormalDataQueueUpdateEvent : _DataEvent is null");
                    _DataEvent?.Invoke(deviceId, normalData);
                    break;
                case (int)BgateDeviceId.PNP_COMMAND:
                    MPosDebug.WriteLine("ConnectionImplementation onNormalDataQueueUpdateEvent : BgateDeviceId.PNP_COMMAND");
                    _DeviceIdList.Clear();
                    // ascending sort 
                    //foreach (var id in normalData.OrderByDescending(x => x).ToArray())
                    foreach (var id in normalData.OrderBy(x => x).ToArray())
                        _DeviceIdList.Add(id);
                    _DevListUpdateEvent?.Invoke(getAllDeviceId());
                    break;
                default:
                    MPosDebug.WriteLine("ConnectionImplementation onNormalDataQueueUpdateEvent : deviceId = " + deviceId);
                    _DataEvent?.Invoke(deviceId, normalData);
                    break;
            }
        }
        private void onStatusDataQueueUpdateEvent(int deviceId)
        {
            MPosDebug.WriteLine("ConnectionImplementation onStatusDataQueueUpdateEvent : deviceId = " + deviceId + ", _statusQueue.Count =" + _DataParser._statusQueue.Count, _DebugId);
            if (_DataParser._statusQueue.Count >= 4)
            {
                var asbData = new byte[4];
                for (int j = 0; j < 4; j++)
                    asbData[j] = _DataParser._statusQueue.Dequeue();
                _StatusUpdateEvent?.Invoke(deviceId, asbData);
            }
        }
        private async Task<bool> startReadTask(MPosDeviceType deviceType)
        {
            bool result = true;
            MPosDebug.WriteLine("ConnectionImplementation startReadTask : START");

            // The task for status update is running on, then cancel it. 
            cancelAllTasks();

            byte[] commandToWrite = null;

            switch (_CommandMode)
            {
                case MPosCommandMode.MPOS_COMMAND_MODE_BGATE:
                    commandToWrite = MPosBgateCmdBuilder.getPnpEnableDisableCmd(true);
                    break;
                default:
                    switch (deviceType)
                    {
                        case MPosDeviceType.MPOS_DEVICE_PRINTER:
                            commandToWrite = MPosEscPosCmdBuilder.getSecurityCode();
                            break;
                        //case MPosDeviceType.MPOS_DEVICE_LABEL_PRINTER:
                        //    //commandToWrite = System.Text.Encoding.ASCII.GetBytes("CB@^PI0\r\n");
                        //    commandToWrite = System.Text.Encoding.ASCII.GetBytes("CB@\r\n");
                        //    break;
                        //case MPosDeviceType.MPOS_DEVICE_CONFIG:
                        //    commandToWrite = new byte[] { 0x08, 0x7e, 0x50, 0x01 };
                        //    break;
                        default:
                            commandToWrite = new byte[] { };
                            break;
                    }
                    break;
            }

            if (await writeAsync((int)BgateDeviceId.PNP_COMMAND, commandToWrite, false, false) == commandToWrite.Length)
            {
                _StatusEventCts = new CancellationTokenSource();
                _StatusEventTask = Task.Factory.StartNew(() =>
                {
                    dataReadTask(_StatusEventCts.Token);
                    //connectableStateTask(_StatusEventCts.Token);
                },
                _StatusEventCts.Token,
                TaskCreationOptions.LongRunning,
                TaskScheduler.Default);
            }
            else
            {
                MPosDebug.WriteLine("ConnectionImplementation startReadTask : failed to start read-task");
                result = false;
            }
            MPosDebug.WriteLine("ConnectionImplementation startReadTask : END");
            return result;
        }
        private void cancelAllTasks()
        {
            MPosDebug.WriteLine("ConnectionImplementation cancelAllTasks : START");
            try
            {
                if (_StatusEventCts != null)
                    _StatusEventCts.Cancel();
                if (_StatusEventTask != null)
                    _StatusEventTask.Wait();
            }
            catch (Exception)
            {
                // Do nothing
            }
            finally
            {
                _StatusEventCts = null;
                _StatusEventTask = null;
            }
            MPosDebug.WriteLine("ConnectionImplementation cancelAllTasks : END");
        }
        private async void dataReadTask(CancellationToken token)
        {
            MPosDebug.WriteLine("ConnectionImplementation dataReadTask : START");

            while (true)
            {
                if (token.IsCancellationRequested)
                {
                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : token.IsCancellationRequested");
                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : END");
                    break;
                }

                await Task.Delay(20);

                try
                {
                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : _IsConnected = " + _IsConnected, _DebugId);
                    if (!_IsConnected)
                    {
                        await closeSocket();
                        if (await connectSocketAsync(_HostAddress, _ServiceName, 3000) == (int)MPosResult.MPOS_SUCCESS)
                        {
                            await Task.Delay(100);
                            _ConnectedEvent?.Invoke();
                        }
                        continue;
                    }

                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : readAsync-1");
                    if (await readAsync(0) == null)
                    {
                        MPosDebug.WriteLine("ConnectionImplementation dataReadTask : no data read");
                        //fireDisconnectedEvent();
                    }
                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : readAsync-2");
                }
                catch (Exception ex)
                {
                    MPosDebug.WriteLine("ConnectionImplementation dataReadTask : " + ex.Message);
                }
            }

            MPosDebug.WriteLine("ConnectionImplementation dataReadTask : END");
        }
        private async void connectableStateTask(CancellationToken token)
        {
            MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : START");

            while (true)
            {
                if (token.IsCancellationRequested)
                {
                    MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : token.IsCancellationRequested");
                    MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : END");
                    return;
                }

                switch (_ConnectionType)
                {
                    case MPosInterfaceType.MPOS_INTERFACE_WIFI:
                    case MPosInterfaceType.MPOS_INTERFACE_ETHERNET:
                        break;
                    default:
                        MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : END");
                        return;
                }

                if (!_IsConnected)
                    continue;

                if (_WritingInProgress)
                    continue;

                await Task.Delay(500);

                try
                {
                    using (var tcpClient = new StreamSocket())
                    {
                        using (var cts = new CancellationTokenSource())
                        {
                            cts.CancelAfter(2500);
                            MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : ConnectAsync-1, _HostAddress = " + _HostAddress);
                            await tcpClient.ConnectAsync(new HostName(_HostAddress), "80" /* HTTP Port Number */, SocketProtectionLevel.PlainSocket).AsTask(cts.Token);
                            MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : ConnectAsync-2");
                            tcpClient.Dispose();
                        }
                    }
                }
                catch (Exception ex)
                {
                    MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : message = " + ex.Message + " (0x" + ex.HResult.ToString("x2") + ")", _DebugId);
                    if (_Streamsocket != null)
                    {
                        switch ((uint)ex.HResult)
                        {
                            case 0x8007274d:    // No connection could be made because the target machine actively refused it
                                break;
                            default:
                                MPosDebug.WriteLine("ConnectionImplementation connectableStateTask : _streamsocket Dispose", _DebugId);
                                // Make the stream socket disposed to stop reading data from the printer in 'dataReadTask' Task.
                                _Streamsocket.Dispose();
                                break;
                        }
                    }
                }
            }
        }
    }
    internal partial class MPosConnectionImplementation : IMPosConnection
    {
        private byte[] _DataStored { get; set; } = null;
        public void setBufferStored(byte[] data, bool addHeaer = true)
        {
            if (_DataStored != null)
            {
                MPosDebug.WriteLine("setBufferStored : old buffer size = " + _DataStored.Length.ToString(), _DebugId);
            }

            if (data != null)
            {
                MPosDebug.WriteLine("setBufferStored : buffer size to add= " + data.Length.ToString(), _DebugId);
            }

            if (_DataStored != null && data != null)
            {
                byte[] newBufferData = new byte[_DataStored.Length + data.Length];
                System.Buffer.BlockCopy(_DataStored, 0, newBufferData, 0, _DataStored.Length);
                System.Buffer.BlockCopy(data, 0, newBufferData, _DataStored.Length, data.Length);
                _DataStored = newBufferData;
            }
            else
            {
                _DataStored = data;
            }

            MPosDebug.WriteLine("setBufferStored : current buffer size = " + _DataStored.Length.ToString(), _DebugId);
        }
        public byte[] getBufferStored()
        {
            return _DataStored;
        }
        public async Task<int> clearBufferStored(int deviceId)
        {
            MPosDebug.WriteLine("MPosConnectionImplementation clearBufferStored : START", _DebugId);
            var result = (int)MPosResult.MPOS_SUCCESS;

            if (_DataStored != null)
            {
                var writtenBytes = await writeAsync(deviceId, _DataStored);
                if (writtenBytes != _DataStored.Length)
                    result = (int)MPosResult.MPOS_FAIL;
                MPosDebug.WriteLine("MPosConnectionImplementation clearBufferStored : _DataStored = " + _DataStored.Length + ", writtenBytes = " + writtenBytes, _DebugId);
            }
            _DataStored = null;
            MPosDebug.WriteLine("MPosConnectionImplementation clearBufferStored : END", _DebugId);
            return result;
        }
    }
    internal class MPosRfcommBluetoothHelper
    {
        public static async Task<bool> checkRfcommConnection(string hostName)
        {
            bool isConnected = true;
            MPosDebug.WriteLine("checkRfcommConnection : START");
            ObservableCollection<MPosRfcommDevice> resultCollection = await MposRfcommDeviceWatcher.getRfcommDeviceCollection();

            if (resultCollection == null)
            {
                MPosDebug.WriteLine("checkRfcommConnection : resultCollection is null");
                MPosDebug.WriteLine("checkRfcommConnection : END");
                return isConnected;
            }

            var rfCommDevice = resultCollection.ToArray().Where(c => c.DeviceInformation.Id.Contains(hostName.ToLower()) || c.DeviceInformation.Id.Contains(hostName.ToUpper())).First();

            if (rfCommDevice != null)
            {
                MPosDebug.WriteLine("checkRfcommConnection : rfcommInfoDisp.Id = " + rfCommDevice.Id);
                var deviceInformation = await DeviceInformation.CreateFromIdAsync(rfCommDevice.Id, new List<string> { "System.Devices.Aep.IsConnected" });
                isConnected = (bool)deviceInformation.Properties.FirstOrDefault(p => p.Key == "System.Devices.Aep.IsConnected").Value;
            }

            MPosDebug.WriteLine("checkRfcommConnection : isConnected = " + isConnected);
            MPosDebug.WriteLine("checkRfcommConnection : END");
            return isConnected;
        }
        public static async Task<bool> checkBluetoothPermission(string hostName)
        {
            MPosDebug.WriteLine("checkBluetoothPermission : START");

            ObservableCollection<MPosRfcommDevice> resultCollection = null;

            //if (MposRfcommDeviceWatcher.getCurrentDeviceCount() == 0)
            resultCollection = await MposRfcommDeviceWatcher.getRfcommDeviceCollection();

            if (resultCollection == null)
            {
                MPosDebug.WriteLine("checkBluetoothPermission : resultCollection is null");
                MPosDebug.WriteLine("checkBluetoothPermission : END");
                return true;
            }

            MPosDebug.WriteLine("checkBluetoothPermission : resultCollection.Count = " + resultCollection.Count);

            var rfCommDevice = resultCollection.ToArray().Where(c => c.DeviceInformation.Id.Contains(hostName.ToLower()) || c.DeviceInformation.Id.Contains(hostName.ToUpper())).First();

            if (rfCommDevice != null)
            {
                // Perform device access checks before trying to get the device. First, we check if consent has been explicitly denied by the user.
                DeviceAccessStatus accessStatus = DeviceAccessInformation.CreateFromId(rfCommDevice.Id).CurrentStatus;
                MPosDebug.WriteLine("checkBluetoothPermission: accessStatus = " + accessStatus);

                switch (accessStatus)
                {
                    case DeviceAccessStatus.DeniedByUser:
                        MPosDebug.WriteLine("checkBluetoothPermission: DeviceAccessStatus.DeniedByUser");
                        MPosDebug.WriteLine("checkBluetoothPermission: END");
                        return false;
                    case DeviceAccessStatus.Allowed:
                        MPosDebug.WriteLine("checkBluetoothPermission: DeviceAccessStatus.Allowed");
                        break;
                    case DeviceAccessStatus.Unspecified:
                        MPosDebug.WriteLine("checkBluetoothPermission: DeviceAccessStatus.Unspecified");
                        break;
                    case DeviceAccessStatus.DeniedBySystem:
                        MPosDebug.WriteLine("checkBluetoothPermission: DeviceAccessStatus.DeniedBySystem");
                        break;
                }
            }

            MPosDebug.WriteLine("checkBluetoothPermission : END");
            return true;
        }

        public static async Task<string> getRfcommId(string hostName)
        {
            MPosDebug.WriteLine("getRfcommId : START");

            ObservableCollection<MPosRfcommDevice> resultCollection = null;
            string rfcommId = null;

            resultCollection = await MposRfcommDeviceWatcher.getRfcommDeviceCollection();

            if (resultCollection == null)
            {
                MPosDebug.WriteLine("getRfcommId : resultCollection is null");
                MPosDebug.WriteLine("getRfcommId : END");
                return rfcommId;
            }

            MPosDebug.WriteLine("getRfcommId : resultCollection.Count = " + resultCollection.Count);

            var rfCommDevice = resultCollection.ToArray().Where(c => c.DeviceInformation.Id.Contains(hostName.ToLower()) || c.DeviceInformation.Id.Contains(hostName.ToUpper())).First();

            if (rfCommDevice != null)
            {
                rfcommId = rfCommDevice.Id;
            }

            MPosDebug.WriteLine("getRfcommId : END");
            return rfcommId;
        }
    }
}