var Task = function(body) {
    this.__context = {};
    this.__context.__args = [];
    this.__randezvousers = [];
    this.__waitingEntries = {};
    this.__queuedCalls = [];
    this.__queuedCallsAt = {};
    this.__select = null;
    this.__waitingCalls = [];

    if (body instanceof Task.Type) {
        this.__type = body;
    } else {
        this.__type = new Task.Type();
        this.__type.body.apply(this.__type, arguments);
    }

    for (var i = 0; i < this.__type.__entries.length; i++) {
        var entry = this.__type.__entries[i];
        this.__queuedCallsAt[entry] = [];
        this[entry] = this.__entryMethod(entry);
        this[entry].task = this;
    }
};

Task.define = function() {
    var global = (function() {return this;}).apply(null);
    for (var name in Task) if (Task[name].prototype instanceof Task.Structure) {
        global[name.toLowerCase()] =
            (function(name) {
                 return function() {
                     var o = new Task[name]();
                     o.__body.apply(o, arguments);
                     return o;};})(name);

    };
    global["select"] = function() {
        var arg0 = arguments[0];
        if (arg0 instanceof Task.Accept || arg0 instanceof Function) {
            var o = new Task.AcceptSelect();
        } else if (arg0 instanceof Task.Call) {
            var o = new Task.CallSelect();
        } else {
            throw new Error("Unknown Structure given to Accept");
        }
        o.__body.apply(o, arguments);
        return o;
    };
};

Task.prototype.activate = function(args) {
    this.__type.__enter(this, arguments);
};

Task.prototype.__entryMethod = function(name) {
    var that = this;
    return function(args, afterRv, beforeRv) {
        if (! (args instanceof Array)) args = []; // event
        var obj = {task: that, at: name,
                   after: afterRv, before: beforeRv,
                   args: args};
        that.__queuedCalls.push(obj);
        that.__queuedCallsAt[name].push(obj);

        var entry = that.__waitingEntries[name];
        if (entry) {
            entry.__proceed(that);
        }
        return obj;
    };
};

Task.prototype.__cancelEntryCall = function(call) {
    var oldQueue = this.__queuedCalls;
    this.__queuedCalls = [];
    for (var i = 0; i< oldQueue.length; i++) {
        if (oldQueue[i] != call) this.__queuedCalls.push(oldQueue[i]);
    }


    var oldQueueAt = this.__queuedCallsAt[call.at];
    this.__queuedCallsAt[call.at] = [];
    for (var i = 0; i < oldQueueAt.length; i++) {
        if (oldQueueAt[i] != call)
            this.__queuedCallsAt[call.at].push(oldQueueAt[i]);
    }
};


Task.Statement = function(body) {
    this.__body = body;
    this.__callback = null;
};

Task.Statement.prototype.__enter = function(task, args) {
    var that  = this;
    setTimeout(
        function() {
            var result = that.__body.apply(task.__context, args);
            task.__context.__prev = result;
            that.__callback.method.call(that.__callback.reciever, task, args);
        }
    );
};

Task.Statement.prototype.__ownedBy = function(owner) {
    // null
};


Task.Structure = function() {
    // null
};

Task.Structure.prototype.__initializer = function() {
    this.__statementHead = null;
    this.__entries = [];
    this.__callback = null;
};

Task.Structure.prototype.__sugar = function(obj) {
    if (obj instanceof Function) {
        if (obj.name) {
            var accept = new Task.Accept();
            accept.__bodyWithSyntaxSugar(obj);
            return accept;
        } else {
            return new Task.Statement(obj);
        }
    } else {
        return obj;
    }
};

Task.Structure.prototype.__loadStatements = function(arg) {
    var statement, statementPrev;
    for (var i = 0; i < arg.length; i++) {
        statement = this.__sugar(arg[i]);
        statement.__ownedBy(this);
        if (this.__statementHead) {
            statementPrev.__callback =
                {method: statement.__enter, reciever: statement};
        } else {
            this.__statementHead = statement;
        }
        statementPrev = statement;
    }
    statementPrev.__callback = {method: this.__leave, reciever: this};
};

Task.Structure.prototype.__ownedBy = function(owner) {
    for (var i  = 0; i < this.__entries.length; i++)
        owner.__entries.push(this.__entries[i]);
};

Task.Structure.prototype.__enter = function(task, args) {
    this.__statementHead.__enter(task, args);
};

Task.Structure.prototype.__leave = function(task, args) {
    this.__callback.method.call(this.__callback.reciever, task, args);
};

