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

namespace Villain.Data.Types
{
	public sealed class BitFields<T>
	{
		private UInt64 _value = 0;
		private Int32 _position = 0;

		private Int32 _remainingLength
		{
			get => (64 - _position);
		}

		public BitFields()
		{
			SetupInitialPosition(typeof(T));
		}

		public BitFields(T value)
		{
			_value = Convert.ToUInt64(value);

			SetupInitialPosition(typeof(T));
		}

		private void SetupInitialPosition(Type type)
		{
			switch (Type.GetTypeCode(type))
			{
				case TypeCode.Byte:
				case TypeCode.SByte:
					_position = (64 - 8);
					break;

				case TypeCode.UInt16:
				case TypeCode.Int16:
					_position = (64 - 16);
					break;

				case TypeCode.UInt32:
				case TypeCode.Int32:
					_position = (64 - 32);
					break;

				case TypeCode.UInt64:
				case TypeCode.Int64:
					_position = 0;
					break;

				default:
					throw new ArgumentException("The supplied type is not supported");
			}
		}

		private UInt64 GetMask(Int32 length)
		{
			UInt64 left = UInt64.MaxValue;
			UInt64 right = 0;

			left = left << (64 - _position);

			if((_position + length) < 64)
			{
				right = UInt64.MaxValue;
				right = right >> (_position + length);
			}
			
			return ~(left | right);
		}

		public bool ReadBoolean()
		{
			UInt64 mask = GetMask(1);

			_position += 1;

			return Convert.ToBoolean((_value & mask) >> _remainingLength);
		}

		public byte ReadByte(Int32 length = 8)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToByte((_value & mask) >> _remainingLength);
		}

		public sbyte ReadSByte(Int32 length = 8)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToSByte((_value & mask) >> _remainingLength);
		}

		public UInt16 ReadUInt16(Int32 length = 16)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToUInt16((_value & mask) >> _remainingLength);
		}

		public Int16 ReadInt16(Int32 length = 16)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToInt16((_value & mask) >> _remainingLength);
		}

		public UInt32 ReadUInt32(Int32 length = 32)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToUInt32((_value & mask) >> _remainingLength);
		}

		public Int32 ReadInt32(Int32 length = 32)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToInt32((_value & mask) >> _remainingLength);
		}

		public UInt64 ReadUInt64(Int32 length = 64)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToUInt64((_value & mask) >> _remainingLength);
		}

		public Int64 ReadInt64(Int32 length = 64)
		{
			UInt64 mask = GetMask(length);

			_position += length;

			return Convert.ToInt64((_value & mask) >> _remainingLength);
		}
		
		public void Write<TWriteValue>(TWriteValue value)
		{
			Int32 valueLength = 0;

			switch (Type.GetTypeCode(value.GetType()))
			{
				case TypeCode.Byte:
				case TypeCode.SByte:
					valueLength = 8;
					break;

				case TypeCode.UInt16:
				case TypeCode.Int16:
					valueLength = 16;
					break;

				case TypeCode.UInt32:
				case TypeCode.Int32:
					valueLength = 32;
					break;

				case TypeCode.UInt64:
				case TypeCode.Int64:
					valueLength = 64;
					break;

				default:
					throw new ArgumentException("The supplied value is not supported");
			}
			
			UInt64 newValue = Convert.ToUInt64(value);

			newValue = newValue << (_remainingLength - valueLength);

			UInt64 keepMask = ~GetMask(valueLength);

			_value = (_value & keepMask);
			_value = (_value | newValue);

			_position += valueLength;
		}
	}
}