/*
 * Copyright (c) 2006, 2013, Oracle and/or its affiliates. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Oracle nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * This source code is provided to illustrate the usage of a given feature
 * or technique and has been deliberately simplified. Additional steps
 * required for a production-quality application, such as security checks,
 * input validation and proper error handling, might not be present in
 * this sample code.
 */

/*
 * Concurrency utilities for JavaScript. These are based on
 * java.lang and java.util.concurrent API. The following functions
 * provide a simpler API for scripts. Instead of directly using java.lang
 * and java.util.concurrent classes, scripts can use functions and
 * objects exported from here.
 */

// shortcut for j.u.c lock classes
var Lock = java.util.concurrent.locks.ReentrantLock;
var RWLock = java.util.concurrent.locks.ReentrantReadWriteLock;

// check if there is a build in sync function, define one if missing
if (typeof sync === "undefined") {
    var sync = function(func, obj) {
        if (arguments.length < 1 || arguments.length > 2 ) {
            throw "sync(function [,object]) parameter count mismatch";
        }

        var syncobj = (arguments.length == 2 ? obj : this);

        if (!syncobj._syncLock) {
            syncobj._syncLock = new Lock();
        }

        return function() {
            syncobj._syncLock.lock();
            try {
                func.apply(null, arguments);
            } finally {
                syncobj._syncLock.unlock();
            }
        };
    };
    sync.docString = "synchronize a function, optionally on an object";
}

/**
 * Wrapper for java.lang.Object.wait
 *
 * can be called only within a sync method
 */
function wait(object) {
    var objClazz = java.lang.Class.forName('java.lang.Object');
    var waitMethod = objClazz.getMethod('wait', null);
    waitMethod.invoke(object, null);
}
wait.docString = "convenient wrapper for java.lang.Object.wait method";

/**
 * Wrapper for java.lang.Object.notify
 *
 * can be called only within a sync method
 */
function notify(object) {
    var objClazz = java.lang.Class.forName('java.lang.Object');
    var notifyMethod = objClazz.getMethod('notify', null);
    notifyMethod.invoke(object, null);
}
notify.docString = "convenient wrapper for java.lang.Object.notify method";

/**
 * Wrapper for java.lang.Object.notifyAll
 *
 * can be called only within a sync method
 */
function notifyAll(object)  {
    var objClazz = java.lang.Class.forName('java.lang.Object');
    var notifyAllMethod = objClazz.getMethod('notifyAll', null);
    notifyAllMethod.invoke(object, null);
}
notifyAll.docString = "convenient wrapper for java.lang.Object.notifyAll method";

/**
 * Creates a java.lang.Runnable from a given script
 * function.
 */
Function.prototype.runnable = function() {
    var args = arguments;
    var func = this;
    return new java.lang.Runnable() {
        run: function() {
            func.apply(null, args);
        }
    }
};

/**
 * Executes the function on a new Java Thread.
 */
Function.prototype.thread = function() {
    var t = new java.lang.Thread(this.runnable.apply(this, arguments));
    t.start();
    return t;
};

/**
 * Executes the function on a new Java daemon Thread.
 */
Function.prototype.daemon = function() {
    var t = new java.lang.Thread(this.runnable.apply(this, arguments));
    t.setDaemon(true);
    t.start();
    return t;
};

/**
 * Creates a java.util.concurrent.Callable from a given script
 * function.
 */
Function.prototype.callable = function() {
    var args = arguments;
    var func = this;
    return new java.util.concurrent.Callable() {
          call: function() { return func.apply(null, args); }
    }
};

/**
 * Registers the script function so that it will be called exit.
 */
Function.prototype.atexit = function () {
    var args = arguments;
    java.lang.Runtime.getRuntime().addShutdownHook(
         new java.lang.Thread(this.runnable.apply(this, args)));
};

/**
 * Executes the function asynchronously.
 *
 * @return a java.util.concurrent.FutureTask
 */