Task.Structure.prototype.__defer = function() {
    return this.__statementHead ?
    {method: this.__statementHead.__enter, reciever: this.__statementHead} :
    {method: this.__callback.method, reciever: this.__callback.reciever};
};



Task.Type = function(body) {
    this.__initializer();
    if (body) this.body(arguments);
};
Task.Type.prototype = new Task.Structure();

Task.Type.prototype.create = function() {
    var task = new Task(this);
    return task;
};

Task.Type.prototype.body = function(body) {
    this.__loadStatements(arguments);
};

Task.Type.prototype.__leave = function(task, args) {
    // null
};



Task.Accept = function(name, body) {
    if (name) this.__body.apply(this, arguments);
};
Task.Accept.prototype = new Task.Structure();

Task.Accept.prototype.__body = function(name, body) {
    this.__name = name;
    this.__initializer();
    this.__entries.push(this.__name);
    this.__loadStatements(Array.prototype.slice.call(arguments, 1));
};

Task.Accept.prototype.__bodyWithSyntaxSugar = function(fun) {
    this.__name = fun.name;
    this.__initializer();
    this.__entries.push(this.__name);
    this.__loadStatements([new Task.Statement(fun)]);
};

Task.Accept.prototype.__enter = function(task, args) {
    if (task.__queuedCallsAt[this.__name].length > 0) {
        this.__proceed(task, args);
    } else {
        this.__wait(task);

    }
};

Task.Accept.prototype.__leave = function(task, args) {
    var randezvouser = task.__randezvousers.shift();
    if (randezvouser.after) {
        setTimeout(
            function() {randezvouser.after(task.__context.__prev);}, 0);
    }
    args = task.__context.__args.pop();
    this.__callback.method.call(this.__callback.reciever, task, args);
};

Task.Accept.prototype.__proceed = function(task, args) {
    if (task.__selectTimer) {
        clearTimeout(task.__selectTimer);
        task.__selectTimer = null;
    }
    var randezvouser = task.__queuedCallsAt[this.__name].shift();
    task.__randezvousers.push(randezvouser);
    if (randezvouser.before) randezvouser.before(randezvouser);
    var oldQueue = task.__queuedCalls;
    task.__queuedCalls = [];
    for (var i = 0; i< oldQueue.length; i++) {
        if (oldQueue[i] != randezvouser) task.__queuedCalls.push(oldQueue[i]);
    }
    if (args) task.__context.__args.push(args);
    this.__statementHead.__enter(task, randezvouser.args);
};

Task.Accept.prototype.__wait = function(task, args) {
    task.__context.__args.push(args);
    task.__waitingEntries[this.__name] = this;
};


Task.AcceptSelect = function(entry) {
    if (entry) this.__body(this, arguments);
};
Task.AcceptSelect.prototype = new Task.Structure();

Task.AcceptSelect.prototype.__body = function(entry) {
    this.__initializer();
    this.__options = {};
    this.__optionNames = [];
    this.__delay = null;
    for (var i = 0; i < arguments.length; i++) {
        var item = this.__sugar(arguments[i]);
        item.__ownedBy(this);
        item.__callback = {method: this.__leave, reciever: this};

        if (item instanceof Task.Delay) {
            this.__delay = item;
        } else {
            this.__options[item.__name] = item;
            this.__optionNames.push(item.__name);
        }
    }
};

Task.AcceptSelect.prototype.__enter = function(task, args) {
    var fastCheck = false;
    var optionNames = this.__optionNames;
    for (var i = 0; i < optionNames.length; i++) {
        if (task.__queuedCallsAt[optionNames[i]].length > 0) {
            fastCheck = true;
            break;
        }
    }
    if (fastCheck) {
        for (var i = 0; i < task.__queuedCalls.length; i++) {
            var at = task.__queuedCalls[i].at;
            for (var j = 0; j < optionNames.length; j++) {
                if (at == optionNames[j]) {
                    this.__options[at].__proceed(task);
                    return;
                }
            }
        }
    } else {
        for (var i = 0; i < optionNames.length; i++) {
            this.__options[optionNames[i]].__wait(task);
        }
        if (this.__delay) {
            var that = this;
            var timer = setTimeout(function() {that.__timeout(task, args);},
                                   this.__delay.__delayInMs(task, args));
            task.__selectTimer = timer;
        }
    }
};

Task.AcceptSelect.prototype.__timeout = function(task, args) {
    task.__selectTimer = null;
    this.__waitingEntries = {};
    var callback = this.__delay.__defer();
    callback.method.call(callback.reciever, task, args);
};


