/***************************************************************************
 * Copyright (c) 2009 Digi International Inc., All Rights Reserved
 * 
 * This software contains proprietary and confidential information of Digi
 * International Inc.  By accepting transfer of this copy, Recipient agrees
 * to retain this software in confidence, to prevent disclosure to others,
 * and to make no use of this software other than that for which it was
 * delivered.  This is an unpublished copyrighted work of Digi International
 * Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 * prohibited.
 * 
 * Restricted Rights Legend
 *
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 * Technical Data and Computer Software clause at DFARS 252.227-7031 or
 * subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 * Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 * Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 ***************************************************************************
 *
 * @Descr: XBeeSerialPort Class
 *
 * @History:
 *    10/01/09:   gsarabia      initial version.
 *
 ***************************************************************************/

#region Namespace Inclusions

using System;
using System.IO;
using System.IO.Ports;
using System.Threading;

#endregion

namespace Digi.ZigBee
{
    public class XBeeSerialPort : IDisposable
    {
        #region Variables

        private SerialPort InternalSerialPort = null;
        private string InternalPortName = "COM1";
        private BaudRateEnum InternalBaudRate = BaudRateEnum._9600;
        private ParityEnum InternalParity = ParityEnum.None;
        private int InternalDataBits = 8;
        private StopBits InternalStopBits = StopBits.One;
        
        private OperationModeEnum InternalOperationMode = OperationModeEnum.API;
        private static bool InternalATCmdMode = false;

        private MemoryStream InternalMemoryStream = new MemoryStream(4096);
        private bool Escape = false;

        private const byte StartDelimiter = 0x7E;  // Start Delimiter
        private const byte CR = 0x0D;  // "\r"

        private bool InternalUploading = false;

        #endregion

        #region Constructors

        /// <summary>Initializes a new instance of the XBeeSerialPort class using the specified port name.</summary>
        /// <param name="MyPortName">The port to use.</param>
        public XBeeSerialPort(string MyPortName)
        {
            InternalPortName = MyPortName;
            InternalSerialPort = new SerialPort(InternalPortName, (int)InternalBaudRate, (Parity)InternalParity, InternalDataBits, InternalStopBits);
        }

        /// <summary>Initializes a new instance of the XBeeSerialPort class using the specified port name and baud rate.</summary>
        /// <param name="MyPortName">The port to use.</param>
        /// <param name="MyBaudRate">The serial baud rate.</param>
        public XBeeSerialPort(string MyPortName, BaudRateEnum MyBaudRate)
        {
            InternalPortName = MyPortName;
            InternalBaudRate = MyBaudRate;
            InternalSerialPort = new SerialPort(InternalPortName, (int)InternalBaudRate, (Parity)InternalParity, InternalDataBits, InternalStopBits);
        }

        /// <summary>Initializes a new instance of the XBeeSerialPort class using the specified port name, baud rate, and parity bit.</summary>
        /// <param name="MyPortName">The port to use.</param>
        /// <param name="MyBaudRate">The serial baud rate.</param>
        /// <param name="MyParity">The parity-checking protocol.</param>
        public XBeeSerialPort(string MyPortName, BaudRateEnum MyBaudRate, ParityEnum MyParity)
        {
            InternalPortName = MyPortName;
            InternalBaudRate = MyBaudRate;
            InternalParity = MyParity;
            InternalSerialPort = new SerialPort(InternalPortName, (int)InternalBaudRate, (Parity)InternalParity, InternalDataBits, InternalStopBits);
        }

        #endregion

        #region Properties

            #region Baud Rate

            /// <summary>Gets or sets the serial baud rate.</summary>
            protected BaudRateEnum BaudRate
            {
                get
                {
                    return InternalBaudRate;
                }
                set
                {
                    if (Enum.IsDefined(typeof(BaudRateEnum), value))
                    {
                        InternalBaudRate = value;
                        InternalSerialPort.BaudRate = (int)InternalBaudRate;
                    }
                    else
                        throw new ArgumentOutOfRangeException("BaudRate", "Baud Rate is out of legal range.");
                }
            }

            #endregion

            #region Parity

            /// <summary>Gets or sets the parity-checking protocol.</summary>
            protected ParityEnum Parity
            {
                get
                {
                    return InternalParity;
                }
                set
                {
                    if (Enum.IsDefined(typeof(ParityEnum), value))
                    {
                        InternalParity = value;
                        InternalSerialPort.Parity = (Parity)InternalParity;
                    }
                    else
                        throw new ArgumentOutOfRangeException("Parity", "Parity is out of legal range.");
                }
            }

            #endregion

            #region Handshake

            /// <summary>Gets or sets the control protocol.</summary>
            protected HandshakeEnum Handshake
            {
                get
                {
                    return (HandshakeEnum)InternalSerialPort.Handshake;
                }
                set
                {
                    if (Enum.IsDefined(typeof(HandshakeEnum), value))
                    {
                        InternalSerialPort.Handshake = (Handshake)value;
                    }
                    else
                        throw new ArgumentOutOfRangeException("Handshake", "Handshake is out of legal range.");
                }
            }

            #endregion

