import pointer from 'json-pointer';

function specificator(general: {[key: string] : any}, specific: {[key: string] : any}) {
    let out = {}
    _specificator(general || {}, specific || {}, out);
    return out;
}

// <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set#Implementing_basic_set_operations>
function difference(setA: Set<string>, setB: Set<string>) {
    let _difference = new Set(setA);
    for (let elem of setB.values()) {
        _difference.delete(elem);
    }
    return _difference;
}

function _specificator(general: {[key: string] : any}, specific: {[key: string] : any}, out = {}, ...path: string[]) {

    let propgeneral = new Set(Object.getOwnPropertyNames(general))
    let propspecific = new Set(Object.getOwnPropertyNames(specific))

    // <https://stackoverflow.com/questions/2342749/is-there-a-library-for-a-set-data-type-in-javascript#answer-31931246>
    let commonProperties = new Set([...propspecific].filter(i => propgeneral.has(i)));
    let generalProperties = difference(propgeneral, commonProperties);
    let specificProperties = difference(propspecific, commonProperties);

    generalProperties.forEach((generalOnlyProp) => {
        let jsonPath = pointer.compile([...path, generalOnlyProp]);
        pointer(out).set(jsonPath, general[generalOnlyProp]);
    });
    specificProperties.forEach((specificOnlyProp) => {
        let jsonPath = pointer.compile([...path, specificOnlyProp]);
        pointer(out).set(jsonPath, specific[specificOnlyProp]);
    });

    commonProperties.forEach((cProp) => {
        if (general[cProp] !== null && typeof general[cProp] === 'object' &&
            specific[cProp] !== null && typeof specific[cProp] === 'object') {
            _specificator(general[cProp], specific[cProp], out, ...[...path, cProp]);
        } else {
            if (specific[cProp] !== undefined) {
                let jsonPath = pointer.compile([...path, cProp]);
                pointer(out).set(jsonPath, specific[cProp]);
            } else {
                let jsonPath = pointer.compile([...path, cProp]);
                pointer(out).set(jsonPath, general[cProp]);
            }
        }
    })
}


export function rollingHeredity(...objects: {[key: string] : any}[]) {
    let specificatedObject = {}
    switch (objects.length) {
        case 0:
        break;
        case 1:
        specificatedObject = objects[0];
        break;
        case 2:
        specificatedObject = specificator(objects[0], objects[1]);
        break;
        default:
        let general = objects.shift();
        specificatedObject = specificator(<{[key: string] : any}>general, rollingHeredity(...objects));
    }
    return specificatedObject;
}


// export function forkedHeredity(array, ...objects) {
//     let common = rollingHeredity(...objects);
//     return array.map((item) => {
//         return specificator(common, item);
//     }) 
// }

export default rollingHeredity;