Task.Delay = function(delay, body) {
    if (delay) this.__body.apply(this, arguments);
};
Task.Delay.prototype = new Task.Structure();

Task.Delay.prototype.__body = function(delay, body) {
    this.__initializer();
    this.__delay = delay;
    if (body) this.__loadStatements(Array.prototype.slice.call(arguments, 1));
};

Task.Delay.prototype.__enter = function(task, args) {
    var callback = this.__defer();
    setTimeout(
        function() {
            callback.method.call(callback.reciever, task, args);
        }, this.__delayInMs(task));
};

Task.Delay.prototype.__delayInMs = function(task, args) {
    var delay = this.__delay;
    if (this.__delay instanceof Function)
        delay = this.__delay.apply(task.__context, args);
    return (delay instanceof Date) ? delay - new Date() : delay;
};


Task.Loop = function(condition, body) {
    if (condition) this.__body.apply(this, arguments);
};
Task.Loop.prototype = new Task.Structure();

Task.Loop.prototype.__body = function(condition, body) {
    this.__initializer();
    this.__condition = condition;
    if (body) this.__loadStatements(Array.prototype.slice.call(arguments, 1));
};

Task.Loop.prototype.__enter = function(task, args) {
    if (this.__condition.call(task.__context)) {
        this.__statementHead.__enter(task, args);
    } else {
        this.__callback.method.call(this.__callback.reciever, task, args);
    }
};

Task.Loop.prototype.__leave = function(task, args) {
    this.__enter(task, args);
};

Task.Call = function(entey) {
    if (entey) this.__body.apply(this, arguments);
};
Task.Call.prototype = new Task.Structure();

Task.Call.prototype.__body = function(target, arg, body) {
    this.__initializer();
    this.__target = target;
    this.__arg = arg;
    if (body) this.__loadStatements(Array.prototype.slice.call(arguments, 2));
};

Task.Call.prototype.__enter = function(task, args) {
    var target = this.__evalTarget(task, args);
    var targetArgs = this.__evalTargetArgs(task, args);
    var targetTask = target.task;

    var callback = this.__defer();
    target.call(targetTask, targetArgs,
                function() {
                    callback.method.call(callback.reciever, task, args);
                });
};

Task.Call.prototype.__evalTarget = function(task, args) {
    if (! this.__target) throw new Error("Entry not exist");
    var target =  (this.__target.task) ?
        this.__target : this.__target.apply(task.__context, args);
    if (! target) throw new Error("Entry not exist");
    return target;
};

Task.Call.prototype.__evalTargetArgs = function(task, args) {
    return (this.__arg instanceof Function) ?
        this.__arg.apply(task.__context, args) : this.__arg;
};

Task.CallSelect = function(calls) {
    if (calls) this.__body.apply(this, arguments);
};
Task.CallSelect.prototype = new Task.Structure();

Task.CallSelect.prototype.__body = function(entry) {
    this.__initializer();
    this.__options = [];
    this.__delay = null;
    for (var i = 0; i < arguments.length; i++) {
        var item = arguments[i];
        item.__ownedBy(this);
        item.__callback = {method: this.__leave, reciever: this};

        if (item instanceof Task.Delay) {
            this.__delay = item;
        } else {
            this.__options.push(item);
        }
    }
};

Task.CallSelect.prototype.__enter = function(task, args) {
    var that = this;
    for (var i = 0; i < this.__options.length; i++) {
        var call = this.__options[i];
        var target = call.__evalTarget(task, args);
        var callback = call.__defer();
        var canceler =
            target.call(target.task, call.__evalTargetArgs(task, args),
                        (function(c) {
                            return function() {
                                c.method.call(c.reciever, task, args);};
                         })(callback),
                         function(accepted) {
                             that.__accepted(task, args, accepted);
                         });
        task.__waitingCalls.push(canceler);
    }

    if (this.__delay) {
        var timer = setTimeout(function() {that.__timeout(task, args);},
            this.__delay.__delayInMs(task, args));
        task.__waitingCalls.delay = timer;
    }

};

Task.CallSelect.prototype.__accepted = function(task, args, accepted) {
    if (task.__waitingCalls.delay)
        clearTimeout(task.__waitingCalls.delay);
    for (var i = 0; i < task.__waitingCalls.length; i++) {
        var call = task.__waitingCalls[i];
        if (call != accepted) call.task.__cancelEntryCall.call(call.task, call);
    }
    task.__waitingCalls = [];
};

Task.CallSelect.prototype.__timeout = function(task, args) {
    this.__accepted(task, args);
    var callback = this.__delay.__defer();
    callback.method.call(callback.reciever, task, args);
};
