/* Test the Promise module. */

if (!('assertEquals' in this)) { /* allow running this test standalone */
    imports.lang.copyPublicProperties(imports.jsUnit, this);
    gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
}

const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Promise = imports.promise;


/* Simple synchronous function. */
let example = function() {
    /* definitions */
    let getX = function() { return 2; };
    let getY = function() { return 3; };
    let add = function(x, y) { return x+y; };

    /* computation */
    let x = getX();
    let y = getY();
    let z = add(x, y);
    if (z<0) throw new Error("Negative!");
    return z;
};

/* Asynchronous equivalent, using a generator. */
let examplePromise =
    Promise.fromGenerator(
        function() {
            /* definitions */
            let xPromise = Promise.fromSync(function() { return 2; });
            let yPromise = Promise.fromSync(function() { return 3; });

            /* computation */
            let x = yield xPromise;
            let y = yield yPromise;

            let zPromise = Promise.fromSync(function(x, y) { return x+y; },
                                              [x,y]);

            let z = yield zPromise;

            if (z<0) throw new Error("Negative!");
            Promise.putReturnFromGenerator(z);
        });

function testExample() {
    /* synchronous function */
    assertEquals(5, example());
    /* Asynchronous Function. (invoked synchronously) */
    examplePromise.get(function(r) {
                           assertEquals(5, r);
                       }, function() {
                           assertTrue(false);
                       });
}

/* OK, now use Mainloop to actually invoke all the parts asynchronously. */
let examplePromise2 = Promise.fromGenerator(
    function() {
        function promiseValueInIdle(v) {
            let valuePromise = new Promise.Promise();
            try {
                Mainloop.idle_add(function() { valuePromise.putReturn(v); return false; });
            } catch (e) {
                valuePromise.putError(e);
            }
            return valuePromise;
        };
        /* computation */
        let x = yield promiseValueInIdle(2);
        let y = yield promiseValueInIdle(3);
        let z = yield promiseValueInIdle(x + y);
        if (z<0) throw new Error("Negative!");
        Promise.putReturnFromGenerator(z);
    });

function testExample2() {
    /* Asynchronous Function. (invoked asynchronously) */
    let success = false;
    examplePromise2.get(function(r) {
                            success = (r==5);
                            Mainloop.quit('asyncTest');
                        }, function(e) {
                            success = false;
                            Mainloop.quit('asyncTest');
                        });
    Mainloop.run('asyncTest');
    assertTrue(success);
}

/** Cached getter functionality. */
function testCachedGet() {
    let i = 0;
    let promise = Promise.fromSync(function() { return ++i; });

    /* We only increment i once, no matter how many times we get the
     * value of the promise. */
    promise.get(function() {}, function() {});
    assertEquals(1, i);
    promise.get(function() {}, function() {});
    assertEquals(1, i);
    promise.get(function() {}, function() {});
    assertEquals(1, i);

    /* works even when the function throws an exception */
    promise = Promise.fromSync(function() { ++i; throw new Error("whoa"); });
    promise.get(function(){}, function(){});
    assertEquals(2, i);
    promise.get(function(){}, function(){});
    assertEquals(2, i);
}

/* Test exception handling in the generator transformation. */
function testExGen() {
    function promiseToAdd(x, y) {
        let p = new Promise.Promise();
        p.putReturn(x + y);
        return p;
    }
    function promiseToThrow() {
        let p = new Promise.Promise();
        p.putError(new Error("this is an error"));
        return p;
    }

    let cleanedUp = false;
    let fooPromise =
        Promise.fromGenerator(
            function() {
                let y = yield promiseToAdd(2, 3);
                try {
                    let z = yield promiseToThrow();
                    return;
                } catch (e) {
                    y = yield promiseToAdd(y, 1);
                    Promise.putReturnFromGenerator(y);
                } finally {
                    cleanedUp = true;
                }
            });

    let success = false;
    fooPromise.get(function(v) { success=(v==6); }, function(e) { success=false; });
    assertTrue(success);
    assertTrue(cleanedUp);
}

