using System.IO;
using System.Net;
using System.Net.Sockets;
using skyscraper5.Skyscraper.IO;

namespace skyscraper5.Ietf.Rfc971
{
    public class InternetHeader
    {
        public InternetHeader(byte[] ipv4Header)
        {
            if (ipv4Header.Length < 20)
            {
                ChecksumValid = false;
                return;
            }

            MemoryStream ms = new MemoryStream(ipv4Header, false);
            byte readUInt8 = ms.ReadUInt8();
            Version = (readUInt8 & 0xf0) >> 4;
            IHL = (readUInt8 & 0x0f);

            TOS = new TypeOfService(ms.ReadUInt8());
            TotalLength = ms.ReadUInt16BE();
            Identification = ms.ReadUInt16BE();

            ushort readUInt16Be = ms.ReadUInt16BE();
            DontFragment = (readUInt16Be & 0x4000) != 0;
            MoreFragments = (readUInt16Be & 0x2000) != 0;
            FragmentOffset = (readUInt16Be & 0x1fff);

            TimeToLive = ms.ReadUInt8();
            Protocol = ms.ReadUInt8();
            HeaderChecksum = ms.ReadUInt16BE();

            HeaderLength = IHL * 4;

            SourceAddress = new IPAddress(ms.ReadBytes(4));
			byte[] destAddresBytes = ms.ReadBytes(4);
			int multicastTemp = destAddresBytes[0];
			multicastTemp &= 0b11100000;
			multicastTemp >>= 4;
            if (multicastTemp == 0b1110)
                IsDestinationMulticast = true;

			DestinationAddress = new IPAddress(destAddresBytes);

			int optionsLength = HeaderLength - (int)ms.Position;

            Options = ms.ReadBytes(optionsLength);
            CalculateChecksum(ipv4Header, IHL);

            NextHeaderOffset = (int)ms.Position;
        }

        public InternetHeader(int trafficClass, int flowLabel, ushort payloadLength, byte nextHeader, byte hopLimit, IPAddress sourceAddress, IPAddress destinationAddress)
        {
	        DestinationAddress = destinationAddress;
	        SourceAddress = sourceAddress;
	        Protocol = nextHeader;
	        TimeToLive = hopLimit;
	        TotalLength = (ushort)(payloadLength + 40);

	        if (DestinationAddress.AddressFamily == AddressFamily.InterNetworkV6)
		        Version = 6;
            else if (DestinationAddress.AddressFamily == AddressFamily.InterNetwork)
		        Version = 4;
	        else
		        throw new NotSupportedException(DestinationAddress.AddressFamily.ToString());

	        ChecksumValid = true;
        }

        public int HeaderLength { get; private set; }

        private void CalculateChecksum(byte[] buffer, int ihl)
        {
            MemoryStream ms = new MemoryStream(buffer, false);
            ushort checksum = ms.ReadUInt16BE();    //Version
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Total Length
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Identification
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Flags
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //TTL

            ms.ReadUInt16BE();
            AddWithCarry(ref checksum, 0);                          //Checksum

            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Source IP
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Source IP
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Destination IP
            AddWithCarry(ref checksum, ms.ReadUInt16BE());          //Destination IP
            if (IHL > 5)
            {
                int left = IHL;
                left -= 5;
                for (int i = 0; i < left; i++)
                {
                    AddWithCarry(ref checksum, ms.ReadUInt16BE());
                    AddWithCarry(ref checksum, ms.ReadUInt16BE());
                }
            }

            checksum = (ushort)~checksum;

            CalculatedChecksum = (ushort)checksum;
            ChecksumValid = CalculatedChecksum == HeaderChecksum;
        }

        private void AddWithCarry(ref ushort checksum, ushort addMe)
        {
            ushort oldChecksum = checksum;
            checksum += addMe;
            if (checksum < oldChecksum)
                checksum++;
        }

        public byte[] Options { get; private set; }

        public IPAddress DestinationAddress { get; private set; }

        public IPAddress SourceAddress { get; private set; }

        public ushort HeaderChecksum { get; private set; }

        public byte Protocol { get; private set; }

        public byte TimeToLive { get; private set; }

        public int FragmentOffset { get; private set; }

        public bool MoreFragments { get; private set; }

        public bool DontFragment { get; private set; }

        public ushort Identification { get; private set; }

        public ushort TotalLength { get; private set; }

        public TypeOfService TOS { get; private set; }
        public int IHL { get; private set; }

        public int Version { get; private set; }

        public bool ChecksumValid { get; private set; }
        public ushort CalculatedChecksum { get; private set; }
		public bool IsDestinationMulticast { get; }

        public int NextHeaderOffset { get; private set; }
	}
}
