'use strict';

var util = require('./util');
var Stack = require('./Stack');
var __slice = Array.prototype.slice;

exports = module.exports = Runtime;

/**
# Runtime API
**/
function Runtime(props){
  if(!(this instanceof Runtime)){
    return new Runtime(props);
  }

  util.merge(this, props);
  this.Stack = this.constructor.Stack.createClass(
    util.merge({}, this.constructor.prototype, this)
  );
}

function reduceStack(self, calls, props){
  var proto = new self.Stack(props);
  function iterator(/* arguments */){
    proto.reduceStack.apply(self, arguments);
    return proto;
  }
  return calls.reduce(iterator, proto);
}

/**
  Missing docs
**/
Runtime.prototype.stack = function(/* sites..., props */) {
  var self = this;
  var calls = __slice.call(arguments);
  var props = util.type(calls[calls.length - 1]).plainObject && calls.pop();

  composer.stack = new this.Stack(props);
  composer.stack.tree = function stackTree(){
    return reduceStack(self, calls, props).tree();
  };

  function composer(host, next) {
    var stack = reduceStack(self, calls, props);
    stack.next = 0; stack.count = stack.length;

    if(host instanceof Stack && typeof next === 'function'){
      stack.host = host;
      stack.args = host.wait && host.args || host.args.concat();
      stack.onStackEnd = stack.onHandleError = next;
    } else {
      stack.args = __slice.call(arguments);
      if(typeof arguments[arguments.length - 1] === 'function'){
        stack.onStackEnd = stack.onHandleError = stack.args.pop();
      }
    }

    return tick(stack);
  }

  function tick(stack){
    var site = stack[stack.next];
    var args = site.fn.stack instanceof Stack
      && [stack, next] || stack.args.concat(next);

    stack.next = ++stack.next < stack.length && stack.next;

    util.asyncDone(function asyncStart(){
      next.wait = Boolean(stack.wait);
      stack.onHandle.call(self, site, stack);
      var ctxt = site.context || stack.context || site;
      var result = site.fn.apply(ctxt, args);
      if(!next.wait && stack.next){ tick(stack); }
      return result;
    }, next);

    var isDone = false;
    function next(error){
      if(error){
        stack.onHandleError.call(self, error, site, stack);
        return;
      }

      if(isDone){ return; } else if(next.wait && arguments.length){
        util.mapFrom(stack.args, arguments, 0);
      }

      isDone = true;
      stack.end = !(--stack.count);
      stack.onHandle.call(self, site, stack);

      if(stack.next){ tick(stack); } else if(stack.end && stack.onStackEnd){
        stack.onStackEnd.apply(self, [null].concat(stack.args));
      }
    }
  }

  return composer;
};

/**
 Missing docs
**/
Runtime.prototype.onHandle = function(){};
Runtime.prototype.onHandleError = function(error){
  throw error;
};

/**
 Missing docs
**/
Runtime.prototype.reduceStack = function(stack, site){
  if(typeof site === 'function'){
    stack.push({fn: site});
  } else if(site && typeof site.fn === 'function'){
    stack.push(site);
  }
};

/**
 Missing docs
**/
Runtime.Stack = Stack;

@@ -468,30 +468,52 @@
  self.Stack(
+util.merge(%7B%7D, self, 
 props)
+)
 ;%0A  function
@@ -1179,38 +1179,8 @@
 tack
- && typeof next === 'function'
 )%7B%0A 
@@ -1706,24 +1706,61 @@
 cat(next);%0A%0A
+    next.wait = Boolean(stack.wait);%0A
     stack.ne
@@ -1854,47 +1854,8 @@
 ()%7B%0A
-      next.wait = Boolean(stack.wait);%0A
     
@@ -1866,36 +1866,25 @@
 ack.onHandle
-.call(self, 
+(
 site, stack)
@@ -2141,32 +2141,39 @@
 error)%7B%0A        
+return 
 stack.onHandleEr
@@ -2175,28 +2175,17 @@
 dleError
-.call(self, 
+(
 error, s
@@ -2201,24 +2201,8 @@
 k);%0A
-        return;%0A
     
@@ -2412,20 +2412,9 @@
 ndle
-.call(self, 
+(
 site
@@ -2585,16 +2585,16 @@
  %7D%0A  %7D%0A%0A
+
   return
@@ -2612,143 +2612,8 @@
 %7D;%0A%0A
-/**%0A Missing docs%0A**/%0ARuntime.prototype.onHandle = function()%7B%7D;%0ARuntime.prototype.onHandleError = function(error)%7B%0A  throw error;%0A%7D;%0A%0A
 /**%0A

merge self with props on reduce Stack to avoid using call of each `on` handle