function testParallel() {
    // this test doesn't really demonstrate that the tasks are started in
    // parallel, but it shows that the basic functionality is correct.
    function promiseValueInIdle(v) {
        let valuePromise = new Promise.Promise();
        try {
            Mainloop.idle_add(function() { valuePromise.putReturn(v); return false; });
        } catch (e) {
            valuePromise.putError(e);
        }
        return valuePromise;
    };
    function promiseErrorInIdle(v) {
        let valuePromise = new Promise.Promise();
        try {
            Mainloop.idle_add(function() { valuePromise.putError(new Error(v)); return false; });
        } catch (e) {
            valuePromise.putError(e);
        }
        return valuePromise;
    };
    function promiseAnotherPromise(p) {
        let newPromise = new Promise.Promise();
        p.get(function(v) {
                  newPromise.putReturn(v);
              },
              function(e) {
                  newPromise.putError(e);
              });
        return newPromise;
    }
    let promisedSum = Promise.fromGenerator(
        function() {
            let promise1 = promiseValueInIdle(1);
            let promise2 = promiseValueInIdle(2);
            let [b,a] = [(yield promise2), (yield promise1)];
                Promise.putReturnFromGenerator(a+b);
        });

    let success = false;
    promisedSum.get(function(r) {
                        success = (r==3);
                        Mainloop.quit('asyncTest');
                    }, function(e) {
                        success = false;
                        Mainloop.quit('asyncTest');
                    });
    Mainloop.run('asyncTest');
    assertTrue(success);

    // here we add a 'promiseAnotherPromise' function which blocks
    // waiting for the given promise.  This *does* test that the
    // functions are executed in parallel: it would deadlock if these
    // were serialized.
    let madeItPartWay = 0;
    let answerPromise = Promise.fromGenerator(
        function() {
            let promise1 = promiseValueInIdle(1);
            let promise2 = promiseErrorInIdle("testError");
            let promise3 = promiseAnotherPromise(promise1);
            yield promise3;
            yield promise1;
            // we should make it to here without deadlocking.
            madeItPartWay = 1;
            // fetching the value of promise2 should throw the exception.
            yield promise2;
            // shouldn't get here.
            madeItPartWay = 2;
            Promise.putReturnFromGenerator(42);
        });

    success = false;
    answerPromise.get(function(r) {
                          success = false;
                          Mainloop.quit('asyncTest');
                      }, function(e) {
                          success = (e.message=='testError');
                          Mainloop.quit('asyncTest');
                      });
    Mainloop.run('asyncTest');
    assertTrue(success);
    assertTrue(madeItPartWay==1);
}

function testAsyncConstructor() {
    // we also support asynchronous creation of objects.
    function promiseWaitForIdle() {
        let p = new Promise.Promise();
        try {
            Mainloop.idle_add(function() { p.putReturn(); return false; });
        } catch (e) {
            p.putError(e);
        }
        return p;
    };
    function Foo(onReturn, onError, val) {
        this._init(onReturn, onError, val);
    }
    Foo.prototype = {
        _init : function(onReturn, onError, val) {
            let p =
                Promise.fromGenerator(
                    function() {
                        yield promiseWaitForIdle();
                        this.val = val;
                    }, this, []);

            p.get(onReturn, onError);
        }
    };

    let promiseFooVal = Promise.fromGenerator(
        function() {
            let foo = yield Promise.fromConstructor(Foo, [5]);
            // all the expected properties of an instance of Foo are present
            assertTrue(typeof foo == "object");
            assertTrue(foo instanceof Foo);
            assertTrue(foo.constructor == Foo);
            Promise.putReturnFromGenerator(foo.val);
        });

    let success = false;
    promiseFooVal.get(function(r) {
                          success = (r==5);
                          Mainloop.quit('asyncTest');
                      }, function(e) {
                          logError(e, "fail");
                          Mainloop.quit('asyncTest');
                      });
    Mainloop.run('asyncTest');
    assertTrue(success);
}

gjstestRun();
