using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FTD2XX_NET;
using ChamChat.Models;

namespace ChamChat
{
    // Communication with FDTI USB-COM485-Plus1
    public class FDTI_USB_COM485 : Interface
    {
        public FDTI_USB_COM485(String ID)
            : base(ID)
        {
            _Type = InterfaceType.FDTI_USB_COM485;
        }

        public FDTI_USB_COM485()
            : base()
        {
            _Type = InterfaceType.FDTI_USB_COM485;
        }


        

        // Properties and fields
        private static FTDI _RS485COM = new FTDI();
        private static uint _Baudrate = 9600;
        private static FlowControl _FlowControl = FlowControl.FT_FLOW_NONE; 
        
       

        private System.Object lockDataTransfer = new System.Object();
        private bool _Connected = false; public Boolean Connected { get { return _Connected; } }



        public override Boolean Open()
        {
            bool result;

            if (_ID != null)
                result = OpenByID();
            else
                result = OpenSingleConnected();
            return result;
        }


        public override Boolean Close()
        {
            _RS485COM.Close();
            _Connected = false;
            _RS485COM = null;
            return true;
        }


        private Boolean OpenByID()
        {
            _InterfaceStatus = null;

            UInt32 ftdiDeviceCount = 0;
            FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;

            // Determine the number of FTDI devices connected to the machine
            ftStatus = _RS485COM.GetNumberOfDevices(ref ftdiDeviceCount);

            // Check status
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to get number of FTDI devices", ftStatus.ToString());
                return false;
            }

            // If no devices available, return
            if (ftdiDeviceCount == 0)
            {
                _InterfaceStatus = new InterfaceStatus("No FTDI devices found");
                return false;
            }


            // Allocate storage for device info list
            FTDI.FT_DEVICE_INFO_NODE[] ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftdiDeviceCount];


