"use strict";

var expect = require('expect.js');
var Immutable = require('immutable');

var Trie = require('../src/trie.js');
var Patch = require('../src/patch.js');

var __ = Trie.__;
var _$ = Trie._$;

function checkPrettyPatch(p, expectedAdded, expectedRemoved) {
  expect(p.pretty()).to.equal(
    ('\n-' + expectedRemoved.join('\n-') +
     '\n+' + expectedAdded.join('\n+') + '\n'));
}

describe('basic patch compilation', function () {
  it('should print as expected', function () {
    checkPrettyPatch(Patch.assert([1, 2]),
    		     [' <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.assert(__),
    		     [' ★ {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub(__),
    		     [' observe<1> ★ {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2]),
    		     [' observe<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.pub('x'),
    		     [' advertise<1> "x" {true}'],
    		     [' ::: nothing']);
  });

  it('should work at nonzero metalevel', function () {
    checkPrettyPatch(Patch.assert([1, 2], 0),
    		     [' <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.assert([1, 2], 1),
    		     [' at-meta<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.assert([1, 2], 2),
    		     [' at-meta<1> at-meta<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);

    checkPrettyPatch(Patch.sub([1, 2], 0),
    		     [' observe<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2], 1),
    		     [' at-meta<1> observe<1> <2> 1 2 {true}',
		      ' observe<1> at-meta<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2], 2),
    		     [' at-meta<1> at-meta<1> observe<1> <2> 1 2 {true}',
		      '            observe<1> at-meta<1> <2> 1 2 {true}',
		      ' observe<1> at-meta<1> at-meta<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
  });
});

describe('patch sequencing', function () {
  it('should do the right thing in simple cases', function () {
    checkPrettyPatch(Patch.assert(__).andThen(Patch.retract(3)),
    		     [' ★ {true}',
		      ' 3 ::: nothing'],
    		     [' 3 {true}']);
    checkPrettyPatch(Patch.assert(3).andThen(Patch.retract(__)),
    		     [' ::: nothing'],
    		     [' ★ {true}']);
    checkPrettyPatch(Patch.assert(__).andThen(Patch.retract(__)),
    		     [' ::: nothing'],
    		     [' ★ {true}']);
    checkPrettyPatch(Patch.assert(3).andThen(Patch.retract(3)),
    		     [' ::: nothing'],
		     [' 3 {true}']);
    checkPrettyPatch(Patch.sub([1, __]).andThen(Patch.unsub([1, 2])),
    		     [' observe<1> <2> 1 ★ {true}',
		      '                  2 ::: nothing'],
    		     [' observe<1> <2> 1 2 {true}']);
    checkPrettyPatch(Patch.sub([__, 2]).andThen(Patch.unsub([1, 2])),
    		     [' observe<1> <2> ★ 2 {true}',
		      '                1 ::: nothing'],
    		     [' observe<1> <2> 1 2 {true}']);
    checkPrettyPatch(Patch.sub([__, __]).andThen(Patch.unsub([1, 2])),
    		     [' observe<1> <2> ★ ★ {true}',
		      '                1 ★ {true}',
		      '                  2 ::: nothing'],
    		     [' observe<1> <2> 1 2 {true}']);
  });

  it('works for longer chains of asserts and retracts', function () {
    var rawPatch =
	Patch.assert(1)
	.andThen(Patch.retract(2))
	.andThen(Patch.retract(3))
	.andThen(Patch.assert(4))
	.andThen(Patch.retract(99));
      checkPrettyPatch(rawPatch,
		       [' 1 {true}',
		        ' 4 {true}'],
		       [' 2 {true}',
		        ' 3 {true}',
		        ' 99 {true}']);

  });
});

describe('patch lifting', function () {
  it('should basically work', function () {
    checkPrettyPatch(Patch.assert([1, 2]).lift(),
    		     [' at-meta<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2]).lift(),
    		     [' at-meta<1> observe<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).lift(),
    		     [' at-meta<1> at-meta<1> <2> 1 2 {true}',
		      '            <2> 1 2 {true}'],
    		     [' ::: nothing']);
  });
});

describe('patch dropping', function () {
  it('should basically work', function () {
    checkPrettyPatch(Patch.assert([1, 2]).drop(),
    		     [' ::: nothing'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2]).drop(),
    		     [' ::: nothing'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.sub([1, 2], 1).drop(),
    		     [' observe<1> <2> 1 2 {true}'],
    		     [' ::: nothing']);
    checkPrettyPatch(Patch.assert([1, 2]).andThen(Patch.assert(Patch.atMeta([1, 2]))).drop(),
    		     [' <2> 1 2 {true}'],
    		     [' ::: nothing']);
  });
});
