/**
 * Passes a loopback fields filter to an object.
 *
 * @param {Object} fields The fields object or array
 * @return {Object} The fields as object
 */
function fieldsToObject(fields) {
    let fieldsObj = {};

    if (Array.isArray(fields)) {
        for (let field of fields)
            fieldsObj[field] = true;
    } else if (typeof fields == 'object') {
        for (let field in fields) {
            if (fields[field])
                fieldsObj[field] = true;
        }
    }

    return fieldsObj;
}

/**
 * Merges two loopback fields filters.
 *
 * @param {Object|Array} src The source fields
 * @param {Object|Array} dst The destination fields
 * @return {Array} The merged fields as an array
 */
function mergeFields(src, dst) {
    let fields = {};
    Object.assign(fields,
        fieldsToObject(src),
        fieldsToObject(dst)
    );
    return Object.keys(fields);
}

/**
 * Merges two loopback where filters.
 *
 * @param {Object|Array} src The source where
 * @param {Object|Array} dst The destination where
 * @return {Array} The merged wheres
 */
function mergeWhere(src, dst) {
    let and = [];
    if (src) and.push(src);
    if (dst) and.push(dst);
    return simplifyOperation(and, 'and');
}

/**
 * Merges two loopback filters returning the merged filter.
 *
 * @param {Object} src The source filter
 * @param {Object} dst The destination filter
 * @return {Object} The result filter
 */
function mergeFilters(src, dst) {
    let res = Object.assign({}, dst);

    if (!src)
        return res;

    if (src.fields)
        res.fields = mergeFields(src.fields, res.fields);
    if (src.where)
        res.where = mergeWhere(res.where, src.where);
    if (src.include)
        res.include = src.include;
    if (src.order)
        res.order = src.order;
    if (src.limit)
        res.limit = src.limit;
    if (src.offset)
        res.offset = src.offset;
    if (src.skip)
        res.skip = src.skip;

    return res;
}

function simplifyOperation(operation, operator) {
    switch (operation.length) {
    case 0:
        return undefined;
    case 1:
        return operation[0];
    default:
        return {[operator]: operation};
    }
}

function buildFilter(params, builderFunc) {
    let and = [];
    for (let param in params) {
        let value = params[param];
        if (value == null) continue;
        let expr = builderFunc(param, value);
        if (expr) and.push(expr);
    }
    return simplifyOperation(and, 'and');
}

module.exports = {
    fieldsToObject: fieldsToObject,
    mergeFields: mergeFields,
    mergeWhere: mergeWhere,
    mergeFilters: mergeFilters,
    buildFilter: buildFilter
};