using System.Text;
using System.Runtime.InteropServices;
using LibEveryFileExplorer.Collections;
using System;
using System.IO;

namespace LibEveryFileExplorer.IO
{
	public class EndianBinaryReader : IDisposable
	{
		private bool disposed;
		private byte[] buffer;

		public Stream BaseStream { get; private set; }
		public Endianness Endianness { get; set; }
		public Endianness SystemEndianness { get { return BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; } }

		private bool Reverse { get { return SystemEndianness != Endianness; } }

		public EndianBinaryReader(Stream baseStream)
			: this(baseStream, Endianness.BigEndian)
		{ }

		public EndianBinaryReader(Stream baseStream, Endianness endianness)
		{
			if (baseStream == null) throw new ArgumentNullException("baseStream");
			if (!baseStream.CanRead) throw new ArgumentException("baseStream");

			BaseStream = baseStream;
			Endianness = endianness;
		}

		~EndianBinaryReader()
		{
			Dispose(false);
		}

		private void FillBuffer(int bytes, int stride)
		{
			if (buffer == null || buffer.Length < bytes)
				buffer = new byte[bytes];

			BaseStream.Read(buffer, 0, bytes);

			if (Reverse)
				for (int i = 0; i < bytes; i += stride)
				{
					Array.Reverse(buffer, i, stride);
				}
		}

		public byte ReadByte()
		{
			FillBuffer(1, 1);

			return buffer[0];
		}

		public byte[] ReadBytes(int count)
		{
			byte[] temp;

			FillBuffer(count, 1);
			temp = new byte[count];
			Array.Copy(buffer, 0, temp, 0, count);
			return temp;
		}

		public sbyte ReadSByte()
		{
			FillBuffer(1, 1);

			unchecked
			{
				return (sbyte)buffer[0];
			}
		}

		public sbyte[] ReadSBytes(int count)
		{
			sbyte[] temp;

			temp = new sbyte[count];
			FillBuffer(count, 1);

			unchecked
			{
				for (int i = 0; i < count; i++)
				{
					temp[i] = (sbyte)buffer[i];
				}
			}

			return temp;
		}

		public char ReadChar(Encoding encoding)
		{
			int size;

			size = GetEncodingSize(encoding);
			FillBuffer(size, size);
			return encoding.GetChars(buffer, 0, size)[0];
		}

		public char[] ReadChars(Encoding encoding, int count)
		{
			int size;

			size = GetEncodingSize(encoding);
			FillBuffer(size * count, size);
			return encoding.GetChars(buffer, 0, size * count);
		}

		private static int GetEncodingSize(Encoding encoding)
		{
			if (encoding == Encoding.UTF8 || encoding == Encoding.ASCII)
				return 1;
			else if (encoding == Encoding.Unicode || encoding == Encoding.BigEndianUnicode)
				return 2;

			return 1;
		}

		public string ReadStringNT(Encoding encoding)
		{
			string text;

			text = "";

			do
			{
				text += ReadChar(encoding);
			} while (!text.EndsWith("\0", StringComparison.Ordinal));

			return text.Remove(text.Length - 1);
		}

		public string ReadString(Encoding encoding, int count)
		{
			return new string(ReadChars(encoding, count));
		}

		public double ReadDouble()
		{
			const int size = sizeof(double);
			FillBuffer(size, size);
			return BitConverter.ToDouble(buffer, 0);
		}

		public double[] ReadDoubles(int count)
		{
			const int size = sizeof(double);
			double[] temp;

			temp = new double[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToDouble(buffer, size * i);
			}
			return temp;
		}

		public Single ReadSingle()
		{
			const int size = sizeof(Single);
			FillBuffer(size, size);
			return BitConverter.ToSingle(buffer, 0);
		}

		public Single[] ReadSingles(int count)
		{
			const int size = sizeof(Single);
			Single[] temp;

			temp = new Single[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToSingle(buffer, size * i);
			}
			return temp;
		}

