"use strict";

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

var Syndicate = require('../src/main.js');
var Dataspace = Syndicate.Dataspace;
var Patch = Syndicate.Patch;

var __ = Syndicate.__;
var _$ = Syndicate._$;

function configurationTrace(bootConfiguration) {
  var eventLog = [];
  function trace(item) {
    eventLog.push(item);
  }

  var G = new Syndicate.Ground(function () {
    bootConfiguration(trace);
  });

  while (G.step()) {
    // do nothing until G becomes inert
  }

  return eventLog;
}

function traceEvent(trace) {
  return function(item) {
    trace((item.type === "stateChange") ? item.patch.pretty() : item);
  }
}

function attributedTraceEvent(attribution, trace) {
  return function(item) {
    trace([attribution, (item.type === "stateChange") ? item.patch.pretty() : item]);
  }
}

function checkTrace(bootConfiguration, expected) {
  expect(configurationTrace(bootConfiguration)).to.eql(expected);
}

describe("configurationTrace", function() {
  describe("with an inert configuration", function () {
    it("should yield an empty trace", function () {
      checkTrace(function (trace) {}, []);
    });
  });

  describe("with a single trace in an inert configuration", function () {
    it("should yield that trace", function () {
      checkTrace(function (trace) { trace(1) }, [1]);
    });
  });

  describe("with some traced communication", function () {
    it("should yield an appropriate trace", function () {
      checkTrace(function (trace) {
	Dataspace.spawn({
	  boot: function () { return Syndicate.sub(__); },
	  handleEvent: traceEvent(trace)
	});
	Dataspace.send(123);
	Dataspace.send(234);
      }, ['\n- ::: nothing'+
	  '\n+ observe<1> ★ {[0]}\n',
	  Syndicate.message(123),
	  Syndicate.message(234)]);
    });
  });
});

describe("nonempty initial routes", function () {
  it("should be immediately signalled to the process", function () {
    // Specifically, no Syndicate.updateRoutes([]) first.
    checkTrace(function (trace) {
      Dataspace.spawn({
	boot: function () { return Patch.assert(["A", __]); },
	handleEvent: function (e) {}
      });
      Dataspace.spawn({
	boot: function () { return Patch.sub(["A", __]); },
	handleEvent: traceEvent(trace)
      });
    }, ['\n- ::: nothing'+
	'\n+ <2> "A" ★ {[0]}\n']);
  });
});

describe("nested actor with an echoey protocol", function () {
  it("shouldn't see an echoed assertion", function () {
    checkTrace(function (trace) {
      Dataspace.spawn(new Dataspace(function () {
	Dataspace.spawn({
	  boot: function () {
	    Dataspace.stateChange(Patch.retract("X", 1)); // happens after subs on next line!
	    return Patch.sub("X", 1).andThen(Patch.assert("X", 1));
	  },
	  handleEvent: traceEvent(trace)
	});
      }));
    }, ['\n- ::: nothing'+
	'\n+ at-meta<1> "X" {["meta"]}\n',
	'\n- at-meta<1> "X" {["meta"]}'+
	'\n+ ::: nothing\n']);
  })
  it("shouldn't see an echoed message", function () {
    checkTrace(function (trace) {
      Dataspace.spawn(new Dataspace(function () {
	Dataspace.spawn({
	  boot: function () {
	    Dataspace.send("X", 1); // happens after subs on next line!
            return Patch.sub("X", 1);
	  },
	  handleEvent: traceEvent(trace)
	});
      }));
    }, [Syndicate.message(Patch.atMeta("X"))]);
  });
  it("shouldn't see an echoed assertion", function () {
  });
});

// describe("actor with nonempty initial routes", function () {
//   it("shouldn't see initial empty conversational context", function () {
//     checkTrace(function (trace) {
//       Dataspace.spawn({
// 	boot: function () { return [pub(["A", __])] },
// 	handleEvent: function (e) {
// 	  Dataspace.spawn(new Actor(function () {
// 	    Actor.observeAdvertisers(
// 	      function () { return ["A", __] },
// 	      { presence: "isPresent" },
// 	      function () {
// 		trace(["isPresent", this.isPresent]);
// 	      });
// 	  }));
// 	}
//       });
//     }, [["isPresent", true]]);
//   });
// });

describe("broadcast message delivery", function () {
  it("should work", function () {
    checkTrace(function (trace) {
      Dataspace.spawn({
        boot: function () { return Patch.sub(["Alice", __]); },
        handleEvent: attributedTraceEvent("Alice", trace)
      });
      Dataspace.spawn({
        boot: function () { return Patch.sub(["Bob", __]); },
        handleEvent: attributedTraceEvent("Bob", trace)
      });
      Dataspace.spawn({
        boot: function () {
          Dataspace.send(["Alice", "For Alice's eyes only"]);
          Dataspace.send(["Bob", "Dear Bob, how are you? Kind regards, etc."]);
          Dataspace.send([__, "Important announcement!"]);
        }
      })
    }, [["Alice", Syndicate.message(["Alice", "For Alice's eyes only"])],
        ["Bob", Syndicate.message(["Bob", "Dear Bob, how are you? Kind regards, etc."])],
        ["Alice", Syndicate.message([__, "Important announcement!"])],
        ["Bob", Syndicate.message([__, "Important announcement!"])]]);
  });
});
