<commit_before>'use strict';

const deckDB = require('../database/deckDatabase');
const slideDB = require('../database/slideDatabase');
const boom = require('boom');
const _ = require('lodash');
const util = require('../lib/util');

let self = module.exports = {

    getDeckChangeLog: function(request, reply) {
        let deckId = request.params.id;

        deckDB.getChangeLog(deckId).then((changeLog) => {
            if (!changeLog) return boom.notFound();

            return prepareChangeLog(changeLog, request.query.simplify);

        }).then(reply).catch((error) => {
            request.log('error', error);
            reply(boom.badImplementation());
        });
    },

    getSlideChangeLog: function(request, reply) {
        let slideId = request.params.id;
        let rootId = request.query.root;

        slideDB.getChangeLog(slideId, rootId).then((changeLog) => {
            if (!changeLog) return boom.notFound();

            return prepareChangeLog(changeLog, request.query.simplify);

        }).then(reply).catch((error) => {
            request.log('error', error);
            reply(boom.badImplementation());
        });
    },

};

const mergeMoves = true;
const mergeParents = true;

function prepareChangeLog(changeLog, simplifyOutput) {
    if (mergeParents) {
        changeLog = mergeChangeParents(changeLog);
    }

    // we add the revise/revert subops
    changeLog.forEach((cur) => {
        if (cur.op === 'replace') {
            let ref = cur.value.ref;
            if (cur.value.kind === 'deck') {
                if (ref.originRevision && ref.originRevision < ref.revision - 1) {
                    // we have a revert!
                    cur.reverted = {
                        from: cur.oldValue.ref.revision,
                        to: ref.originRevision,
                    };
                    cur.action = 'revert';
                } else {
                    cur.action = 'revise';
                }
            }

            if (cur.value.kind === 'slide') {
                if (ref.revision < cur.oldValue.ref.revision || cur.action === 'revert') {
                    // we have a revert!
                    cur.reverted = {
                        from: cur.oldValue.ref.revision,
                        to: ref.revision,
                    };
                    cur.action = 'revert';
                } else {
                    // it's a slide edit action
                    cur.action = 'edit';
                }
            }
        }
    });

    if (mergeMoves) {
        let hold;
        changeLog = changeLog.reduce((acc, cur) => {
            if (hold) {
                // TODO check timestamps as well
                if (cur.op === 'add' && _.isEqual(hold.value, cur.value) && hold.user === cur.user) {
                    // we have a move, so merge and push
                    acc.push({
                        op: 'move',
                        from: hold.path,
                        path: cur.path,
                        value: cur.value,

                        timestamp: cur.timestamp,
                        user: cur.user,
                    });

                } else {
                    // just push both
                    acc.push(hold, cur);
                }

                // in any case, unset 'hold'
                hold = undefined;
            } else if (cur.op === 'remove') {
                // just hold it, don't push it yet
                hold = cur;
            } else {
                // push it forward
                acc.push(cur);
            }
            return acc;

        }, []);

        // also add hold if leftover
        if (hold) changeLog.push(hold);
    }

    // fill `action` were missing, and detect 'rename' actions<commit_msg>Fix /deck/:id/changes not showing moves after new implementation<commit_after>'use strict';

const deckDB = require('../database/deckDatabase');
const slideDB = require('../database/slideDatabase');
const boom = require('boom');
const _ = require('lodash');
const util = require('../lib/util');

let self = module.exports = {

    getDeckChangeLog: function(request, reply) {
        let deckId = request.params.id;

        deckDB.getChangeLog(deckId).then((changeLog) => {
            if (!changeLog) return boom.notFound();

            return prepareChangeLog(changeLog, request.query.simplify);

        }).then(reply).catch((error) => {
            request.log('error', error);
            reply(boom.badImplementation());
        });
    },

    getSlideChangeLog: function(request, reply) {
        let slideId = request.params.id;
        let rootId = request.query.root;

        slideDB.getChangeLog(slideId, rootId).then((changeLog) => {
            if (!changeLog) return boom.notFound();

            return prepareChangeLog(changeLog, request.query.simplify);

        }).then(reply).catch((error) => {
            request.log('error', error);
            reply(boom.badImplementation());
        });
    },

};

const mergeMoves = true;
const mergeParents = true;

function prepareChangeLog(changeLog, simplifyOutput) {
    if (mergeParents) {
        changeLog = mergeChangeParents(changeLog);
    }

    // we add the revise/revert subops
    changeLog.forEach((cur) => {
        if (cur.op === 'replace') {
            let ref = cur.value.ref;
            if (cur.value.kind === 'deck') {
                if (ref.originRevision && ref.originRevision < ref.revision - 1) {
                    // we have a revert!
                    cur.reverted = {
                        from: cur.oldValue.ref.revision,
                        to: ref.originRevision,
                    };
                    cur.action = 'revert';
                } else {
                    cur.action = 'revise';
                }
            }

            if (cur.value.kind === 'slide') {
                if (ref.revision < cur.oldValue.ref.revision || cur.action === 'revert') {
                    // we have a revert!
                    cur.reverted = {
                        from: cur.oldValue.ref.revision,
                        to: ref.revision,
                    };
                    cur.action = 'revert';
                } else {
                    // it's a slide edit action
                    cur.action = 'edit';
                }
            }
        }
    });

    if (mergeMoves) {
        let hold;
        changeLog = changeLog.reduce((acc, cur) => {
            if (hold) {
                // TODO check timestamps as well
                if (['add', 'remove'].includes(cur.op)) {
                    // in order to qualify for a move:
                    if (cur.op !== hold.op // cur.op is distict from hold.op (add/remove or remove/add sequence)
                        && _.isEqual(hold.value, cur.value) // both ops were for the same value (deck tree node)
                        && hold.user === cur.user // both ops were by the same user
                        && cur._id.getTimestamp() - hold._id.getTimestamp() === 0 // timestamps (second-based accuracy) should be the same 
                    ) {
                        // we have a move, so merge and push
                        acc.push({
                            op: 'move',
                            from: hold.path,
                            path: cur.path,
                            value: cur.value,

                            timestamp: cur.timestamp,
                            user: cur.user,
                        });

                        // and unset 'hold'
                        hold = undefined;                        
                    } else {
                        // push hold, set cur to hold
                        acc.push(hold);
                        hold = cur;
                    }

                } else {
                    // just push both
                    acc.push(hold, cur);

                    // and unset 'hold'
                    hold = undefined;                        
                }

            } else if (['add', 'remove'].includes(cur.op)) {
                // just hold it, don't push it yet
                hold = cur;
            } else {
                // push it forward
                acc.push(cur);
            }
            return acc;

        }, []);

        // also add hold if leftover
        if (hold) changeLog.push(hold);
    }

    // fill `action` were missing, and detect 'rename' actions
{'fraction_non_alphanumeric': 0.11987717019015, 'fraction_numerical': 0.00035431675918270935, 'mean_word_length': 1.194919647485744, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1768986', 'n_tokens_mistral': 2264, 'n_tokens_neox': 2108, 'n_words': 513}