using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Hardware.Usb;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.IO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using static Android.Bluetooth.BluetoothClass;

namespace FiscalPrinterSDK
{

    public class FiscalCommAndroid_Usb : FiscalComm

    {
        private int FIRST_BYTE = 0; //FTDI packets of 64 bytes which contain 2 status bytes and 62 data bytes.

        private UsbManager mUsbManager;
        private UsbDevice mUsbDevice;
        private UsbDeviceConnection mDeviceConn;
        private UsbEndpoint[] mEndpoints;
        private byte[] readBuf = new byte[1024];
        private int readLen = 0;
        private string mLastError;

        //private UsbDeviceConnection com = mUsbManager.openDevice(device);

        public FiscalCommAndroid_Usb(Context ctx, UsbDevice device)
        {
            this.mUsbManager = (UsbManager)ctx.GetSystemService(Context.UsbService);
            this.mUsbDevice = device;
            this.mEndpoints = new UsbEndpoint[2];
        }

        public override void ClearReceive()
        {
            readLen= 0;
        }

        public override void Close()
        {
            if (mDeviceConn != null)
            {
                try
                {
                    mDeviceConn.Close();
                }
                catch 
                {
                    throw;
                }
            }

           
        }

        private UsbDeviceConnection OpenConnection(UsbDevice device, UsbEndpoint[] endpoints)
        {
            if (!mUsbManager.HasPermission(mUsbDevice))
            {
                throw new IOException("Permission denied");
            }
            for (int i = 0; i < mUsbDevice.InterfaceCount; i++)
            {
                UsbInterface iface = mUsbDevice.GetInterface(i);
                UsbEndpoint usbEpInp = null;
                UsbEndpoint usbEpOut = null;

                // Enumerate end points
                for (int j = 0; j < iface.EndpointCount; j++)
                {
                    UsbEndpoint endpoint = iface.GetEndpoint(j);

                    // Check interface type
                    if (endpoint.Type != UsbAddressing.XferBulk) continue; 

                    if (endpoint.Direction == UsbAddressing.In)
                    {
                        usbEpInp = endpoint;
                    }

                    if (endpoint.Direction == UsbAddressing.Out)
                    {
                        usbEpOut = endpoint;
                    }
                }

                if (usbEpInp != null || usbEpOut != null)
                {
                    UsbDeviceConnection usbDevConn = mUsbManager.OpenDevice(mUsbDevice);

                    if (mUsbDevice.ProductName.Equals("FT232R USB UART"))
                    {
                        FIRST_BYTE = 2; //FTDI USB packets 2 status bytes and 62 data bytes, so first byte in packet is 2
                                        // Configure FTDI port.
                        usbDevConn.ControlTransfer((UsbAddressing)0x40, 0, 0, 0, null, 0, 0); // Reset
                        usbDevConn.ControlTransfer((UsbAddressing)0x40, 0, 1, 0, null, 0, 0); // Clear Rx
                        usbDevConn.ControlTransfer((UsbAddressing)0x40, 0, 2, 0, null, 0, 0); // Clear Tx

                        usbDevConn.ControlTransfer((UsbAddressing)0x40, 0x03, 0x001A, 0, null, 0, 0); // Set baud rate to 115200
                    }


                    // Check connection
                    if (usbDevConn == null)
                    {
                        throw new IOException("Open failed");
                    }

                    if (!usbDevConn.ClaimInterface(iface, true))
                    {
                        usbDevConn.Close();
                        throw new IOException("Access denied");
                    }

                    mEndpoints[0] = usbEpInp;
                    mEndpoints[1] = usbEpOut;
                    return usbDevConn;
                }
            }

            throw new IOException("Open failed");
        }

        public override void Connect()
        {
            mDeviceConn = OpenConnection(mUsbDevice, mEndpoints);
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override byte[] Read(int maxLength, int timeout)
        {
            byte[] tmp = new byte[64];

            int endTime = System.Environment.TickCount + timeout;
            while (readLen < maxLength)
            {
                if (System.Environment.TickCount > endTime) throw new TimeoutException();

                int len = mDeviceConn.BulkTransfer(mEndpoints[0], tmp, tmp.Length, timeout);
                if (len < 0)
                {
                    System.Diagnostics.Debug.WriteLine("Read bulkTransfer failed: " + len);

                    try
                    {
                        if (endTime > System.Environment.TickCount)
                        {
                            mLastError = "Read failed";
                        }
                    }
                    catch (Exception e)
                    {
                        throw;
                    }
                }
                else if (len > FIRST_BYTE)
                {
                    len -= FIRST_BYTE;
                    System.Diagnostics.Debug.WriteLine($"<< ({len}): {HexToString(tmp, FIRST_BYTE, len)}");
                    Array.Copy(tmp, FIRST_BYTE, readBuf, readLen, len);
                    readLen += len;
                }

            }

            var r = new byte[maxLength];
            Array.Copy(readBuf, 0, r, 0, maxLength);

            Array.Copy(readBuf, maxLength, readBuf, 0, readLen - maxLength);
            readLen -= maxLength;

            return r;
        }

        public static string HexToString(byte[] data)
        {
            if (data == null)
                return "";
            return HexToString(data, 0, data.Length);
        }
        public static string HexToString(byte[] data, int offset, int length)
        {
            return HexToString(data, offset, length, null, null);
        }
        public static string HexToString(byte[] data, int offset, int length, string byteDelimiter, string bytePrefix)
        {
            return HexToString(data, offset, length, byteDelimiter, bytePrefix, 0);
        }
        public static string HexToString(byte[] data, int offset, int length, string byteDelimiter, string bytePrefix, int splitBytes)
        {
            string HEX = "0123456789ABCDEF";
            int byteSize = 2;
            if (!string.IsNullOrEmpty(byteDelimiter))
                byteSize += byteDelimiter.Length;
            if (!string.IsNullOrEmpty(bytePrefix))
                byteSize += bytePrefix.Length;
            System.Text.StringBuilder sb = new System.Text.StringBuilder(byteSize * length);

            for (int i = 0, split = 0; i < length; i++, split++)
            {
                if (splitBytes != 0 && split == splitBytes)
                {
                    sb.Append(System.Environment.NewLine);
                    split = 0;
                }
                if (!string.IsNullOrEmpty(bytePrefix))
                    sb.Append(bytePrefix);
                sb.Append(HEX[(data[offset + i] >> 4) & 0x0f]);
                sb.Append(HEX[data[offset + i] & 0x0f]);
                if (i < (length - 1) && !string.IsNullOrEmpty(byteDelimiter))
                    sb.Append(byteDelimiter);
            }
            return sb.ToString();
        }


        public override string ToString()
        {
            return base.ToString();
        }

        public override void Write(byte[] data, uint len, int timeout)
        {
            int written = 0;
            while (written < len)
            {
                if (mLastError != null)
                {
                    throw new IOException(mLastError);
                }
                var toWrite = data.SubArray(written, (int)len - written);
                int w = mDeviceConn.BulkTransfer(mEndpoints[1], toWrite, toWrite.Length, 2000);
                if (w < 0)
                {
                    System.Diagnostics.Debug.WriteLine("Write bulkTransfer failed: " + w);
                    mLastError = "Write error " + w;
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine($">> ({w}): {HexToString(data, written, w)}");
                    written += w;
                }
            }
        }
    }
    public static class SharedExtension
    {
        public static T[] SubArray<T>(this T[] data, int index, int length = -1)
        {
            if (length == -1)
                length = data.Length - index;
            T[] result = new T[length];
            Array.Copy(data, index, result, 0, length);
            return result;
        }
    }
}