		public Vector2 ReadVector2()
		{
			return new Vector2(ReadSingle(), ReadSingle());
		}

		public Vector3 ReadVector3()
		{
			return new Vector3(ReadSingle(), ReadSingle(), ReadSingle());
		}

		public Vector4 ReadVector4()
		{
			return new Vector4(ReadSingle(), ReadSingle(), ReadSingle(), ReadSingle());
		}

		public Single ReadFx16()
		{
			return ReadInt16() / 4096f;
		}

		public Vector3 ReadVecFx16()
		{
			return new Vector3(ReadFx16(), ReadFx16(), ReadFx16());
		}

		public Single ReadFx32()
		{
			return ReadInt32() / 4096f;
		}

		public Single[] ReadFx32s(int count)
		{
			Single[] result = new float[count];
			for (int i = 0; i < count; i++)
			{
				result[i] = ReadInt32() / 4096f;
			}
			return result;
		}

		public Vector3 ReadVecFx32()
		{
			return new Vector3(ReadFx32(), ReadFx32(), ReadFx32());
		}

		public Int32 ReadInt32()
		{
			const int size = sizeof(Int32);
			FillBuffer(size, size);
			return BitConverter.ToInt32(buffer, 0);
		}

		public Int32[] ReadInt32s(int count)
		{
			const int size = sizeof(Int32);
			Int32[] temp;

			temp = new Int32[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToInt32(buffer, size * i);
			}
			return temp;
		}

		public Int64 ReadInt64()
		{
			const int size = sizeof(Int64);
			FillBuffer(size, size);
			return BitConverter.ToInt64(buffer, 0);
		}

		public Int64[] ReadInt64s(int count)
		{
			const int size = sizeof(Int64);
			Int64[] temp;

			temp = new Int64[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToInt64(buffer, size * i);
			}
			return temp;
		}

		public Int16 ReadInt16()
		{
			const int size = sizeof(Int16);
			FillBuffer(size, size);
			return BitConverter.ToInt16(buffer, 0);
		}

		public Int16[] ReadInt16s(int count)
		{
			const int size = sizeof(Int16);
			Int16[] temp;

			temp = new Int16[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToInt16(buffer, size * i);
			}
			return temp;
		}

		public UInt16 ReadUInt16()
		{
			const int size = sizeof(UInt16);
			FillBuffer(size, size);
			return BitConverter.ToUInt16(buffer, 0);
		}

		public UInt16[] ReadUInt16s(int count)
		{
			const int size = sizeof(UInt16);
			UInt16[] temp;

			temp = new UInt16[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToUInt16(buffer, size * i);
			}
			return temp;
		}

		public UInt32 ReadUInt32()
		{
			const int size = sizeof(UInt32);
			FillBuffer(size, size);
			return BitConverter.ToUInt32(buffer, 0);
		}

		public UInt32[] ReadUInt32s(int count)
		{
			const int size = sizeof(UInt32);
			UInt32[] temp;

			temp = new UInt32[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToUInt32(buffer, size * i);
			}
			return temp;
		}

		public UInt64 ReadUInt64()
		{
			const int size = sizeof(UInt64);
			FillBuffer(size, size);
			return BitConverter.ToUInt64(buffer, 0);
		}

		public UInt64[] ReadUInt64s(int count)
		{
			const int size = sizeof(UInt64);
			UInt64[] temp;

			temp = new UInt64[count];
			FillBuffer(size * count, size);

			for (int i = 0; i < count; i++)
			{
				temp[i] = BitConverter.ToUInt64(buffer, size * i);
			}
			return temp;
		}

		public void Close()
		{
			Dispose();
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (!disposed)
			{
				if (disposing)
				{
					if (BaseStream != null)
					{
						BaseStream.Close();
					}
				}

				buffer = null;

				disposed = true;
			}
		}
	}

	public enum Endianness
	{
		BigEndian,
		LittleEndian,
	}
}