            #region Operation Mode

            /// <summary>Gets or sets Operation Mode.</summary>
            protected OperationModeEnum OperationMode
            {
                get
                {
                    return InternalOperationMode;
                }
                set
                {
                    if (Enum.IsDefined(typeof(OperationModeEnum), value))
                        InternalOperationMode = value;
                    else
                        throw new ArgumentOutOfRangeException("Operation Mode", "Operation Mode is out of legal range.");
                }
            }

            #endregion

            #region AT Command Mode

            /// <summary>Gets or sets AT Command Mode.</summary>
            protected static bool ATCommandMode
            {
                get
                {
                    return InternalATCmdMode;
                }
                set
                {
                    InternalATCmdMode = value;
                }
            }

            #endregion

            #region Uploading

            /// <summary>sets Uploading.</summary>
            protected bool Uploading
            {
                set
                {
                    InternalUploading = value;
                }
            }

            #endregion

        #endregion

        #region Methods

            #region Open Xbee Serial Port

            /// <summary>Opens the Xbee Serial Port connection.</summary>
            public void Open()
            {
                if (!InternalSerialPort.IsOpen)
                {
                    InternalSerialPort.ReadTimeout = 2000;
                    InternalSerialPort.WriteTimeout = 2000;
                    InternalSerialPort.Handshake = (Handshake)HandshakeEnum.Request_To_Send;
                    InternalSerialPort.Open();
                    InternalSerialPort.DataReceived += new SerialDataReceivedEventHandler(XBeeSerialPortRx);
                    XBeeSerialPortOpen();
                }
                else
                    throw new InvalidOperationException("The specified serial port is open.");
            }

            #endregion

            #region Close Xbee Serial Port

            /// <summary>Closes the Xbee Serial Port connection and disposes of the internal MemoryStream object.</summary>
            public void Close()
            {
                if (InternalSerialPort != null && InternalSerialPort.IsOpen)
                {
                    InternalSerialPort.DataReceived -= new SerialDataReceivedEventHandler(XBeeSerialPortRx);
                    InternalSerialPort.Close();
                }
            }

            #endregion

            #region XBee Serial Port Tx

            /// <summary>Writes data from a buffer to the XBee Serial Port.</summary>
            /// <param name="OutBuffer">The byte array that contains the data to write to the port.</param>
            protected void XBeeSerialPortTx(byte[] OutBuffer)
            {
                if ((InternalOperationMode == OperationModeEnum.API_With_Escaped) && !InternalUploading)
                {
                    MemoryStream OutBufferWithEscaped = new MemoryStream();
                    OutBufferWithEscaped.WriteByte(StartDelimiter);   // Start Delimiter
                    for (int i = 1; i < OutBuffer.Length; i++)
                    {
                        if (OutBuffer[i] == (byte)Escape_Characters.XOn || OutBuffer[i] == (byte)Escape_Characters.XOff || OutBuffer[i] == (byte)Escape_Characters.Escape || OutBuffer[i] == (byte)Escape_Characters.FrameDelimiter)
                        {
                            OutBufferWithEscaped.WriteByte((byte)Escape_Characters.Escape);
                            OutBufferWithEscaped.WriteByte((byte)(0x20 ^ OutBuffer[i]));
                        }
                        else
                            OutBufferWithEscaped.WriteByte(OutBuffer[i]);
                    }
                    OutBuffer = OutBufferWithEscaped.ToArray();
                }
                InternalSerialPort.Write(OutBuffer, 0, OutBuffer.Length);
            }

            #endregion

            #region XBee Serial Port Rx