Function.prototype.future = (function() {
    // default executor for future
    var juc = java.util.concurrent;
    var theExecutor = juc.Executors.newSingleThreadExecutor();
    // clean-up the default executor at exit
    (function() { theExecutor.shutdown(); }).atexit();
    return function() {
        return theExecutor.submit(this.callable.apply(this, arguments));
    };
})();

/**
 * Executes a function after acquiring given lock. On return,
 * (normal or exceptional), lock is released.
 *
 * @param lock lock that is locked and unlocked
 */
Function.prototype.sync = function (lock) {
    if (arguments.length == 0) {
        throw "lock is missing";
    }
    var res = new Array(arguments.length - 1);
    for (var i = 0; i < res.length; i++) {
        res[i] = arguments[i + 1];
    }
    lock.lock();
    try {
        this.apply(null, res);
    } finally {
        lock.unlock();
    }
};

/**
 * Causes current thread to sleep for specified
 * number of milliseconds
 *
 * @param interval in milliseconds
 */
function sleep(interval) {
    java.lang.Thread.sleep(interval);
}
sleep.docString = "wrapper for java.lang.Thread.sleep method";

/**
 * Schedules a task to be executed once in N milliseconds specified.
 *
 * @param callback function or expression to evaluate
 * @param interval in milliseconds to sleep
 * @return timeout ID (which is nothing but Thread instance)
 */
function setTimeout(callback, interval) {
    if (! (callback instanceof Function)) {
        callback = new Function(callback);
    }

    // start a new thread that sleeps given time
    // and calls callback in an infinite loop
    return (function() {
         try {
             sleep(interval);
         } catch (x) { }
         callback();
    }).daemon();
}
setTimeout.docString = "calls given callback once after specified interval";

/**
 * Cancels a timeout set earlier.
 * @param tid timeout ID returned from setTimeout
 */
function clearTimeout(tid) {
    // we just interrupt the timer thread
    tid.interrupt();
}
clearTimeout.docString = "interrupt a setTimeout timer";

/**
 * Schedules a task to be executed once in
 * every N milliseconds specified.
 *
 * @param callback function or expression to evaluate
 * @param interval in milliseconds to sleep
 * @return timeout ID (which is nothing but Thread instance)
 */
function setInterval(callback, interval) {
    if (! (callback instanceof Function)) {
        callback = new Function(callback);
    }

    // start a new thread that sleeps given time
    // and calls callback in an infinite loop
    return (function() {
         while (true) {
             try {
                 sleep(interval);
             } catch (x) {
                 break;
             }
             callback();
         }
    }).daemon();
}
setInterval.docString = "calls given callback every specified interval";

/**
 * Cancels a timeout set earlier.
 * @param tid timeout ID returned from setTimeout
 */
function clearInterval(tid) {
    // we just interrupt the timer thread
    tid.interrupt();
}
clearInterval.docString = "interrupt a setInterval timer";

/**
 * Simple access to thread local storage.
 *
 * Script sample:
 *
 *  __thread.x = 44;
 *  function f() {
 *      __thread.x = 'hello';
 *      print(__thread.x);
 *  }
 *  f.thread();       // prints 'hello'
 * print(__thread.x); // prints 44 in main thread
 */
var __thread = (function () {
    var map = new Object();
    return new JSAdapter({
        __has__: function(name) {
            return map[name] != undefined;
        },
        __get__: function(name) {
            if (map[name] != undefined) {
                return map[name].get();
            } else {
                return undefined;
            }
        },
        __put__: sync(function(name, value) {
            if (map[name] == undefined) {
                var tmp = new java.lang.ThreadLocal();
                tmp.set(value);
                map[name] = tmp;
            } else {
                map[name].set(value);
            }
        }),
        __delete__: function(name) {
            if (map[name] != undefined) {
                map[name].set(null);
            }
        }
    });
})();