/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export class ParsedURL {
    static REGEXP: RegExp = new RegExp('^([^:/\\?#]+)?:\\/\\/([^:/\\?#]+)(:([^:/\\?#]+))?(\\/([^\\?#]*))*(\\?([^#]*))?(#(.*))?');

    scheme: string;
    host: string;
    port: string;
    path: string;
    query: string;
    fragment: string;

    static parse(url: string): ParsedURL {
        let result: ParsedURL;

        let matches = url.match(ParsedURL.REGEXP);
        if (matches && matches.length > 0) {
            result = {
                scheme: matches[1],
                host: matches[2],
                port: matches[4],
                path: matches[5],
                query: matches[6],
                fragment: matches[7]
            };
        }
        return result;
    }
}

export class ValidationUtils {
    private static VALID_NAME_REGEXP: RegExp = new RegExp('^[\\w\\d-_]*$');
    private static DN_TEMPLATE_REGEXP: RegExp =
        new RegExp('(?:[A-Za-z][\\w-]*|\\d+(?:\\.\\d+)*)' +
            '=(?:#(?:[\\dA-Fa-f]{2})+|(?:[^,=\\+<>#;\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})*|"(?:[^\\"]|\\[,=\\+<>#;\\"]|' +
            '\\[\\dA-Fa-f]{2})*")' +
            '(?:\\+(?:[A-Za-z][\\w-]*|\\d+(?:\\.\\d+)*)' +
            '=(?:#(?:[\\dA-Fa-f]{2})' +
            '+|(?:[^,=\\+<>#;\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})*|"(?:[^\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})*"))' +
            '*(?:,(?:[A-Za-z][\\w-]*|\\d+(?:\\.\\d+)*)' +
            '=(?:#(?:[\\dA-Fa-f]{2})+|(?:[^,=\\+<>#;\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})*|"(?:[^\\"]|\\[,=\\+<>#;\\"]|' +
            '\\[\\dA-Fa-f]{2})*")' +
            '(?:\\+(?:[A-Za-z][\\w-]*|\\d+(?:\\.\\d+)*)=(?:#(?:[\\dA-Fa-f]{2})+|(?:[^,=\\+<>#;\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})' +
            '*|"(?:[^\\"]|\\[,=\\+<>#;\\"]|\\[\\dA-Fa-f]{2})*"))*)*');


    private static PRINCIPAL_MAPPING_REGEXP = new RegExp('^(?:(?:[a-zA-Z\\*]+[\\,]?)+=[a-zA-Z]+[;]?)*$');
    private static SIGNED_NUMBER_REGEP = new RegExp('^-?\\d+$');

    static LDAP_URL_SCHEMES: string[] = ['ldap', 'ldaps'];
    static HTTP_URL_SCHEMES: string[] = ['http', 'https'];
    static CAS_PROTOCOLS: string[] = ['CAS10', 'CAS20', 'CAS20_PROXY', 'CAS30', 'CAS30_PROXY', 'SAML'];
    static HTTP_METHODS: string[] = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT'];

    static parseBoolean(value: string): boolean {
        let parsed: boolean;
        if (value) {
            try {
                parsed = JSON.parse(value);
            } catch (e) {
                // just return undefined
            }
        }
        return parsed;
    }


    static parseURL(url: string): ParsedURL {
        return ParsedURL.parse(url);
    }


    static isValidNumber(value: string): boolean {
        return (value && !isNaN(Number(value)));
    }

    static isValidSignedNumber(value: string): boolean {
        return ValidationUtils.SIGNED_NUMBER_REGEP.test(value);
    }

    static isValidBoolean(value: string): boolean {
        return (ValidationUtils.parseBoolean(value) !== undefined);
    }

    static isValidString(value: string): boolean {
        return (value && value.length > 0);
    }

    static isValidValue(validValues: string[], value: string): boolean {
        let isValid = false;
        if (value && validValues) {
            if (validValues.indexOf(value) > -1) {
                isValid = true;
            }
        }
        return isValid;
    }

    static isValidResourceName(name: string): boolean {
        return ValidationUtils.isValidString(name) && ValidationUtils.VALID_NAME_REGEXP.test(name);
    }

    static isValidURL(url: string): boolean {
        let isValid = false;

        let parsedURL = ValidationUtils.parseURL(url);
        if (parsedURL) {
            // Make sure it has at least a valid scheme, host and port
            if (parsedURL.scheme) {
                if (parsedURL.host) {
                    // Port is optional
                    isValid = parsedURL.port ? ValidationUtils.isValidNumber(parsedURL.port) : true;
                }
            }
        }

        return isValid;
    }


    static isValidURLOfScheme(url: string, acceptableSchemes?: string[]): boolean {
        let isValid = false;

        let parsedURL = ValidationUtils.parseURL(url);
        if (parsedURL) {
            // Make sure it has at least a valid scheme, host and port
            if (parsedURL.scheme) {
                if (acceptableSchemes && acceptableSchemes.indexOf(parsedURL.scheme) < 0) {
                    console.debug('\tParsed URL scheme is not among acceptable schemes');
                    return false;
                }
                if (parsedURL.host) {
                    // Port is optional
                    isValid = parsedURL.port ? ValidationUtils.isValidNumber(parsedURL.port) : true;
                }
            }
        }

        return isValid;
    }

    static isValidLdapURL(url: string): boolean {
        return ValidationUtils.isValidURLOfScheme(url, ValidationUtils.LDAP_URL_SCHEMES);
    }

    static isValidHttpURL(url: string): boolean {
        return ValidationUtils.isValidURLOfScheme(url, ValidationUtils.HTTP_URL_SCHEMES);
    }

    static isValidDNTemplate(dnTemplate: string): boolean {
        return ValidationUtils.DN_TEMPLATE_REGEXP.test(dnTemplate);
    }

    static isValidCASProtocol(protocol: string): boolean {
        let isValid = false;

        if (protocol) {
            isValid = (ValidationUtils.CAS_PROTOCOLS.indexOf(protocol) > -1);
        }

        return isValid;
    }

    static isValidPrincipalMapping(mapping: string): boolean {
        let isValid = false;

        if (mapping) {
            isValid = ValidationUtils.PRINCIPAL_MAPPING_REGEXP.test(mapping);
        }

        return isValid;
    }

    static isValidHTTPMethod(method: string): boolean {
        return ValidationUtils.HTTP_METHODS.includes(method);
    }

    static isValidHostName(hostName: string): boolean {
        // For now, make sure it's a valid string, and does NOT exceed the acceptable host name length
        return ValidationUtils.isValidString(hostName) && hostName.length < 256;
    }

}
