// external modules
var LZString = require('lz-string');
var DiffMatchPatch = require('diff-match-patch');
var dmp = new DiffMatchPatch();

// core
var config = require("../config.js");
var logger = require("../logger.js");

process.on('message', function(data) {
    if (!data || !data.msg || !data.cacheKey) {
        return logger.error('dmp worker error: not enough data');
    }
    switch (data.msg) {
        case 'create patch':
            if (!data.hasOwnProperty('lastDoc') || !data.hasOwnProperty('currDoc')) {
                return logger.error('dmp worker error: not enough data on create patch');
            }
            try {
                var patch = createPatch(data.lastDoc, data.currDoc);
                process.send({
                    msg: 'check',
                    result: patch,
                    cacheKey: data.cacheKey
                });
            } catch (err) {
                logger.error('dmp worker error', err);
                process.send({
                    msg: 'error',
                    error: err,
                    cacheKey: data.cacheKey
                });
            }
            break;
        case 'get revision':
            if (!data.hasOwnProperty('revisions') || !data.hasOwnProperty('count')) {
                return logger.error('dmp worker error: not enough data on get revision');
            }
            try {
                var result = getRevision(data.revisions, data.count);
                process.send({
                    msg: 'check',
                    result: result,
                    cacheKey: data.cacheKey
                });
            } catch (err) {
                logger.error('dmp worker error', err);
                process.send({
                    msg: 'error',
                    error: err,
                    cacheKey: data.cacheKey
                });
            }
            break;
    }
});

function createPatch(lastDoc, currDoc) {
    var ms_start = (new Date()).getTime();
    var diff = dmp.diff_main(lastDoc, currDoc);
    dmp.diff_cleanupSemantic(diff);
    var patch = dmp.patch_make(lastDoc, diff);
    patch = dmp.patch_toText(patch);
    var ms_end = (new Date()).getTime();
    if (config.debug) {
        logger.info(patch);
        logger.info((ms_end - ms_start) + 'ms');
    }
    return patch;
}

function getRevision(revisions, count) {
    var ms_start = (new Date()).getTime();
    var startContent = null;
    var lastPatch = [];
    var applyPatches = [];
    var authorship = [];
    if (count <= Math.round(revisions.length / 2)) {
        // start from top to target
        for (var i = 0; i < count; i++) {
            var revision = revisions[i];
            if (i == 0) {
                startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
            }
            if (i != count - 1) {
                var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
                applyPatches = applyPatches.concat(patch);
            }
            lastPatch = revision.patch;
            authorship = revision.authorship;
        }
        // swap DIFF_INSERT and DIFF_DELETE to achieve unpatching
        for (var i = 0, l = applyPatches.length; i < l; i++) {
            for (var j = 0, m = applyPatches[i].diffs.length; j < m; j++) {
                var diff = applyPatches[i].diffs[j];
                if (diff[0] == DiffMatchPatch.DIFF_INSERT)
                    diff[0] = DiffMatchPatch.DIFF_DELETE;
                else if (diff[0] == DiffMatchPatch.DIFF_DELETE)
                    diff[0] = DiffMatchPatch.DIFF_INSERT;
            }
        }
    } else {
        // start from bottom to target
        var l = revisions.length - 1;
        for (var i = l; i >= count - 1; i--) {
            var revision = revisions[i];
            if (i == l) {
                startContent = LZString.decompressFromBase64(revision.lastContent);
                authorship = revision.authorship;
            }
            if (revision.patch) {
                var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
                applyPatches = applyPatches.concat(patch);
            }
            lastPatch = revision.patch;
            authorship = revision.authorship;
        }
    }
    try {
        var finalContent = dmp.patch_apply(applyPatches, startContent)[0];
    } catch (err) {
        throw new Error(err);
    }
    var data = {
        content: finalContent,
        patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
        authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
    };
    var ms_end = (new Date()).getTime();
    if (config.debug) {
        logger.info((ms_end - ms_start) + 'ms');
    }
    return data;
}

// log uncaught exception
process.on('uncaughtException', function (err) {
    logger.error('An uncaught exception has occured.');
    logger.error(err);
    logger.error('Process will exit now.');
    process.exit(1);
});