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

using Fiesta.Networking.Encryption;
using Villain.Data;

namespace Fiesta.Networking.Packets
{
	public class OutPacket : Villain.Networking.Packets.Packet
	{
		public virtual UInt16 Type { get; protected set; }

		public OutPacket() : base()
		{
			PacketTypeAttribute packetTypeAttribute = GetType().GetCustomAttributes(typeof(PacketTypeAttribute), true).FirstOrDefault() as PacketTypeAttribute;

			if (packetTypeAttribute != null)
			{
				Type = packetTypeAttribute.Type;
			}
		}

		public OutPacket(UInt16 type) : base()
		{
			Type = type;
		}

		public OutPacket(Enum type) : base()
		{
			Type = type.GetValue<UInt16>();
		}

		private void WriteHeader()
		{
			Int64 size = _memoryStream.Length;

			size += sizeof(UInt16);

			if (size > byte.MaxValue)
			{
				_memoryStream.Position = (sizeof(byte) + sizeof(UInt16) + sizeof(UInt16));
				BinaryWriter.Write(_memoryStream.ToArray());
				_memoryStream.Position = 0;

				BinaryWriter.Write(0x00);
				BinaryWriter.Write((UInt16)size);
			}
			else
			{
				_memoryStream.Position = (sizeof(byte) + sizeof(UInt16));
				BinaryWriter.Write(_memoryStream.ToArray());
				_memoryStream.Position = 0;

				BinaryWriter.Write((byte)size);
			}

			Type = (UInt16)(((Type >> 0x8) << 0xA) + (Type & 0xFF));
			BinaryWriter.Write(Type);

			_memoryStream.Position = _memoryStream.Length;
		}

		public override byte[] GetData()
		{
			WriteHeader();

			return base.GetData();
		}

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

			Int32 offset = 1;

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

			_memoryStream = new MemoryStream(encryption.Decrypt(data, offset, (data.Length - offset)));
			BinaryWriter = new BinaryWriter(_memoryStream);
		}

		public enum PaddingDirection
		{
			Left,
			Right
		}

		public void WriteFiller(Int32 length, byte value)
		{
			for (Int32 i = 0; i < length; i++)
			{
				WriteByte(value);
			}
		}

		public void WriteByte(byte value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteSByte(sbyte value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteBytes(byte[] value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteInt16(Int16 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteUInt16(UInt16 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteInt32(Int32 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteUInt32(UInt32 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteInt64(Int64 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteUInt64(UInt64 value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteFloat(float value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteDouble(double value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteChar(char value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteString(string value, Int32 padding = 0, PaddingDirection direction = PaddingDirection.Right, char pad = '\0')
		{
			if (padding > 0)
			{
				switch (direction)
				{
					case PaddingDirection.Left:
						if (value.Length < padding)
						{
							WriteFiller((padding - value.Length), (byte)pad);
						}

						WriteString(value);
						break;

					case PaddingDirection.Right:
						WriteString(value);

						if (value.Length < padding)
						{
							WriteFiller((padding - value.Length), (byte)pad);
						}
						break;
				}
			}
			else
			{
				WriteString(value);
			}
		}

		public void WriteString(string value, bool addNullTerminator = false)
		{
			if (addNullTerminator)
			{
				value += '\0';
			}

			WriteString(value);
		}
		
		public void WriteString(string value)
		{
			BinaryWriter.Write(Encoding.ASCII.GetBytes(value));
		}

		public void WriteStringWithLength(string value)
		{
			BinaryWriter.Write(value);
		}

		public void WriteArray<T>(T[] value) where T : struct
		{
			byte[] bytes = new byte[value.Length * Marshal.SizeOf(typeof(T))];

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

			WriteBytes(bytes);
		}
	}
}