'use strict';

function isRegExp(obj) {
    return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
}

/**
 * 
 * @param {{}} pattern The pattern object to use
 * @param {string} propName The name of the property to compare
 * @param {(event: any) => any} [eventPropertyExtractor] If given, this function is used to extract the property value from the event object. Otherwise the propName is used
 */
function stringOrRegExpCompare(pattern, propName, eventPropertyExtractor) {
    /** @type {RegExp | string | string[]} */
    const field = pattern[propName];
    const hasExtractor = typeof eventPropertyExtractor === 'function';
    if (isRegExp(field)) {
        return function (event) {
            const eventValue = hasExtractor ? eventPropertyExtractor(event) : event[propName];
            return (eventValue != null && /** @type {RegExp} */ (field).test(eventValue));
        };
    } else if (Array.isArray(field)) {
        return function (event) {
            const eventValue = hasExtractor ? eventPropertyExtractor(event) : event[propName];
            // An array matches when any element is found that satisfies the constraint
            return eventValue != null  && field.find(f => f === eventValue) != null;
        };
    } else {
        return function (event) {
            const eventValue = hasExtractor ? eventPropertyExtractor(event) : event[propName];
            return (eventValue != null && field === eventValue);
        };
    }
}

const patternCompareFunctions = {

    logic: function (pattern) {
    },

    id: (pattern) => stringOrRegExpCompare(pattern, 'id'),

    name: (pattern) => stringOrRegExpCompare(pattern, 'name'),

    change: function (pattern) {
        switch (pattern.change) {
            case 'eq': return function (event) { return (event.newState.val === event.oldState.val); };
            case 'ne': return function (event) { return (event.newState.val !== event.oldState.val); };
            case 'gt': return function (event) { return (event.newState.val > event.oldState.val); };
            case 'ge': return function (event) { return (event.newState.val >= event.oldState.val); };
            case 'lt': return function (event) { return (event.newState.val < event.oldState.val); };
            case 'le': return function (event) { return (event.newState.val <= event.oldState.val); };
            default:   return function (/* event */) { return true; };
            // on any other logic, just signal about message
        }
    },
    ack: function (pattern) {
        if (pattern.ack === true || pattern.ack === 'true') {
            return function (event) {
                return (event.newState.ack === true || event.newState.ack === 'true');
            };
        } else {
            return function (event) {
                return (event.newState.ack === false || event.newState.ack === 'false');
            };
        }
    },
    oldAck: function (pattern) {
        if (pattern.oldAck === true || pattern.oldAck === 'true') {
            return function (event) {
                return (event.oldState.ack === true || event.oldState.ack === 'true');
            };
        } else {
            return function (event) {
                return (event.oldState.ack === false || event.oldState.ack === 'false');
            };
        }
    },
    val: function (pattern) {
        const pval = pattern.val;
        return function (event) {
            return pval === event.newState.val;
        };
    },
    valGt: function (pattern) {
        const pvalGt = pattern.valGt;
        return function (event) {
            return event.newState.val > pvalGt;
        };
    },
    valGe: function (pattern) {
        const pvalGe = pattern.valGe;
        return function (event) {
            return event.newState.val >= pvalGe;
        };
    },
    valLt: function (pattern) {
        const pvalLt = pattern.valLt;
        return function (event) {
            return event.newState.val < pvalLt;
        };
    },
    valLe: function (pattern) {
        const pvalLe = pattern.valLe;
        return function (event) {
            return event.newState.val <= pvalLe;
        };
    },
    valNe: function (pattern) {
        const pvalNe = pattern.valNe;
        return function (event) {
            return event.newState.val !== pvalNe;
        };
    },

    oldVal: function (pattern) {
        const poldVal = pattern.oldVal;
        return function (event) {
            return poldVal === event.oldState.val;
        };
    },
    oldValGt: function (pattern) {
        const poldValGt = pattern.oldValGt;
        return function (event) {
            return event.oldState.val > poldValGt;
        };
    },
    oldValGe: function (pattern) {
        const poldValGe = pattern.oldValGe;
        return function (event) {
            return event.oldState.val >= poldValGe;
        };
    },
    oldValLt: function (pattern) {
        const poldValLt = pattern.oldValLt;
        return function (event) {
            return event.oldState.val < poldValLt;
        };
    },
    oldValLe: function (pattern) {
        const poldValLe = pattern.oldValLe;
        return function (event) {
            return event.oldState.val <= poldValLe;
        };
    },
    oldValNe: function (pattern) {
        const poldValNe = pattern.oldValNe;
        return function (event) {
            return event.oldState.val !== poldValNe;
        };
    },

    ts: function (pattern) {
        const pts = pattern.ts;
        return function (event) {
            return pts === event.newState.ts;
        };
    },
    tsGt: function (pattern) {
        const ptsGt = pattern.tsGt;
        return function (event) {
            return event.newState.ts > ptsGt;
        };
    },
    tsGe: function (pattern) {
        const ptsGe = pattern.tsGe;
        return function (event) {
            return event.newState.ts >= ptsGe;
        };
    },
    tsLt: function (pattern) {
        const ptsLt = pattern.tsLt;
        return function (event) {
            return event.newState.ts < ptsLt;
        };
    },
    tsLe: function (pattern) {
        const ptsLe = pattern.tsLe;
        return function (event) {
            return event.newState.ts <= ptsLe;
        };
    },

    oldTs: function (pattern) {
        const poldTs = pattern.oldTs;
        return function (event) {
            return poldTs === event.oldState.ts;
        };
    },
    oldTsGt: function (pattern) {
        const poldTsGt = pattern.oldTsGt;
        return function (event) {
            return event.oldState.ts > poldTsGt;
        };
    },
    oldTsGe: function (pattern) {
        const poldTsGe = pattern.oldTsGe;
        return function (event) {
            return event.oldState.ts >= poldTsGe;
        };
    },
    oldTsLt: function (pattern) {
        const poldTsLt = pattern.oldTsLt;
        return function (event) {
            return event.oldState.ts < poldTsLt;
        };
    },
    oldTsLe: function (pattern) {
        const poldTsLe = pattern.oldTsLe;
        return function (event) {
            return event.oldState.ts <= poldTsLe;
        };
    },

    lc: function (pattern) {
        const plc = pattern.lc;
        return function (event) {
            return plc === event.newState.lc;
        };
    },
    lcGt: function (pattern) {
        const plcGt = pattern.lcGt;
        return function (event) {
            return event.newState.lc > plcGt;
        };
    },
    lcGe: function (pattern) {
        const plcGe = pattern.lcGe;
        return function (event) {
            return event.newState.lc >= plcGe;
        };
    },
    lcLt: function (pattern) {
        const plcLt = pattern.lcLt;
        return function (event) {
            return event.newState.lc < plcLt;
        };
    },
    lcLe: function (pattern) {
        const plcLe = pattern.lcLe;
        return function (event) {
            return event.newState.lc <= plcLe;
        };
    },

    oldLc: function (pattern) {
        const poldLc = pattern.oldLc;
        return function (event) {
            return poldLc === event.oldState.lc;
        };
    },
    oldLcGt: function (pattern) {
        const poldLcGt = pattern.oldLcGt;
        return function (event) {
            return event.oldState.lc > poldLcGt;
        };
    },
    oldLcGe: function (pattern) {
        const poldLcGe = pattern.oldLcGe;
        return function (event) {
            return event.oldState.lc >= poldLcGe;
        };
    },
    oldLcLt: function (pattern) {
        const poldLcLt = pattern.oldLcLt;
        return function (event) {
            return event.oldState.lc < poldLcLt;
        };
    },
    oldLcLe: function (pattern) {
        const poldLcLe = pattern.oldLcLe;
        return function (event) {
            return event.oldState.lc <= poldLcLe;
        };
    },

    from: (pattern) => stringOrRegExpCompare(pattern, 'from',
        (event) => event && event.newState && event.newState.from
    ),

    fromNe: (pattern) => !stringOrRegExpCompare(pattern, 'fromNe',
        (event) => event && event.newState && event.newState.from
    ),

    oldFrom: (pattern) => stringOrRegExpCompare(pattern, 'oldFrom',
        (event) => event && event.oldState && event.oldState.from
    ),

    oldFromNe: (pattern) => !stringOrRegExpCompare(pattern, 'oldFromNe',
        (event) => event && event.oldState && event.oldState.from
    ),

    channelId: (pattern) => stringOrRegExpCompare(pattern, 'channelId'),
    channelName: (pattern) => stringOrRegExpCompare(pattern, 'channelName'),
    deviceId: (pattern) => stringOrRegExpCompare(pattern, 'deviceId'),
    deviceName: (pattern) => stringOrRegExpCompare(pattern, 'deviceName'),

    enumId: (pattern) => {
        /** @type {RegExp | string | string[]} */
        const penumId = pattern.enumId;

        function ensureEnumIDsIsArray(enumIds) {
            if (!Array.isArray(enumIds)) {
                console.error(`enumIds is of type ${typeof enumIds} but should be an array: ${JSON.stringify(enumIds)}`);
                return false;
            }
            return true;
        }

        if (isRegExp(penumId)) {
            return function (event) {
                const enumIds = event.enumIds;
                if (enumIds == null || !ensureEnumIDsIsArray(enumIds)) return false;
                // Test if any enum name matches the regex:
                return enumIds.find(e => /** @type {RegExp} */ (penumId).test(e)) != null;
            };
        } else if (Array.isArray(penumId)) {
            return function (event) {
                const enumIds = event.enumIds;
                if (enumIds == null || !ensureEnumIDsIsArray(enumIds)) return false;
                // Test if the enum names of the event and the given array intersect
                return enumIds.find(e => penumId.indexOf(e) > -1) != null;
            };
        } else {
            return function (event) {
                const enumIds = event.enumIds;
                if (enumIds == null || !ensureEnumIDsIsArray(enumIds)) return false;
                return (enumIds && enumIds.indexOf(penumId) !== -1);
            };
        }
    },

    enumName: function (pattern) {
        /** @type {RegExp | string | string[]} */
        const penumName = pattern.enumName;

        function ensureEnumNamesIsArray(enumNames) {
            if (!Array.isArray(enumNames)) {
                console.error(`enumNames is of type ${typeof enumNames} but should be an array: ${JSON.stringify(enumNames)}`);
                return false;
            }
            return true;
        }

        if (isRegExp(penumName)) {
            return function (event) {
                const enumNames = event.enumNames;
                if (enumNames == null || !ensureEnumNamesIsArray(enumNames)) return false;
                // Test if any enum name matches the regex:
                return enumNames.find(e => /** @type {RegExp} */ (penumName).test(e)) != null;
            };
        } else if (Array.isArray(penumName)) {
            return function (event) {
                const enumNames = event.enumNames;
                if (enumNames == null || !ensureEnumNamesIsArray(enumNames)) return false;
                // Test if the enum names of the event and the given array intersect
                return enumNames.find(e => penumName.indexOf(e) > -1) != null;
            };
        } else {
            return function (event) {
                const enumNames = event.enumNames;
                if (enumNames == null || !ensureEnumNamesIsArray(enumNames)) return false;
                return (enumNames && enumNames.indexOf(penumName) !== -1);
            };
        }
    }
};


module.exports = patternCompareFunctions;
