/**
 * The task.
 */
var Task = function(body) {
    this.__context = {
        task: this
    };
    this.__context.__args = [];
    this.__randezvousers = [];
    this.__waitingEntries = {};
    this.__queuedCalls = [];
    this.__queuedCallsAt = {};
    this.__select = {};
    this.__waitingCalls = [];
    this.__status = 0;
    this.__master = null;
    this.__slaves = [];
    this.__slaves.terminated = 0;

    if (body instanceof Task.Type) {
        // created from a task type
        this.__type = body;
    } else {
        // add-hook run
        this.__type = new Task.Type();
        this.__type.body.apply(this.__type, arguments);
    }

    // define entries as a instance specific method
    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; // used to confirm the method as an entry
    }
};


/**
 * Defineds some functions as a syntax sugar on a given object or global scope.
 * @param {Object} o An object to register the functions to.
 */
Task.define = function(o) {
    // get the global object (or given o)
    var global = o || (function() {return this;}).apply(null);
    // register childlen of Task.Structure to the object
    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);

    };
    // selct() is used for AcceptSelect and CallSelect
    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;
    };

    global["when"] = function(condition) {
        return new Task.When(condition);
    };

};

/**
 * Activates the task with given arguments.
 * The arguments passed to the all statements in the task.
 */
Task.prototype.activate = function(args) {
    if (this.__status == 0) {
        this.__status++;
        this.__type.__enter(this, arguments);
    } else {
        throw new Error("The task instance was already activated.");
    }
};

Task.prototype.callable = function() {
    if (this.__status < 2) {
        return false;
    }
    return true;
};

Task.prototype.terminated = function() {
    if (this.__status > 2) {
        return true;
    }
    return false;
};


/**
 * Remove all elements which matches 'item' from an Array 'ary'.
 * @param {Array} ary An array.
 * @param {Object} item An object to be rejcted.
 */
Task.__rejectArray = function(ary, item) {
    var newAry = [];
    for (var i = 0; i < ary.length; i++) if (ary[i] != item) newAry.push(ary[i]);
    return newAry;
};

/**
 * Creates and returns a function object used as an entry.
 * @param {string} name The name of the entry.
 */
Task.prototype.__entryMethod = function(name) {
    var that = this;
    var fun =  function(args, afterRv, beforeRv) {
        if (that.__status > 1) return false;
        // when set as a browser's event handler,
        // an event object passed as the first argument
        if (! (args instanceof Array)) args = [];

        // an object to stores what to do in randevous
        var obj = {
            task: that, at: name, after: afterRv, before: beforeRv, args: args
        };

        // push into the queue once
        that.__queuedCalls.push(obj);
        that.__queuedCallsAt[name].push(obj);

        // when the target entry wating already, accept to the acception
        var entry = that.__waitingEntries[name];
        if (entry) entry.__accept(that);

        return obj;
    };
    fun.count = function() {
        return that.__queuedCallsAt[name].length;
    };

    return fun;
};

/**
 * Canncels an entry call already issued.
 * @param {Object} call The calling object returned from the entry call.
 */
Task.prototype.__cancelEntryCall = function(call) {
    this.__queuedCalls = Task.__rejectArray(this.__queuedCalls, call);
    this.__queuedCallsAt[call.at]  =
        Task.__rejectArray(this.__queuedCallsAt[call.at], call);
};

Task.prototype.__hasAcceptableEntryCall = function(entry, task, args) {
    if (this.__queuedCallsAt[entry.__name].length > 0) {
        return (entry.__condition)
            ? entry.__condition.apply(task.__context, args)
            : true;
    } else {
        return false;
    }
};

Task.prototype.__setMaster = function(master) {
    this.__master = master;
};

Task.prototype.__terminate = function() {
    this.__select.terminate();
};


Task.prototype.__finalize = function() {
    this.__status++;
    if (this.__master) this.__master.__slaveTerminated();
};


Task.prototype.__slaveTerminated = function() {
    if (this.__slaves.length == ++this.__slaves.terminated)
        this.__finalize();
};

Task.prototype.__tryTerminateSlaves = function() {
    if (this.__slaves.length == 0) {
        this.__finalize();
        return;
    }

    var flag = true;
    for (var i = 0; i < this.__slaves.length; i++) {
        var slave = this.__slaves[i];
        if (! slave.__isTerminatable()) {
            flag =  false;
            break;
        }
    }

    if (flag) {
        for (var i = 0; i < this.__slaves.length; i++) {
            var slave = this.__slaves[i];
            if (! slave.terminated()) slave.__terminate();
        }
    }
};

Task.prototype.__isTerminatable = function() {
    if (this.__status > 2)
        return true;
    if (this.__slave && this.__slaves.length == this.__slaves.terminated)
        return false;
    if (this.__select.terminate)
        return true;
    return false;
};


/**
 * An abstract class to represent a task structure.
 * A task structure contains some task statements.
 * Do nothing in the constructor to avoid bothering on inheritance.
 */
Task.Structure = function() {
    // null
};

/**
 * Initializes the instance.
 */
