/**
 * Cache to store generated match functions
 * @type {Object}
 */
let pMatchFunctionCache = {};
function compare_tagname(tag1, tag2) {
    if (!tag1) {
        return !tag2;
    }
    if (!tag2) {
        return !tag1;
    }
    return tag1.toLowerCase() === tag2.toLowerCase();
}
/**
 * Function cache
 */
const functionCache = {
    f145(el, tagName, classes) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        if (el.id !== tagName.substr(1)) {
            return false;
        }
        for (let cls = classes, i = 0; i < cls.length; i++) {
            if (el.classNames.indexOf(cls[i]) === -1) {
                return false;
            }
        }
        return true;
    },
    f45(el, tagName, classes) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        for (let cls = classes, i = 0; i < cls.length; i++) {
            if (el.classNames.indexOf(cls[i]) === -1) {
                return false;
            }
        }
        return true;
    },
    f15(el, tagName) {
        'use strict';
        tagName = tagName || '';
        if (el.id !== tagName.substr(1)) {
            return false;
        }
        return true;
    },
    f1(el, tagName) {
        'use strict';
        tagName = tagName || '';
        if (el.id !== tagName.substr(1)) {
            return false;
        }
    },
    f5() {
        'use strict';
        return true;
    },
    f55(el, tagName, classes, attr_key) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        attr_key = attr_key || '';
        const attrs = el.attributes;
        return attrs.hasOwnProperty(attr_key);
    },
    f245(el, tagName, classes, attr_key, value) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        attr_key = (attr_key || '').toLowerCase();
        value = value || '';
        const attrs = el.attributes;
        return Object.keys(attrs).some((key) => {
            const val = attrs[key];
            return key.toLowerCase() === attr_key && val === value;
        });
        // for (let cls = classes, i = 0; i < cls.length; i++) {if (el.classNames.indexOf(cls[i]) === -1){ return false;}}
        // return true;
    },
    f25(el, tagName, classes, attr_key, value) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        attr_key = (attr_key || '').toLowerCase();
        value = value || '';
        const attrs = el.attributes;
        return Object.keys(attrs).some((key) => {
            const val = attrs[key];
            return key.toLowerCase() === attr_key && val === value;
        });
        // return true;
    },
    f2(el, tagName, classes, attr_key, value) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        attr_key = (attr_key || '').toLowerCase();
        value = value || '';
        const attrs = el.attributes;
        return Object.keys(attrs).some((key) => {
            const val = attrs[key];
            return key.toLowerCase() === attr_key && val === value;
        });
    },
    f345(el, tagName, classes) {
        'use strict';
        tagName = tagName || '';
        classes = classes || [];
        if (!compare_tagname(el.tagName, tagName)) {
            return false;
        }
        for (let cls = classes, i = 0; i < cls.length; i++) {
            if (el.classNames.indexOf(cls[i]) === -1) {
                return false;
            }
        }
        return true;
    },
    f35(el, tagName) {
        'use strict';
        tagName = tagName || '';
        return compare_tagname(el.tagName, tagName);
    },
    f3(el, tagName) {
        'use strict';
        tagName = tagName || '';
        // if (el.tagName !== tagName) {
        // 	return false;
        // }
        return compare_tagname(el.tagName, tagName);
    }
};
/**
 * Matcher class to make CSS match
 *
 * @class Matcher
 */
export default class Matcher {
    /**
     * Creates an instance of Matcher.
     * @param {string} selector
     *
     * @memberof Matcher
     */
    constructor(selector) {
        this.nextMatch = 0;
        this.matchers = selector.split(' ').map((matcher) => {
            if (pMatchFunctionCache[matcher]) {
                return pMatchFunctionCache[matcher];
            }
            const parts = matcher.split('.');
            const tagName = parts[0];
            const classes = parts.slice(1).sort();
            // let source = '"use strict";';
            let function_name = 'f';
            let attr_key = '';
            let value = '';
            if (tagName && tagName !== '*') {
                if (tagName.startsWith('#')) {
                    // source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';// 1
                    function_name += '1';
                }
                else {
                    // https://github.com/taoqf/node-html-parser/issues/86
                    // const reg = /\[\s*([\w-]+)(\s*=\s*(((?<quote>'|")\s*(.*)(\k<quote>))|(\S*)))?\s*\]/.exec(tagName);
                    // `[a-b]`,`[ a-b ]`,`[a-b=c]`, `[a-b=c'd]`,`[a-b='c\' d"e ']`,`[ a-b = 'c\' d"e ' ]`,`[a-b="c' d\"e " ]`,`[ a-b = "c' d\"e " ]`
                    const reg = /\[\s*([\w-]+)(\s*=\s*(('\s*(.*)'|"\s*(.*)")|(\S*)))?\s*\]/.exec(tagName);
                    if (reg) {
                        attr_key = reg[1];
                        value = reg[5] || reg[6] || reg[7];
                        // source += `let attrs = el.attributes;for (let key in attrs){const val = attrs[key]; if (key == "${attr_key}" && val == "${value}"){return true;}} return false;`;// 2
                        function_name += '2';
                    }
                    else {
                        // source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';// 3
                        function_name += '3';
                    }
                }
            }
            if (classes.length > 0) {
                // source += 'for (let cls = ' + JSON.stringify(classes) + ', i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;';// 4
                function_name += '4';
            }
            // source += 'return true;';// 5
            function_name += '5';
            const obj = {
                func: functionCache[function_name],
                tagName: tagName || '',
                classes: classes || '',
                attr_key: attr_key || '',
                value: value || ''
            };
            // source = source || '';
            return (pMatchFunctionCache[matcher] = obj);
        });
    }
    /**
     * Trying to advance match pointer
     * @param  {HTMLElement} el element to make the match
     * @return {bool}           true when pointer advanced.
     */
    advance(el) {
        if (this.nextMatch < this.matchers.length &&
            this.matchers[this.nextMatch].func(el, this.matchers[this.nextMatch].tagName, this.matchers[this.nextMatch].classes, this.matchers[this.nextMatch].attr_key, this.matchers[this.nextMatch].value)) {
            this.nextMatch++;
            return true;
        }
        return false;
    }
    /**
     * Rewind the match pointer
     */
    rewind() {
        this.nextMatch--;
    }
    /**
     * Trying to determine if match made.
     * @return {bool} true when the match is made
     */
    get matched() {
        return this.nextMatch === this.matchers.length;
    }
    /**
     * Rest match pointer.
     * @return {[type]} [description]
     */
    reset() {
        this.nextMatch = 0;
    }
    /**
     * flush cache to free memory
     */
    flushCache() {
        pMatchFunctionCache = {};
    }
}
