using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace iSTP_Embedded
{
    public partial class EmbeddedPacket
    {
        private Byte[] ReadBuffer = new Byte[8];
        private enum PacketParsingStatus
        {
            Header_s,
            Number_s,
            TimeStamp_s,
            Type_s,
            ID_s,
            Length_s,
            Data_s,
            Checksum_s,
            Exit_s
        }

        public enum ReadResult
        {
            PacketReceived,
            CksError,
            Cancelled,
            Error
        }

        private Boolean ReadToBuffer(Stream InStream, Byte[] buffer, int Length, WaitHandle CancelEvent)
        {
            int pos = 0;
            IAsyncResult Result;
            WaitHandle[] WaitHandles = new WaitHandle[2];
            WaitHandles[1] = CancelEvent;
            Boolean Response = true;

            while (Length != pos)
            {                
                Result = InStream.BeginRead(buffer, pos, (Length - pos), null, null);
                WaitHandles[0] = Result.AsyncWaitHandle;

                if (0 == WaitHandle.WaitAny(WaitHandles))
                {
                    pos += InStream.EndRead(Result);
                }
                else
                {
                    Response = false;
                    break;
                }
            }

            return Response;
        }


        public ReadResult ReadFromStream(Stream InStream, WaitHandle CancelEvent, Object PacketLock)
        {
            int packetHdr;
            ReadResult Result = ReadResult.Error;
            PacketParsingStatus Status = PacketParsingStatus.Header_s;

            try
            {

                do
                {
                    switch (Status)
                    {
                        case PacketParsingStatus.Header_s:
                            packetHdr = 0;

                            while (packetHdr < 4)
                            {
                                if (false == ReadToBuffer(InStream, ReadBuffer, 1, CancelEvent))
                                {
                                    Result = ReadResult.Cancelled;
                                    Status = PacketParsingStatus.Exit_s;
                                    break;
                                }

                                if ('#' == ReadBuffer[0])
                                {
                                    packetHdr++;
                                }
                                else
                                {
                                    packetHdr = 0;
                                }
                            }
                            if (packetHdr == 4)
                            {
                                Status = PacketParsingStatus.Number_s;
                            }
                            break;

                        case PacketParsingStatus.Number_s:
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                UInt32 NewMessageNumber = BinaryConverter.GetUInt32(ReadBuffer, 0);

                                MsgNumber = NewMessageNumber;
                                Status = PacketParsingStatus.TimeStamp_s;
                                Monitor.Enter(PacketLock);
                            }
                            break;

                        case PacketParsingStatus.TimeStamp_s:
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt64), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                MsgTimeStamp = BinaryConverter.GetDouble(ReadBuffer, 0);
                                Status = PacketParsingStatus.Type_s;
                            }
                            break;

                        case PacketParsingStatus.Type_s:
                            #region legacy protocol
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                MsgType = BinaryConverter.GetUInt32(ReadBuffer, 0);

                                Status = PacketParsingStatus.ID_s;
                            }
                            break;
                            #endregion
                        #region new protocol
                        //if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                        //{
                        //    Result = ReadResult.Cancelled;
                        //    Status = PacketParsingStatus.Exit;
                        //    Monitor.Exit(PacketLock);
                        //}
                        //else
                        //{
                        //    MsgType = BinaryConverter.GetUInt32(ReadBuffer, 0);

                        //    Status = PacketParsingStatus.ID_s;
                        //}
                        //break;
                        #endregion

                        case PacketParsingStatus.ID_s:
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                MsgID = BinaryConverter.GetUInt32(ReadBuffer, 0);

                                Status = PacketParsingStatus.Length_s;
                            }
                            break;

                        case PacketParsingStatus.Length_s:
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                MsgLength = BinaryConverter.GetInt32(ReadBuffer, 0);

                                if (MsgLength <= EmbeddedPacket.MaxMsgDataLength)
                                {
                                    Status = PacketParsingStatus.Data_s;
                                }
                                else
                                {
                                    Status = PacketParsingStatus.Header_s;
                                    Monitor.Exit(PacketLock);
                                }
                            }
                            break;

                        case PacketParsingStatus.Data_s:
                            if (MsgLength > 0)
                            {
                                PrepareBuffer();
                                if (false == ReadToBuffer(InStream, MsgData, MsgLength, CancelEvent))
                                {
                                    Result = ReadResult.Cancelled;
                                    Status = PacketParsingStatus.Exit_s;
                                    Monitor.Exit(PacketLock);
                                }
                            }

                            Status = PacketParsingStatus.Checksum_s;
                            break;

                        case PacketParsingStatus.Checksum_s:
                            if (false == ReadToBuffer(InStream, ReadBuffer, sizeof(UInt32), CancelEvent))
                            {
                                Result = ReadResult.Cancelled;
                                Status = PacketParsingStatus.Exit_s;
                                Monitor.Exit(PacketLock);
                            }
                            else
                            {
                                MsgChecksum = BinaryConverter.GetUInt32(ReadBuffer, 0);
                                uint crc = CalcCRC32();
                                if (crc == MsgChecksum)
                                {
                                    Result = ReadResult.PacketReceived;
                                    Status = PacketParsingStatus.Exit_s;
                                    Monitor.Exit(PacketLock);
                                }
                                else
                                {
                                    Result = ReadResult.CksError;
                                    Status = PacketParsingStatus.Exit_s;
                                    Monitor.Exit(PacketLock);
                                }
                            }
                            break;
                    }
                } while (Status != PacketParsingStatus.Exit_s);
            }
            catch (IOException ex)
            {
                EmbeddedError.Add("EmbeddedPacketReader.cs", "Error during communication - problem with port", ex.ToString(), true);
            }

            return Result;
        }
    }
}
