var proxy = __PROXY__;

var direct = 'DIRECT';

var directDomains = __DIRECT_DOMAINS__;

var domainsUsingProxy = __DOMAINS__;

var localTlds = __LOCAL_TLDS__;

var cnips = __CN_IPS__;

var hasOwnProperty = Object.hasOwnProperty;

var ipRegExp = new RegExp(/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/);

function isIPv6(ip) {
    // Split the IP address into groups of hexadecimal digits
    const groups = ip.split(':');

    // An IPv6 address must have at least one group and at most 8 groups
    if (groups.length < 1 || groups.length > 8) {
        return false;
    }

    // Check that each group is a valid hexadecimal number
    for (const group of groups) {
        // Check that the group is not null, undefined, or an empty string before calling parseInt()
        if (group === null || group === undefined || group === '') {
            continue;
        }

        // Use parseInt() to check if the group is a valid hexadecimal number
        const value = parseInt(group, 16);
        if (isNaN(value) || value < 0 || value > 0xFFFF) {
            return false;
        }
    }

    // If the address contains a double colon, ensure that it appears only once
    if (ip.includes('::')) {
        if (ip.indexOf('::') !== ip.lastIndexOf('::')) {
            return false;
        }
    }

    // The address is valid if it passes all the checks
    return true;
}

function convertAddress(ipchars) {
    var bytes = ipchars.split('.');
    var result = ((bytes[0] & 0xff) << 24) |
                 ((bytes[1] & 0xff) << 16) |
                 ((bytes[2] & 0xff) <<  8) |
                  (bytes[3] & 0xff);
    return result;
}

function match(ip) {
    var left = 0, right = cnips.length;
    do {
        var mid = Math.floor((left + right) / 2),
            ipf = (ip & cnips[mid][1]) >>> 0,
            m   = (cnips[mid][0] & cnips[mid][1]) >>> 0;
        if (ipf == m) {
            return true;
        } else if (ipf > m) {
            left  = mid + 1;
        } else {
            right = mid;
        }
    } while (left + 1 <= right)
    return false;
}

function isInDirectDomain(host) {
    if (hasOwnProperty.call(directDomains, host)) {
        return true;
    }
    for (var domain in directDomains) {
        if (host.endsWith('.' + domain)) {
            return true;
        }
    }
    return false;
}

function isInProxyDomain(host) {
    if (hasOwnProperty.call(domainsUsingProxy, host)) {
        return true;
    }
    for (var domain in domainsUsingProxy) {
        if (host.endsWith('.' + domain)) {
            return true;
        }
    }
    return false;
}

function isLocalTestDomain(domain) {
    // Chrome uses .test as testing gTLD.
    var tld = domain.substring(domain.lastIndexOf('.'));
    if (tld === domain) {
        return false;
    }
    return Object.hasOwnProperty.call(localTlds, tld);
}

/* https://github.com/frenchbread/private-ip */
function isPrivateIp(ip) {
    return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(ip) ||
        /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(ip) ||
        /^(::f{4}:)?172\.(1[6-9]|2\d|30|31)\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(ip) ||
        /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(ip) ||
        /^(::f{4}:)?169\.254\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(ip) ||
        /^f[cd][0-9a-f]{2}:/i.test(ip) ||
        /^fe80:/i.test(ip) ||
        /^::1$/.test(ip) ||
        /^::$/.test(ip);
}

function FindProxyForURL(url, host) {
    if (isPlainHostName(host)
        || isPrivateIp(host)
        || isLocalTestDomain(host)
        || host === 'localhost') {
        alert(`${host} MATCHES LOCAL, USING DIRECT`)
        return direct;
    }

    if (shExpMatch(url, "http:*")) {
        alert(`${host} IS USING HTTP, USING DIRECT`)
        return direct;
    }

    if (!ipRegExp.test(host)) {
        if (isInDirectDomain(host)) {
            alert(`${host} MATCHES DIRECT DOMAIN`)
            return direct
        }
        if (isInProxyDomain(host)) {
            alert(`${host} MATCHES PROXY DOMAIN`)
            return proxy;
        }
        strIp = dnsResolve(host);
    } else {
        strIp = host
    }

    if (!strIp) {
        return proxy;
    }

    intIp = convertAddress(strIp);
    if (match(intIp)) {
        alert(`${host} MATCHES CNIP`)
        return direct;
    }

    alert(`${host} NO RULES WARE MATCHED, USING PROXY`)
    return proxy;
}