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

using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;

namespace EA.GameTalk
{
	class SocketReader
	{
		private Connection mConnection;

		int mSizeBufferMax = 2 * 1024 * 1024;
		byte[] mBuffer = null;
		int mBytesReadFromTheWire = 0;
		int mStartMsgIndex = 0;
		int mCurrentMessageLength = 0;
		int mEndOfCurrentMessage = 0;

		// Try expermenting with NetworkStream and BufferedStream (for network efficiency) one day
		/*NetworkStream mNetworkStream = null;
		BufferedStream mBufferedReaderStream = null;*/

		internal SocketReader(Connection connection)
		{
			mConnection = connection;
			mBuffer = new byte[mSizeBufferMax];

			// Try expermenting with NetworkStream and BufferedStream (for network efficiency) one day
			/*mNetworkStream = connection.GetStream();
			mBufferedReaderStream = new BufferedStream(mNetworkStream);*/
		}

		internal void ClearCurrentMessageState()
		{
			mCurrentMessageLength = 0;
			mEndOfCurrentMessage = 0;
		}
		/// <summary>
		/// If the user isn't careful this method can actually shrink their receiving
		/// buffer which isn't likely what the user will want in the long-term
		/// </summary>
		/// <param name="buffer"></param>
		/// <param name="currentMessageLength"></param>
		/// <param name="startMsgIndex"></param>
		/// <param name="bytesReadFromTheWire"></param>
		/// <param name="endOfCurrentMessage"></param>
		/// <returns></returns>
		private byte[] ResizeReceivingBuffer(byte[] buffer, int currentMessageLength,
			ref int startMsgIndex, ref int bytesReadFromTheWire, ref int endOfCurrentMessage)
		{
			byte[] tempBuffer = null;
			if (startMsgIndex > 0) // Just shift the bytes to the start of the array
			{
				tempBuffer = new byte[buffer.Length];
				Array.Copy(buffer, startMsgIndex, tempBuffer, 0, buffer.Length - startMsgIndex);
				bytesReadFromTheWire -= startMsgIndex;
				endOfCurrentMessage -= startMsgIndex;
				startMsgIndex = 0;
			}
			else // There is only one message - we have to increase the size of the buffer
			{
				tempBuffer = new byte[currentMessageLength];
				Array.Copy(buffer, 0, tempBuffer, 0, buffer.Length);
			}
			return tempBuffer;
		}

		/// <summary>
		/// Take one "haul" from the wire - then generate byte arrays perfectly sized to each GameTalkMessage
		/// </summary>
		internal void ReceiveData(ref byte[] singleGtmBuffer)
		{
			if (!mConnection.Client.Connected)
			{
				Thread.Sleep(50);
				return;
			}

			int numBytesToRead = mBuffer.Length - mBytesReadFromTheWire;
			if (numBytesToRead == 0)
			{
				Debug.Assert(false, "Somehow the dynamic buffer hasn't been resized and there is insufficient room for any new data");
				mConnection.Close();
				return;
			}
			// Read the incoming buffer - we can incrementally update the third parameter (instead of recomputing it every time)
			int size = mConnection.Client.Receive(mBuffer, mBytesReadFromTheWire, numBytesToRead, SocketFlags.None);
			mBytesReadFromTheWire += size;
			do // Need to iterate here to digest potentially multiple messages received in a single pull from the wire
			{
				if ((mCurrentMessageLength == 0) && (mBytesReadFromTheWire >= 4))
				{
					mCurrentMessageLength = BitConverter.ToInt32(mBuffer, mStartMsgIndex) + 4;
					// Check if we have a single message bigger than the remaining space in our mBuffer
					mEndOfCurrentMessage = mStartMsgIndex + mCurrentMessageLength;
					if (mEndOfCurrentMessage > mBuffer.Length)
						mBuffer = ResizeReceivingBuffer(mBuffer, mCurrentMessageLength, ref mStartMsgIndex, ref mBytesReadFromTheWire, ref mEndOfCurrentMessage);

					// There is a way to create this array once and only recreate it when "resizing needs" arise
					singleGtmBuffer = new byte[mCurrentMessageLength];
				}

				if ((mEndOfCurrentMessage == 0) || (mEndOfCurrentMessage > mBytesReadFromTheWire)) // more to come
				{
					// is it more efficient to copy parts incrementally or to just do a one time copy?
					break;
				}
				else if (mEndOfCurrentMessage <= mBytesReadFromTheWire)
				{
					Array.Copy(mBuffer, mStartMsgIndex, singleGtmBuffer, 0, mCurrentMessageLength);
					lock (mConnection.PacketWriteBuffer)
					{
						mConnection.PacketWriteBuffer.Add(singleGtmBuffer);
						Monitor.Pulse(mConnection.PacketWriteBuffer);
					}

					if (mEndOfCurrentMessage == mBytesReadFromTheWire)
					{
						// The last byte pulled from the wire was the last byte from the message just added to the queue - reset indices
						mBytesReadFromTheWire = 0;
						mStartMsgIndex = 0;
						//Trace.WriteLine("Connection: reset mBuffer indices");
					}
					else
						mStartMsgIndex += mCurrentMessageLength;

					ClearCurrentMessageState();
				}
			} while (mBytesReadFromTheWire > mStartMsgIndex + 4);
		}
	}
}
