using skyscraper5.Skyscraper.Plugins;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security.AccessControl;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace skyscraper5.src.Skyscraper.Scraper.Dns
{
    public class SkyscraperDnsCache
    {
        public SkyscraperDnsCache(IDnsDataSource dataSource)
        {
            _dnsDataSource = dataSource;
		}

        private Dictionary<IPAddress, string> _dnsCache;
        private IDnsDataSource _dnsDataSource;
        private static long cacheHits, cacheMiss;
        private static long dbMiss;
        

        internal string GetDnsName(IPAddress source)
        {
            if (_dnsCache == null)
                _dnsCache = new Dictionary<IPAddress, string>();
            if (!_dnsCache.ContainsKey(source))
            {
                string fromDataSource = null;
                if (!IsSpecialIpAddress(source))
                    fromDataSource = _dnsDataSource.DnsIpToName(source);
                else
                    fromDataSource = source.ToString();

                _dnsCache.Add(source, fromDataSource);
                return fromDataSource;
            }

            if (_dnsCache.ContainsKey(source) && _dnsCache[source] != null)
            {
                cacheHits++;
            }
            else
            {
                cacheMiss++;
            }

            if (_dnsCache[source] == null)
            {
                _dnsCache.Remove(source);
                return GetDnsName(source);
            }
			return _dnsCache[source];
		}

        private bool packetProcessingDisabled;
        private IDnsParser dnsPacketParser;
        public KeyValuePair<string,IPAddress> LastLearned { get; private set; }
        internal bool ProcessDnsPacket(byte[] payload)
        {
            if (packetProcessingDisabled)
                return false;

            if (dnsPacketParser == null)
            {
				IReadOnlyList<IDnsParser> dnsParsers = PluginManager.GetInstance().GetDnsParsers();
                switch(dnsParsers.Count)
                {
                    case 0:
						packetProcessingDisabled = true;
						return false;
                    case 1:
						dnsPacketParser = dnsParsers[0];
                        break;
                    default:
                        throw new NotImplementedException("more than 1 dns parser");
				}
            }

			DnsParseResult dnsParseResult = dnsPacketParser.Parse(payload);
            if (dnsParseResult == null)
                return false;
            if (dnsParseResult.Records == null)
                return false;
            if (dnsParseResult.Records.Count == 0)
                return false;
            if (_dnsCache == null)
                _dnsCache = new Dictionary<IPAddress, string>();
            bool result = false;
            foreach(DnsRecord record in dnsParseResult.Records)
            {
                bool isIpAddress = (record.RecordType.Item2.Equals("A") || record.RecordType.Item2.Equals("AAAA"));
                if (isIpAddress)
                {   
                    _dnsCache[record.IP] = record.Domain;
                    LastLearned = new KeyValuePair<string, IPAddress>(record.Domain, record.IP);
					bool ipCheck = !_dnsDataSource.TestForIp(record.IP);
                    if (ipCheck)
                        result = true;
                    else
                        dbMiss++;
                }
                _dnsDataSource.RememberDnsRecord(record);
            }
            return result;
        }

        private bool IsSpecialIpAddress(IPAddress ipAddress)
        {
			byte[] bytes = ipAddress.GetAddressBytes();
            if (bytes[0] == 0)   //Current Network
                return true;

            if (bytes[0] == 10) //Local communications
                return true;    

            if (bytes[0] == 10 && bytes[1] >= 64 && bytes[1] <= 127)    //Shared Address Space
                return true;

            if (bytes[0] == 127)    //Loopback       
                return true;

            if (bytes[0] == 169 && bytes[1] == 254) //Link local addresses
                return true;

            if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31)    //Local communications
                return true;

            if (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 0)  //IETF Protocol assignments
                return true;

			if (bytes[0] == 192 && bytes[1] == 0 && bytes[2] == 2)  //TEST-NET-1
				return true;

			if (bytes[0] == 192 && bytes[1] == 88 && bytes[2] == 99)  //Reserved
				return true;

			if (bytes[0] == 192 && bytes[1] == 168)  //Local communications
				return true;

            if (bytes[0] == 198 && (bytes[1] == 18 || bytes[1] == 19))
                return true;

			if (bytes[0] == 198 && (bytes[1] == 51 && bytes[2] == 100))  //TEST-NET-2
				return true;

			if (bytes[0] == 203 && bytes[1] == 0 && bytes[2] == 113)  //TEST-NET-2
				return true;

            if (bytes[0] >= 224 && bytes[0] <= 239)     //Multicast
                return true;

            if (bytes[0] >= 240 && bytes[0] <= 255)     //Reserved
                return true;
            return false;
		}
    }


}
