/* Copyright © 2015-2016 David Valdman */
define(function(require, exports, module){
var EventHandler = require('../events/EventHandler');
var EventMapper = require('../events/EventMapper');
var SimpleStream = require('../streams/SimpleStream');
var preTickQueue = require('../core/queues/preTickQueue');
var postTickQueue = require('../core/queues/postTickQueue');
var dirtyQueue = require('../core/queues/dirtyQueue');
var State = require('../core/SUE');
var EVENTS = {
START : 'start',
UPDATE : 'update',
END : 'end',
RESIZE : 'resize'
};
/**
* Stream listens to `resize`, `start`, `update` and `end` events and
* emits `start`, `update` and `end` events. `Resize` events get
* unified with `start`, `update`, and `end` events depending on
* when they are fired within Samsara's engine cycle.
*
* If listening to multiple sources, Stream emits a single event per
* Engine cycle.
*
* @example
*
* var position = new Transitionable([0,0]);
* var size = new EventEmitter();
*
* var translationStream = Stream.lift(function(position, size){
* var translation = [
* position[0] + size[0],
* position[1] + size[1]
* ];
*
* return Transform.translate(translation);
* }, [positionStream, sizeStream]);
*
* translationStream.on('start', function(transform){
* console.log(transform);
* });
*
* translationStream.on('update', function(transform){
* console.log(transform);
* });
*
* translationStream.on('end', function(transform){
* console.log(transform);
* });
*
* position.set([100, 50], {duration : 500});
* size.emit('resize', [100,100]);
*
* @class Stream
* @extends Streams.SimpleStream
* @namespace Streams
* @param [options] {Object} Options
* @param [options.start] {Function} Custom logic to map the `start` event
* @param [options.update] {Function} Custom logic to map the `update` event
* @param [options.end] {Function} Custom logic to map the `end` event
* @constructor
*/
function Stream(options){
this._eventInput = new EventHandler();
this._eventOutput = new EventHandler();
EventHandler.setInputHandler(this, this._eventInput);
EventHandler.setOutputHandler(this, this._eventOutput);
var counter = 0;
var isUpdating = false;
var dirtyStart = false;
var dirtyUpdate = false;
var dirtyEnd = false;
function start(data){
var payload = options && options.start ? options.start(data) : data;
if (payload !== false) this.emit(EVENTS.START, payload);
dirtyStart = false;
}
function update(data){
var payload = options && options.update ? options.update(data) : data;
if (payload !== false) this.emit(EVENTS.UPDATE, payload);
dirtyUpdate = false;
}
function end(data){
var payload = options && options.end ? options.end(data) : data;
if (payload !== false) this.emit(EVENTS.END, payload);
dirtyEnd = false;
}
this._eventInput.on(EVENTS.START, function(data){
counter++;
if (dirtyStart || isUpdating) return false;
dirtyStart = true;
preTickQueue.push(start.bind(this, data));
}.bind(this));
this._eventInput.on(EVENTS.UPDATE, function(data){
isUpdating = true;
if (dirtyUpdate) return false;
dirtyUpdate = true;
postTickQueue.push(update.bind(this, data));
}.bind(this));
this._eventInput.on(EVENTS.END, function(data){
counter--;
if (isUpdating && counter > 0){
update.call(this, data);
return false;
}
isUpdating = false;
if (dirtyEnd) return;
dirtyEnd = true;
dirtyQueue.push(end.bind(this, data));
}.bind(this));
this._eventInput.on(EVENTS.RESIZE, function(data){
switch (State.get()){
case State.STATES.START:
this.trigger(EVENTS.START, data);
break;
case State.STATES.UPDATE:
this.trigger(EVENTS.UPDATE, data);
break;
case State.STATES.END:
this.trigger(EVENTS.END, data);
break;
}
}.bind(this));
}
Stream.prototype = Object.create(SimpleStream.prototype);
Stream.prototype.constructor = Stream;
/**
* Lift is like map, except it maps several event sources,
* not only one.
*
* @example
*
* var liftedStream = Stream.lift(function(payload1, payload2){
* return payload1 + payload2;
* }, [stream2, stream2]);
*
* liftedStream.on('name'), function(data){
* // data = 3;
* });
*
* stream2.emit('name', 1);
* stream2.emit('name', 2);
*
* @method lift
* @static
* @param map {Function} Function to map stream payloads
* @param streams {Array|Object} Stream sources
*/
Stream.lift = SimpleStream.lift;
/**
* Batches events for provided object of streams in
* {key : stream} pairs. Emits one event per Engine cycle.
*
* @method merge
* @static
* @param streams {Object} Dictionary of `resize` streams
*/
Stream.merge = function(streamObj){
var mergedStream = new Stream();
var mergedData = (streamObj instanceof Array) ? [] : {};
mergedStream.addStream = function(key, stream){
var mapper = (function(key){
return new EventMapper(function(data){
mergedData[key] = data;
return mergedData;
});
})(key);
mergedStream.subscribe(mapper).subscribe(stream);
};
for (var key in streamObj){
var stream = streamObj[key];
mergedStream.addStream(key, stream);
}
return mergedStream;
};
module.exports = Stream;
});