            // Populate our device list
            ftStatus = _RS485COM.GetDeviceList(ftdiDeviceList);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to get FTDI devices", ftStatus.ToString());
                return false;
            }


            // Find all USB-COM485 devices 
            List<FTDI.FT_DEVICE_INFO_NODE> ftdiDevices = new List<FTDI.FT_DEVICE_INFO_NODE>();
            for (UInt32 i = 0; i < ftdiDeviceCount; i++)
                ftdiDevices.Add(ftdiDeviceList[i]);
            List<FTDI.FT_DEVICE_INFO_NODE> USBCOM485List = ftdiDevices.FindAll(delegate(FTDI.FT_DEVICE_INFO_NODE node) { return  node.SerialNumber == _ID; });




            if (USBCOM485List.Count > 1)
            {
                _InterfaceStatus = new InterfaceStatus("More than one USB-COM485 devices with given serial number found");
                return false;
            }

            if (USBCOM485List.Count == 0)
            {
                _InterfaceStatus = new InterfaceStatus("No USB-COM485 device found with given serial number");
                return false;
            }

            // Open device by serial number
            ftStatus = _RS485COM.OpenBySerialNumber(USBCOM485List[0].SerialNumber);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to open USB-COM485 device", ftStatus.ToString());
                return false;
            }

            _ID = USBCOM485List[0].SerialNumber;


            // Set up device data parameters
            // Set Baud rate
            ftStatus = _RS485COM.SetBaudRate(_Baudrate);

            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set baud rate in USB-COM485 device", ftStatus.ToString());
                return false;
            }

            

            // Set data characteristics - Data bits, Stop bits, Parity
            ftStatus = _RS485COM.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8, FTDI.FT_STOP_BITS.FT_STOP_BITS_1, FTDI.FT_PARITY.FT_PARITY_NONE);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set data characteristics in USB-COM485 device", ftStatus.ToString());
                return false;
            }

            // Set flow control - set Xon/Xoff control OFF
            ushort flowCtrl = (ushort)_FlowControl;


            ftStatus = _RS485COM.SetFlowControl(flowCtrl, 0x11, 0x13);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set flow control in USB-COM485 device", ftStatus.ToString());
                return false;
            }

            // Set read timeout to 5 seconds, write timeout to infinite
            ftStatus = _RS485COM.SetTimeouts(5000, 0);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set timeouts in USB-COM485 device", ftStatus.ToString());
                return false;
            }
            _Connected = true;

            return true;
        }


        private Boolean OpenSingleConnected()
        {
            _InterfaceStatus = null;

            UInt32 ftdiDeviceCount = 0;
            FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;

            // Determine the number of FTDI devices connected to the machine
            ftStatus = _RS485COM.GetNumberOfDevices(ref ftdiDeviceCount);

            // Check status
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to get number of FTDI devices",ftStatus.ToString());
                return false;
            }

            // If no devices available, return
            if (ftdiDeviceCount == 0)
            {
                _InterfaceStatus = new InterfaceStatus("No FTDI devices found");
                return false;
            }


            // Allocate storage for device info list
            FTDI.FT_DEVICE_INFO_NODE[] ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftdiDeviceCount];


            // Populate our device list
            ftStatus = _RS485COM.GetDeviceList(ftdiDeviceList);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to get FTDI devices", ftStatus.ToString());
                return false;
            }





            // Find all USB-COM485 devices 
            List<FTDI.FT_DEVICE_INFO_NODE> ftdiDevices = new List<FTDI.FT_DEVICE_INFO_NODE>();
            for (UInt32 i = 0; i < ftdiDeviceCount; i++)
                ftdiDevices.Add(ftdiDeviceList[i]);
            List<FTDI.FT_DEVICE_INFO_NODE> USBCOM485List = ftdiDevices.FindAll(delegate(FTDI.FT_DEVICE_INFO_NODE node) { return (node.Description.ToString().Contains("FTDI_USB_RS485")); });
            

            if (USBCOM485List.Count > 1)
            {
                _InterfaceStatus = new InterfaceStatus("More than one USB-COM485 device found");
                return false;
            }

            if (USBCOM485List.Count == 0 )
            {
                _InterfaceStatus = new InterfaceStatus("No USB-COM485 device found");
                return false;
            }



            // Open device by serial number
            ftStatus = _RS485COM.OpenBySerialNumber(USBCOM485List[0].SerialNumber);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to open USB-COM485 device", ftStatus.ToString());
                return false;
            }

            _ID = USBCOM485List[0].SerialNumber;


            // Set up device data parameters
            // Set Baud rate
            ftStatus = _RS485COM.SetBaudRate(_Baudrate);

            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set baud rate in USB-COM485 device", ftStatus.ToString());
                return false;
            }

            // Set data characteristics - Data bits, Stop bits, Parity
            ftStatus = _RS485COM.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8, FTDI.FT_STOP_BITS.FT_STOP_BITS_1, FTDI.FT_PARITY.FT_PARITY_NONE);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set data characteristics in USB-COM485 device", ftStatus.ToString());
                return false;
            }

            // Set flow control - set Xon/Xoff control OFF
            ushort flowCtrl = (ushort)_FlowControl;


            ftStatus = _RS485COM.SetFlowControl(flowCtrl, 0x11, 0x13);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set flow control in USB-COM485 device", ftStatus.ToString());
                return false;
            }


            // Set read timeout to 5 seconds, write timeout to infinite
            ftStatus = _RS485COM.SetTimeouts(5000, 0);
            if (ftStatus != FTDI.FT_STATUS.FT_OK)
            {
                _InterfaceStatus = new InterfaceStatus("Failed to set timeouts in USB-COM485 device", ftStatus.ToString());
                return false;
            }
           
            _Connected = true;

            return true;
        }



        private string NOTINUSE_ReplaceDelimeters(string cmd)
        {
            string result = cmd;
            result = result.Replace(((char)13).ToString(), "<CR>");
            result = result.Replace(((char)10).ToString(), "<LF>");
            return result;
        }



        public override DataTransferLog DataTransfer(String Command, out String _ReturnData, Int32 ReturnDataWaitms)
        {
            lock (lockDataTransfer) // Calling threads will wait for the current thread to finish executing the locked section.
            {
                DataTransferLog dataTransferLog = new DataTransferLog(ANSI_X328.ToANSI(Command), "", this);

                _ReturnData = "";
                _InterfaceStatus = null;


                // Construct string to send
                string dataToSend = Command.Replace(" ", "");

                FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;

                UInt32 numBytesWritten = 0;
                ftStatus = _RS485COM.Write(dataToSend, dataToSend.Length, ref numBytesWritten);



                if (ftStatus != FTDI.FT_STATUS.FT_OK)
                {
                    _InterfaceStatus = new InterfaceStatus("Failed to write to USB-COM485 device", ftStatus.ToString());
                    _ReturnData = "Failed to write to USB-COM485 device | " + ftStatus.ToString();
                    dataTransferLog.ReturnData = _ReturnData;
                    return dataTransferLog;
                }


                // Read from devices
                if (ReturnDataWaitms > 0)
                {
                    System.Threading.Thread.Sleep(ReturnDataWaitms);

                    UInt32 numBytesAvailable = 0;
                    ftStatus = _RS485COM.GetRxBytesAvailable(ref numBytesAvailable);
                    if (ftStatus != FTDI.FT_STATUS.FT_OK)
                    {
                        _InterfaceStatus = new InterfaceStatus("Failed to get number of bytes available to read from USB-COM485 device", ftStatus.ToString());
                        _ReturnData = "Failed to get number of bytes available to read from USB-COM485 device | " + ftStatus.ToString();
                        dataTransferLog.ReturnData = _ReturnData;
                        return dataTransferLog;
                    }

                    // Now that we have the amount of data we want available, read it
                    string readData;
                    UInt32 numBytesRead = 0;


                    // Note that the Read method is overloaded, so can read string or byte array data
                    ftStatus = _RS485COM.Read(out readData, numBytesAvailable, ref numBytesRead);
                    if (ftStatus != FTDI.FT_STATUS.FT_OK)
                    {
                        _InterfaceStatus = new InterfaceStatus("Failed to read data from USB-COM485 device", ftStatus.ToString());

                        _ReturnData = "Failed to read data from USB-COM485 device | " + ftStatus.ToString();
                        dataTransferLog.ReturnData = _ReturnData;
                        return dataTransferLog;
                    }
                    _ReturnData = readData.Trim();
                    dataTransferLog.ReturnData = _ReturnData;
                }
                else
                {
                    _ReturnData = "not requested";
                    dataTransferLog.ReturnData = _ReturnData;
                }

                return dataTransferLog;
            } // End lock
        }



        public override string ToString()
        {
            string comPortName = "COM N/A";
            try
            {
                _RS485COM.GetCOMPort(out comPortName);
            }
            catch { }

            byte latency = 0;
            try
            {
                _RS485COM.GetLatency(ref latency);
            }
            catch { }


            return string.Format("{0}.{1} on {2}: Baud {3:0}, Flowcontrol {4}, Latency {5}", _Type, _ID,comPortName, _Baudrate, _FlowControl.ToString(), latency.ToString());
        }




    }






}