            /// <summary>Represents the method that will handle the DataReceived event of a SerialPort object.</summary>
            /// <param name="sender">The source of the event, which is the SerialPort object.</param>
            /// <param name="e">A SerialDataReceivedEventArgs object that contains the event data.</param>
            protected void XBeeSerialPortRx(object sender, SerialDataReceivedEventArgs e)
            {
                if (XBeeFrameReceived != null)
                {
                    int BytesToRead = InternalSerialPort.BytesToRead;
                    byte[] InBuffer = new byte[BytesToRead];
                    InternalSerialPort.Read(InBuffer, 0, BytesToRead);
                    if (InternalUploading)
                    {
                        XBeeFrameReceived(this, InBuffer);
                    }
                    else if (InternalOperationMode == OperationModeEnum.API || InternalOperationMode == OperationModeEnum.API_With_Escaped)
                    {
                        if (InternalOperationMode == OperationModeEnum.API_With_Escaped)
                        {
                            for (int i = 0; i < BytesToRead; i++)
                            {
                                if (Escape)
                                {
                                    Escape = false;
                                    InternalMemoryStream.WriteByte((byte)(0x20 ^ InBuffer[i]));
                                }
                                else if (InBuffer[i] == (byte)Escape_Characters.Escape)
                                {
                                    i++;
                                    if (i >= InBuffer.Length)
                                        Escape = true;
                                    else
                                        InternalMemoryStream.WriteByte((byte)(0x20 ^ InBuffer[i]));
                                }
                                else
                                    InternalMemoryStream.WriteByte(InBuffer[i]);
                            }
                        }
                        else    // InternalOperationMode == OperationModeEnum.API
                            InternalMemoryStream.Write(InBuffer, 0, BytesToRead);
NewApiFrame:            if (InternalMemoryStream.Length > 3)
                        {
                            InBuffer = InternalMemoryStream.ToArray();
                            InternalMemoryStream.SetLength(0);
                            if (InBuffer[0] == StartDelimiter)
                            {
                                ushort FrameDataLength = (ushort)(256 * InBuffer[1] + InBuffer[2]);
                                if (InBuffer.Length < (1 + 2 + FrameDataLength + 1))   // Start Delimiter + Length + Frame Data + Checksum
                                    InternalMemoryStream.Write(InBuffer, 0, InBuffer.Length);
                                else if (InBuffer.Length == (1 + 2 + FrameDataLength + 1))  // Start Delimiter + Length + Frame Data + Checksum
                                {
                                    Thread.Sleep(10);
                                    XBeeFrameReceived(this, InBuffer);
                                }
                                else    //InBuffer.Length > (Start Delimiter + Length + Frame Data + Checksum)
                                {
                                    byte[] SerialPortFrame = new byte[1 + 2 + FrameDataLength + 1];
                                    Array.Copy(InBuffer, SerialPortFrame, (1 + 2 + FrameDataLength + 1));
                                    InternalMemoryStream.Write(InBuffer, (1 + 2 + FrameDataLength + 1), InBuffer.Length - (1 + 2 + FrameDataLength + 1));
                                    Thread.Sleep(10);
                                    XBeeFrameReceived(this, SerialPortFrame);
                                    goto NewApiFrame;
                                }
                            }
                            else
                            {
                                int Position = 0;
                                foreach (byte Byte in InBuffer)
                                {
                                    if (Byte == StartDelimiter)
                                        break;
                                    Position++;
                                }
                                if (Position < InBuffer.Length)
                                {
                                    InternalMemoryStream.Write(InBuffer, Position, InBuffer.Length - Position);
                                    goto NewApiFrame;
                                }
                            }
                        }
                    }
                    else    // InternalOperationMode == OperationModeEnum.Transparent
                    {
                        if (InternalATCmdMode) // AT Command Mode Enable
                        {
                            InternalMemoryStream.Write(InBuffer, 0, BytesToRead);
NewFrame:                   if (InternalMemoryStream.Length > 0)
                            {
                                InBuffer = InternalMemoryStream.ToArray();
                                InternalMemoryStream.SetLength(0);
                                int Position = 1;
                                foreach (byte Byte in InBuffer)
                                {
                                    if (Byte == CR)
                                        break;
                                    Position++;
                                }
                                if (Position < InBuffer.Length)
                                {
                                    byte[] SerialPortFrame = new byte[Position];
                                    Array.Copy(InBuffer, SerialPortFrame, SerialPortFrame.Length);
                                    Thread.Sleep(10);
                                    XBeeFrameReceived(this, SerialPortFrame);
                                    InternalMemoryStream.Write(InBuffer, Position, InBuffer.Length - Position);
                                    goto NewFrame;
                                }
                                else
                                {
                                    if (Position == InBuffer.Length && InBuffer[Position - 1] == CR)
                                    {
                                        Thread.Sleep(10);
                                        XBeeFrameReceived(this, InBuffer);
                                    }
                                    else
                                        InternalMemoryStream.Write(InBuffer, 0, InBuffer.Length);
                                }
                            }
                        }
                        else    // AT Command Mode Disable
                        {
                            Thread.Sleep(10);
                            XBeeFrameReceived(this, InBuffer);
                        }
                    }
                }
            }

            #endregion

            #region Dispose

            /// <summary>Releases all resources used by the XBeeSerialPort.</summary>
            public void Dispose()
            {
                if (InternalSerialPort.IsOpen)
                    Close();
                InternalMemoryStream = null;
                InternalSerialPort.Dispose();
                InternalSerialPort = null;
            }

            #endregion

        #endregion

        #region Events & Delegates

        /// <summary>Represents the method that will handle the XBee serial port open event of a XBeeSerialPort object.</summary>
        protected event XBeeSerialPortOpenEventHandler XBeeSerialPortOpen;

        /// <summary>Represents the method that will handle the XBee serial port open event of a XBeeSerialPort object.</summary>
        /// <param name="sender">The source of the event, which is the XBeeSerialPort object.</param>
        protected delegate void XBeeSerialPortOpenEventHandler();

        /// <summary>Represents the method that will handle the XBee frame received event of a XBeeSerialPort object.</summary>
        protected event XBeeFrameReceivedEventHandler XBeeFrameReceived;

        /// <summary>Represents the method that will handle the XBee frame received event of a XBeeSerialPort object.</summary>
        /// <param name="sender">The source of the event, which is the XBeeSerialPort object.</param>
        /// <param name="XBeeSerialData">A byte array that contains the event data.</param>
        protected delegate void XBeeFrameReceivedEventHandler(XBeeSerialPort sender, byte[] XBeeSerialData);

        #endregion
    }
}
