'use strict';

// Porting tests from Apache CouchDB
// https://github.com/davisp/couchdb/blob/local_doc_revs/test/
// etap/060-kt-merging.t

var PouchDB = require('../../packages/node_modules/pouchdb-for-coverage');
var merge = PouchDB.utils.merge;
var winningRev = PouchDB.utils.winningRev;

describe('test.merge.js', function () {

  var simple;
  var two0;
  var two1;
  var newleaf;
  var withnewleaf;
  var newbranch;
  var newdeepbranch;
  var stemmededit;
  var stemmedconflicts;
  var newbranchleaf;
  var newbranchleafbranch;
  var stemmed2;
  var stemmed3;
  var partialrecover;

  /*
   * Our merge() function actually mutates the input object, because it's
   * more performant than deep cloning the object every time it's passed
   * into merge(). So in order for these tests to pass, we need to redefine
   * these objects every time.
   */
  beforeEach(function () {
    simple = {pos: 1, ids: ['1', {}, []]};
    two0 = {pos: 1, ids: ['1', {}, [['2_0', {}, []]]]};
    two1 = {pos: 1, ids: ['1', {}, [['2_1', {}, []]]]};
    newleaf = {pos: 2, ids: ['2_0', {}, [['3', {}, []]]]};
    withnewleaf = {pos: 1, ids: ['1', {}, [['2_0', {}, [['3', {}, []]]]]]};
    newbranch = {pos: 1, ids: ['1', {}, [['2_0', {}, []], ['2_1', {}, []]]]};
    newdeepbranch = {pos: 2, ids: ['2_0', {}, [['3_1', {}, []]]]};

    stemmededit = {pos: 3, ids: ['3', {}, []]};
    stemmedconflicts = [simple, stemmededit];

    newbranchleaf = {
      pos: 1,
      ids: ['1', {}, [['2_0', {}, [['3', {}, []]]], ['2_1', {}, []]]]
    };

    newbranchleafbranch = {
      pos: 1,
      ids: ['1', {}, [
        ['2_0', {}, [['3', {}, []], ['3_1', {}, []]]], ['2_1', {}, []]
      ]]
    };

    stemmed2 = [
      {pos: 1, ids: ['1', {}, [['2_1', {}, []]]]},
      {pos: 2, ids: ['2_0', {}, [['3', {}, []], ['3_1', {}, []]]]}
    ];

    stemmed3 = [
      {pos: 2, ids: ['2_1', {}, []]},
      {pos: 3, ids: ['3', {}, []]},
      {pos: 3, ids: ['3_1', {}, []]}
    ];

    partialrecover = [
      {pos: 1, ids: ['1', {}, [['2_0', {}, [['3', {}, []]]]]]},
      {pos: 2, ids: ['2_1', {}, []]},
      {pos: 3, ids: ['3_1', {}, []]}
    ];
  });

  it('Merging a path into an empty tree is the path', function () {
    merge([], simple, 10).should.deep.equal({
      tree: [simple],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Remerge path into path is reflexive', function () {
    merge([simple], simple, 10).should.deep.equal({
      tree: [simple],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('Merging a path with multiple entries is the path', function () {
    merge([], two0, 10).should.deep.equal({
      tree: [two0],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Merging a path with multiple entries is reflexive', function () {
    merge([two0], two0, 10).should.deep.equal({
      tree: [two0],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('Merging a subpath into a path results in the path', function () {
    merge([two0], simple, 10).should.deep.equal({
      tree: [two0],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('Merging a new leaf gives us a new leaf', function () {
    merge([two0], newleaf, 10).should.deep.equal({
      tree: [withnewleaf],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Merging a new branch returns a proper tree', function () {
    merge([two0], two1, 10).should.deep.equal({
      tree: [newbranch],
      stemmedRevs: [],
      conflicts: 'new_branch'
    });
  });

  it('Order of merging does not affect the resulting tree', function () {
    merge([two1], two0, 10).should.deep.equal({
      tree: [newbranch],
      stemmedRevs: [],
      conflicts: 'new_branch'
    });
  });

  it('Merging a new_leaf doesnt return new_branch when branches exist',
     function () {
    merge([newbranch], newleaf, 10).should.deep.equal({
      tree: [newbranchleaf],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Merging a deep branch with branches works', function () {
    merge([newbranchleaf], newdeepbranch, 10).should.deep.equal({
      tree: [newbranchleafbranch],
      stemmedRevs: [],
      conflicts: 'new_branch'
    });
  });

  it('New information reconnects steming induced conflicts', function () {
    merge(stemmedconflicts, withnewleaf, 10).should.deep.equal({
      tree: [withnewleaf],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Simple stemming works', function () {
    merge([two0], newleaf, 2).should.deep.equal({
      tree: [newleaf],
      stemmedRevs: ['1-1'],
      conflicts: 'new_leaf'
    });
  });

  it('Merge with stemming works correctly for branches', function () {
    merge([newbranchleafbranch], simple, 2).should.deep.equal({
      tree: stemmed2,
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('Merge with stemming to leaves works fine', function () {
    merge([newbranchleafbranch], simple, 1).should.deep.equal({
      tree: stemmed3,
      stemmedRevs: ['1-1', '2-2_0'],
      conflicts: 'internal_node'
    });
  });

  it('Merging unstemmed recovers as much as possible without losing info',
     function () {
    merge(stemmed3, withnewleaf, 10).should.deep.equal({
      tree: partialrecover,
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('winningRev returns the longest leaf', function () {
    var tree = [
      {"pos": 1, "ids": [
        "bfe70372c90ded1087239e5191984f76", {}, [
          ["44d71a718b90e4696c06a90e08912c8f", {}, []],
          ["56e657612d55ab1a402dcb281c874f2a", {}, [
            ["93c3db16462f656f7172ccabd3cf6cd6", {}, []]
          ]]
        ]
      ]}
    ];
    winningRev({rev_tree: tree})
      .should.equal("3-93c3db16462f656f7172ccabd3cf6cd6");
  });

  it('winningRev returns the longest leaf again', function () {
    // this one is from issue #293
    var tree = [
      {"pos": 1, "ids": [
        "203db1a1810a838895d561f67b224b5d", {}, [
          ["bf5e08a4f9fa6d33a53f4a00ae3ea399", {}, [
            ["28cd77a3ca30f79e1cfffcd6a41ca308", {}, []]
          ]]
        ]
      ]},
      {"pos": 1, "ids": [
        "c6d5cce35bcfbef90b20f140d723cbdb", {}, [
          ["1b8dfbb1267e213328920bae43f2f597", {}, []],
          ["59ed830b84b276ab776c3c51aaf93a16", {}, [
            ["64a9842c6aea50bf24660378e496e853", {}, []]
          ]]
        ]
      ]}
    ];
    winningRev({rev_tree: tree})
      .should.equal("3-64a9842c6aea50bf24660378e496e853");
  });

  // ///// These are tests from CouchDB's kt-merging.erl test suite

  var one = {pos: 1, ids: ['1', {}, []]};
  it('The empty tree is the identity for merge.', function () {
    merge([], one, 10).should.deep.equal({
      tree: [one],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  it('Merging is reflexive', function () {
    merge([one], one, 10).should.deep.equal({
      tree: [one],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var two = {pos: 1, ids: ['2', {}, []]};
  var twoSibs = [one, two];
  it('Merging a prefix of a tree with the tree yields the tree.', function () {
    merge(twoSibs, one, 10).should.deep.equal({
      tree: twoSibs,
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var three = {pos: 1, ids: ['3', {}, []]};
  var threeSibs = [one, two, three];
  it('Merging a third unrelated branch leads to a conflict.', function () {
    merge(twoSibs, three, 10).should.deep.equal({
      tree: threeSibs,
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var twoChild = {pos: 1, ids: ['1', {}, [
    ['1a', {}, [
      ['1aa', {}, []]
    ]]
  ]]};
  it('Merging two children is still reflexive.', function () {
    merge([twoChild], twoChild, 10).should.deep.equal({
      tree: [twoChild],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var twoChildSibs = {pos: 1, ids: ['1', {}, [
    ['1a', {}, []],
    ['1b', {}, []]
  ]]};
  it('Merging a tree to itself is itself.', function () {
    merge([twoChildSibs], twoChildSibs, 10).should.deep.equal({
      tree: [twoChildSibs],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var twoChildPlusSibs = {pos: 1, ids: ['1', {}, [
    ['1a', {}, [
      ['1aa', {}, []]
    ]],
    ['1b', {}, []]
  ]]};
  it('Merging tree of uneven length at node 2.', function () {
    merge([twoChild], twoChildSibs, 10).should.deep.equal({
      tree: [twoChildPlusSibs],
      stemmedRevs: [],
      conflicts: 'new_branch'
    });
  });

  var stemmed1b = {pos: 2, ids: ['1a', {}, []]};
  it('Merging a tree with a stem.', function () {
    merge([twoChildSibs], stemmed1b, 10).should.deep.equal({
      tree: [twoChildSibs],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var twoChildPlusSibs2 = {pos: 1, ids: ['1', {}, [
    ['1a', {}, []],
    ['1b', {}, [
      ['1bb', {}, []]
    ]]
  ]]};
  var stemmed1bb = {pos: 3, ids: ['1bb', {}, []]};
  it('Merging a stem at a deeper level.', function () {
    merge([twoChildPlusSibs2], stemmed1bb, 10).should.deep.equal({
      tree: [twoChildPlusSibs2],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var stemmedTwoChildSibs2 = [
    {pos: 2, ids: ['1a', {}, []]},
    {pos: 2, ids: ['1b', {}, [['1bb', {}, []]]]}
  ];
  it('Merging a stem at a deeper level against paths at deeper levels.',
     function () {
    merge(stemmedTwoChildSibs2, stemmed1bb, 10).should.deep.equal({
      tree: stemmedTwoChildSibs2,
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var stemmed1aa = {pos: 3, ids: ['1aa', {}, []]};
  it("Merging a single tree with a deeper stem.", function () {
    merge([twoChild], stemmed1aa, 10).should.deep.equal({
      tree: [twoChild],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var stemmed1a = {pos: 2, ids: ['1a', {}, [['1aa', {}, []]]]};
  it('Merging a larger stem.', function () {
    merge([twoChild], stemmed1a, 10).should.deep.equal({
      tree: [twoChild],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('More merging.', function () {
    merge([stemmed1a], stemmed1aa, 10).should.deep.equal({
      tree: [stemmed1a],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  var oneChild = {pos: 1, ids: ['1', {}, [['1a', {}, []]]]};
  it('Merging should create conflicts.', function () {
    merge([oneChild], stemmed1aa, 10).should.deep.equal({
      tree: [oneChild, stemmed1aa],
      stemmedRevs: [],
      conflicts: 'internal_node'
    });
  });

  it('Merging should have no conflicts.', function () {
    merge([oneChild, stemmed1aa], twoChild, 10).should.deep.equal({
      tree: [twoChild],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

  var foo = {pos: 1, ids: ['foo', {}, [
    ['foo2', {}, []],
    ['foo3', {}, []]
  ]]};
  var bar = {pos: 1, ids: ['foo', {}, [
    ['foo3', {}, [
      ['foo4', {}, []]
    ]]
  ]]};
  var fooBar = {pos: 1, ids: ['foo', {}, [
    ['foo2', {}, []],
    ['foo3', {}, [
      ['foo4', {}, []]
    ]]
  ]]};

  it('Merging trees with conflicts ought to behave.', function () {
    merge([foo], bar, 10).should.deep.equal({
      tree: [fooBar],
      stemmedRevs: [],
      conflicts: 'new_leaf'
    });
  });

});