Task.Structure.prototype.__initializer = function() {
    /**
     * The first one of task the statements.
     */
    this.__statementHead = null;
    /**
     * The list of entry names contained by this instance and children.
     * Updated by calling __ownedBy().
     */
    this.__entries = [];
    /**
     * The object which has keys(method, reciever).
     * After execution of all the statements,
     * call next process using this object.
     */
    this.__callback = null;
};

/**
 * Evaluates syntax sugar and returns the result.
 */
Task.Structure.prototype.__sugar = function(obj) {
    if (obj instanceof Function) {
        return new Task.Statement(obj);
    } else if (! (obj instanceof Task.Structure)) {
        var entries = [];
        for (var name in obj) if (obj.hasOwnProperty(name)) {
            if (name != "delay") entries.push(new Task.Accept(name, obj[name]));
            else entries.push(new Task.Delay(obj[name]));
        }
        var select = new Task.AcceptSelect();
        select.__body.apply(select, entries);
        return select;
    } else {
        return obj;
    }
};

Task.Structure.prototype.__body = function() {
    this.__initializer();
};

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};
};


/**
 * A class represents a processing unit.
 */
Task.Statement = function(body) {
    this.__body = body;
    this.__callback = null;
};
Task.Statement.prototype = new Task.Structure();

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

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

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


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) {
    task.__status++;
    var context = task.__context;
    for (var i in context) {
        if (context.hasOwnProperty(i)
            && (context[i] instanceof Task)
            && context[i] != task) {
                var slave = context[i];
                task.__slaves.push(slave);
                slave.__setMaster(task);
                if (slave.terminated()) task.__slaves.terminated++;
        }
    }
    task.__tryTerminateSlaves();
};



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.__condition = null;
    if (body) this.__loadStatements(Array.prototype.slice.call(arguments, 1));
};

Task.Accept.prototype.__enter = function(task, args) {
    if (task.__hasAcceptableEntryCall(this, task, args)) {
        this.__accept(task, args);
    } else {
        task.__context.__args.push(args);
        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.__accept = function(task) {
    if (task.__select.timer) {
        clearTimeout(task.__select.timer);
        task.__select = {};
    }
    task.__waitingEntries = {};
    var randezvouser = task.__queuedCallsAt[this.__name].shift();
    task.__randezvousers.push(randezvouser);
    if (randezvouser.before) randezvouser.before(randezvouser);
    task.__queuedCalls = Task.__rejectArray(task.__queuedCalls, randezvouser);
    var callback = this.__defer();
    callback.method.call(callback.reciever, task, randezvouser.args);
};

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


Task.Accept.prototype.__wait = function(task) {
    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;
    this.__terminate = 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 if (item instanceof Task.Terminate) {
            this.__terminate = item;
        } else {
            this.__options[item.__name] = item;
            this.__optionNames.push(item.__name);
        }
    }

    if (this.__delay && this.__terminate)
        throw new Error(
            "terminate and delay altarnative are exclusive each other.");
};

Task.AcceptSelect.prototype.__enter = function(task, args) {
    var acceptableEntries = [];
    var optionNames = this.__optionNames;
    for (var i = 0; i < optionNames.length; i++) {
        if (task.__hasAcceptableEntryCall(this.__options[optionNames[i]],
                                          task,
                                          args)) {
            acceptableEntries.push(optionNames[i]);
        }
    }
    if (acceptableEntries.length > 0) {
        for (var i = 0; i < task.__queuedCalls.length; i++) {
            var at = task.__queuedCalls[i].at;
            for (var j = 0; j < acceptableEntries.length; j++) {
                if (at == acceptableEntries[j]) {
                    this.__options[at].__accept(task);
                    return;
                }
            }
        }
    } else {
        task.__context.__args.push(args);
        for (var i = 0; i < optionNames.length; i++) {
            var entry = this.__options[optionNames[i]];
            if (entry.__condition)
                if (! entry.__condition.apply(task.__context, args))
                    continue;
            entry.__wait(task);
        }
        var that = this;
        if (this.__delay) {
            var timer = setTimeout(function() {that.__timeout(task, args);},
                                   this.__delay.__delayInMs(task, args));
            task.__select.timer = timer;
        } else if (this.__terminate) {
            task.__select.terminate = function() {
                task.__type.__leave(task, args);
            };
            if (task.__master) task.__master.__tryTerminateSlaves();
        }
    }
};

Task.AcceptSelect.prototype.__timeout = function(task, args) {
    task.__select = {};
    task.__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) {
    var flag;
    if (this.__condition instanceof Function){
        flag = this.__condition.call(task.__context);
    } else if (type(this.__condition) == "number") {

    }

    if (flag) {
        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.Redirect = function(fun, args) {
    if (fun) this.__body.apply(this, arguments);
};
Task.Redirect.prototype = new Task.Structure();

Task.Redirect.prototype.__body = function(fun, args) {
    args = Array.prototype.slice.call(arguments, 1);
    this.__loadStatements(
        [new Task.Statement(function() {return fun.apply(this, args);})]);
};



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);
};

Task.Terminate = function() {
};
Task.Terminate.prototype = new Task.Structure();



Task.When = function(condition) {
    this.__condition = condition;
};

Task.When.prototype.accept = function(args) {
    var accept = new Task.Accept();
    accept.__body.apply(accept, arguments);
    accept.__condition = this.__condition;
    return accept;
};
