"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeDeltas = exports.differentiateDeltas = exports.createMergeSimilarDeltas = exports.mergeSimilarDeltas = exports.applyDelta = exports.createDelta = void 0;
/**
 * Create delta to transform from src into dest
 *
 * @param src  - the source data set
 * @param dest - the destination data set
 *
 * @return the delta
 */
function createDelta(src, dest) {
    var delta = {};
    // Loop through all keys
    var key_set = new Set(Object.keys(src).concat(Object.keys(dest)));
    key_set.forEach(function (key) {
        var src_data = src[key];
        var dest_data = dest[key];
        // If source does not contain the key, use entire dest data
        if (!src_data || !src_data.length) {
            delta[key] = dest_data;
            return;
        }
        // If the key no longer exists in dest then nullify this key
        if (!dest_data || !dest_data.length) {
            delta[key] = null;
            return;
        }
        // If neither condition above is true then create the key iteratively
        var delta_key = _createDeltaKey(src_data, dest_data);
        if (delta_key.changed) {
            delta[key] = delta_key.data;
        }
    });
    return delta;
}
exports.createDelta = createDelta;
/**
 * Apply a delta to a bucket
 *
 * @param bucket - The bucket data
 * @param delta  - The delta to apply
 *
 * @return the bucket with the delta applied
 */
function applyDelta(bucket, delta) {
    if (bucket === void 0) { bucket = {}; }
    var appliedDelta = {};
    if (!delta) {
        return bucket;
    }
    // Loop through all keys
    var key_set = new Set(Object.keys(bucket).concat(Object.keys(delta)));
    key_set.forEach(function (key) {
        var bucket_data = bucket[key];
        var delta_data = delta[key];
        // If bucket does not contain the key, use entire delta data
        if (!bucket_data || !bucket_data.length) {
            appliedDelta[key] = delta_data;
            return;
        }
        // If delta does not contain the key then retain bucket data
        if (delta_data === null) {
            return;
        }
        // If delta does not contain the key then retain bucket data
        if (delta_data === undefined) {
            appliedDelta[key] = bucket_data;
            return;
        }
        // If neither condition above is true then create the key iteratively
        appliedDelta[key] = _applyDeltaKey(bucket_data, delta_data);
    });
    return appliedDelta;
}
exports.applyDelta = applyDelta;
/**
 * Merge an array of deltas
 *
 * We will merge delta data by applying deltas on top of each other. The type
 * should not change so we just use the most recent type. We will take the most
 * recent timestamp and step_id and treat the presence of a concluding save in
 * one delta as a concluding save for all deltas
 *
 * @param deltas - An array of deltas to merge
 *
 * @return A single merged delta or null if an empty array was supplied
 */
function mergeSimilarDeltas(deltas, apply) {
    var merge_type = 'data', merge_timestamp = 0, merge_concluding_save = false, merge_step_id = 0, merge_data = {};
    if (deltas.length === 0) {
        return null;
    }
    deltas.forEach(function (delta) {
        merge_timestamp = Math.max(merge_timestamp, delta.timestamp);
        merge_step_id = Math.max(merge_step_id, delta.step_id);
        merge_type = delta.type;
        merge_concluding_save = merge_concluding_save || delta.concluding_save;
        merge_data = apply(merge_data, delta.data);
    });
    return {
        type: merge_type,
        timestamp: merge_timestamp,
        concluding_save: merge_concluding_save,
        step_id: merge_step_id,
        data: merge_data,
    };
}
exports.mergeSimilarDeltas = mergeSimilarDeltas;
/**
 * Returns the mergeSimilarDeltas function with the apply argument already
 * satisfied
 *
 * @param apply - A function to apply deltas
 */
exports.createMergeSimilarDeltas = function (apply) { return function (deltas) {
    return mergeSimilarDeltas(deltas, apply);
}; };
/**
 * Merge deltas by type
 *
 * We will iterate through the deltas and merge only where appropriate.
 *
 * As we loop through we will add deltas which are available to be merged into
 * an array. When we encounter a delta which is not eligible for merging we
 * merge everything in the pending array and then start a new pernding array
 * with the ineligible delta. Because of this pending array, we will need to
 * finish this process by merging all pending deltas
 *
 * @param deltas - An array of deltas to merge
 *
 * @return An array of deltas which have been merged
 */
function differentiateDeltas(deltas, merge) {
    var merged_deltas = [];
    var pending_merge = [];
    var pending_type = null;
    var pending_step = 0;
    deltas.forEach(function (delta) {
        // If the delta is the same step and type then add
        // to pending and proceed with the next delta
        if (delta.type === pending_type && delta.step_id === pending_step) {
            pending_type = delta.type;
            pending_step = delta.step_id;
            pending_merge.push(delta);
            return;
        }
        var merged = merge(pending_merge);
        if (merged !== null) {
            merged_deltas.push(merged);
        }
        pending_merge = [delta];
        pending_type = delta.type;
        pending_step = delta.step_id;
    });
    // Merge any pending deltas
    return merged_deltas.concat(merge(pending_merge) || []);
}
exports.differentiateDeltas = differentiateDeltas;
/**
 *
 * @param delta - An array of deltas to merge
 */
function mergeDeltas(deltas) {
    return differentiateDeltas(deltas, exports.createMergeSimilarDeltas(applyDelta));
}
exports.mergeDeltas = mergeDeltas;
/**
 * Apply the delta key iteratively
 *
 * @param bucket - The bucket data array
 * @param delta  - The delta data array
 *
 * @return the applied delta
 */
function _applyDeltaKey(bucket, delta) {
    var data = [];
    var max_size = Math.max(delta.length, bucket.length);
    for (var i = 0; i < max_size; i++) {
        var delta_datum = delta[i];
        var bucket_datum = bucket[i];
        if (delta_datum === null) {
            break;
        }
        else if (delta_datum === undefined) {
            data[i] = bucket_datum;
        }
        else if (_deepEqual(delta_datum, bucket_datum)) {
            data[i] = bucket_datum;
        }
        else {
            data[i] = delta_datum;
        }
    }
    return data;
}
/**
 * Build the delta key iteratively
 *
 * @param src  - the source data array
 * @param dest - the destination data array
 *
 * @return an object with an changed flag and a data array
 */
function _createDeltaKey(src, dest) {
    var data = [];
    var max_size = Math.max(dest.length, src.length);
    var changed = false;
    for (var i = 0; i < max_size; i++) {
        var dest_datum = dest[i];
        var src_datum = src[i];
        // terminate the key if we don't have a dest value
        if (dest_datum === undefined) {
            changed = true;
            data[i] = null;
            break;
        }
        else if (_deepEqual(dest_datum, src_datum)) {
            data[i] = undefined;
        }
        else {
            changed = true;
            data[i] = dest_datum;
        }
    }
    return {
        changed: changed,
        data: data,
    };
}
/**
 * Compare two arrays by index
 *
 * @param a - the first array to compare
 * @param b - the second array to compare
 */
function _deepEqual(a, b) {
    if (Array.isArray(a)) {
        if (!Array.isArray(b) || a.length !== b.length) {
            return false;
        }
        return a.every(function (item, i) { return _deepEqual(item, b[i]); });
    }
    return a === b;
}
//# sourceMappingURL=data:application/json;base64, 