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

using Fiesta.Networking.Encryption;
using System.Runtime.InteropServices;

namespace Fiesta.Networking.Packets
{
	public class InPacket : Villain.Networking.Packets.Packet
	{
		private bool _hasReadLength = false;
		private bool _hasReadType = false;

		public UInt16 Type
		{
			get;
			protected set;
		}
		
		public InPacket(byte[] data) : base(data)
		{
			ReadLength();
		}

		public override byte[] AppendMissingData(byte[] data)
		{
			byte[] remainingData = base.AppendMissingData(data);

			if (!_hasReadLength)
			{
				ReadLength();

				remainingData = AppendMissingData(data);
			}

			return remainingData;
		}

		public void ReadLength()
		{
			if (!_hasReadLength)
			{
				UInt16 length;

				if (MissingDataLength > 0)
				{
					if ((_memoryStream.Length - _memoryStream.Position) < MissingDataLength)
					{
						MissingDataLength -= (_memoryStream.Length - _memoryStream.Position);

						return;
					}

					length = BinaryReader.ReadUInt16();
				}
				else
				{
					length = BinaryReader.ReadByte();

					if (length == 0x00)
					{
						if ((_memoryStream.Length - _memoryStream.Position) < sizeof(UInt16))
						{
							MissingDataLength = (sizeof(UInt16) - (_memoryStream.Length - _memoryStream.Position));

							return;
						}

						length = BinaryReader.ReadUInt16();
					}
				}

				_hasReadLength = true;

				if (_memoryStream.Length < length)
				{
					MissingDataLength = (length - _memoryStream.Length);
				}
				else
				{
					MissingDataLength = 0;

					HandleExtraData(length);
				}
			}
		}
		
		public void ReadType()
		{
			if (!_hasReadType)
			{
				if ((_memoryStream.Length - 1) > 0xFF)
				{
					_memoryStream.Position = 3;
				}
				else
				{
					_memoryStream.Position = 1;
				}
				
				Type = BinaryReader.ReadUInt16();
				Type = (UInt16)(((Type >> 0xA) << 0x8) + (Type & 0x3FF));

				_hasReadType = true;
			}
		}

		public void Decrypt(IEncryption encryption)
		{
			byte[] data = _memoryStream.ToArray();

			Int32 offset = 1;

			if (data[0] == 0x00)
			{
				offset += 2;
			}

			data = encryption.Decrypt(data, offset, (data.Length - offset));

			_memoryStream = new MemoryStream(data);
			BinaryReader = new BinaryReader(_memoryStream);
		}
		
		public void Skip(Int32 length)
		{
			_memoryStream.Seek(length, SeekOrigin.Current);
		}

		public byte ReadByte()
		{
			return BinaryReader.ReadByte();
		}

		public sbyte ReadSByte()
		{
			return BinaryReader.ReadSByte();
		}

		public byte[] ReadBytes(Int32 count)
		{
			return BinaryReader.ReadBytes(count);
		}

		public Int16 ReadInt16()
		{
			return BinaryReader.ReadInt16();
		}

		public UInt16 ReadUInt16()
		{
			return BinaryReader.ReadUInt16();
		}

		public Int32 ReadInt32()
		{
			return BinaryReader.ReadInt32();
		}

		public UInt32 ReadUInt32()
		{
			return BinaryReader.ReadUInt32();
		}

		public Int64 ReadInt64()
		{
			return BinaryReader.ReadInt64();
		}

		public UInt64 ReadUInt64()
		{
			return BinaryReader.ReadUInt64();
		}

		public float ReadFloat()
		{
			return BinaryReader.Read();
		}

		public double ReadDouble()
		{
			return BinaryReader.ReadDouble();
		}

		public char ReadChar()
		{
			return BinaryReader.ReadChar();
		}

		public string ReadString()
		{
			return ReadString(false);
		}

		public string ReadString(bool toNullTerminator = false)
		{
			if (toNullTerminator)
			{
				string value = "";

				char nextCharacter;
				while ((nextCharacter = (char)BinaryReader.ReadByte()) != '\0')
				{
					value += nextCharacter;
				}

				return value;
			}
			else
			{
				return BinaryReader.ReadString();
			}
		}

		public string ReadString(Int32 length, bool trimNullTerminators = true)
		{
			string value = "";

			for (Int32 i = 0; i < length; i++)
			{
				value += (char)BinaryReader.ReadByte();
			}

			if (trimNullTerminators)
			{
				value = value.TrimEnd('\0');
			}

			return value;
		}

		public T[] ReadArray<T>(Int32 arraySize)
		{
			T[] value = new T[arraySize];

			byte[] bytes = ReadBytes((Marshal.SizeOf(typeof(T)) * arraySize));

			Buffer.BlockCopy(bytes, 0, value, 0, bytes.Length);

			return value;
		}
	}
}