/*
* Copyright (c) 2005-2007
* Authors: KSS Project Contributors (see doc/CREDITS.txt)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/

/* Create kukit namespace */

if (typeof(kukit) == 'undefined') {
    var kukit = {};
}

;;; /*  ----------------------------------------------------------------
;;;  *  Lines starting with the ;;; are only cooked in development mode.
;;;  *  ----------------------------------------------------------------
;;;  */

/*
 * kukit.E is a proxy variable used globally for error and info messages.
 * This assure the following code can be used:
 *    
 *     ;;; kukit.E = 'This is the error message';
 *     throw kukit.E;
 *
 * or:
 *
 *     ;;; kukit.E = 'The event' + event + ' caused problems';
 *     method_with_info(x, kukit.E);
 *
 * or even:
 *
 *     ;;; kukit.E = 'The event' + event + ' caused problems ';
 *     ;;; kukit.E += 'and this is a very long line ';
 *     ;;; kukit.E += 'so we split it to parts for better readibility';
 *     ;;; kukit.logWarning(kukit.E);
 *
 */
kukit.E = 'Unknown message (kss optimized for production mode)';

// Browser identification. We need these switches only at the moment.
try {
    kukit.HAVE_SAFARI = navigator.vendor && 
        navigator.vendor.indexOf('Apple') != -1;
    kukit.HAVE_IE =  eval("_SARISSA_IS_IE");
} catch (e) {}

;;; // Activation of extra logging panel: if necessary
;;; // this allows to start the logging panel from the browser with
;;; //    javascript:kukit.showLog();
;;; kukit.showLog = function() {
;;;     var msg = 'Logging is on the console: request to show logging pane';
;;;     msg += ' ignored.';
;;;     kukit.logWarning(msg);
;;; };

/*
 * Cookie handling code taken from: 
 * http://www.quirksmode.org/js/cookies.html
 * Cookie handling is in dom.js, but this method
 * is needed right here for log handling.
 */

kukit.readCookie = function(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

// a null function that is used for logging
kukit._null = function() {};

kukit._COOKIE_LOGLEVEL = '__kss_loglevel';

// an empty logger
kukit._logger = new function() {

    this.updateLogLevel = function() {
;;;     // set default level
;;;     this.loglevel = 0;
;;;     // read the cookie
;;;     var cookie = kukit.readCookie(kukit._COOKIE_LOGLEVEL);
;;;     if (cookie) {
;;;         // decode it to a numeric level
;;;         cookie = cookie.toLowerCase();
;;;         // Cookies are quoted in Zope, for some reason (???)
;;;         // ie we get '"VALUE"' here. Let's compensate this.
;;;         if (cookie.substr(0, 1) == '"') {
;;;             cookie = cookie.substr(1, cookie.length - 2);
;;;         }
;;;         if (cookie == 'debug') this.loglevel = 0;
;;;         if (cookie == 'info') this.loglevel = 1;
;;;         if (cookie == 'warning') this.loglevel = 2;
;;;         if (cookie == 'error') this.loglevel = 3;
;;;     };
        // Call the function that sets up the handlers
        this._setupHandlers();
        // Wrap the just set up handlers, to include wrapping
;;;     this.logDebug = this._logFilter(this.logDebug, 0);
;;;     this.log = this._logFilter(this.log, 1);
;;;     this.logWarning = this._logFilter(this.logWarning, 2);
;;;     this.logError = this._logFilter(this.logError, 3);
;;;     this.logFatal = this._logFilter(this.logFatal, 3);
    };

    // Log filter, for use from the handlers.
;;; this._logFilter = function(f, currentlevel) {
;;;     return (currentlevel >= this.loglevel) ? f : kukit._null;
;;; };

    // This sets up the handlers and allows to set them
    // up again with a different cookie setting.
    // Will be overwritten by different loggers.
    this._setupHandlers = function() {
        this.logDebug = kukit._null;
        this.log = kukit._null;
        this.logWarning = kukit._null;
        this.logError = kukit._null;
        this.logFatal = kukit._null;
    };
}();

// Stub functions that can be used for logging
kukit.logDebug = function(message) {kukit._logger.logDebug(message);};
kukit.log = function(message) {kukit._logger.log(message);};
kukit.logWarning = function(message) {kukit._logger.logWarning(message);};
kukit.logError = function(message) {kukit._logger.logError(message);};
kukit.logFatal = function(message) {kukit._logger.logFatal(message);};

// Function to change the log level from javascript
// level must be one of "DEBUG", "INFO", "WARNING", "ERROR".
// (Small caps are tolerated as well)
kukit.setLogLevel = function(level) {
;;;     // Store it in the cookie so that it persists through requests.
;;;     kukit.dom.createCookie(kukit._COOKIE_LOGLEVEL, level);
;;;     // re-establish the log handlers, based on this cookie setting
;;;     kukit._logger.updateLogLevel();
}

// We want a way of knowing if Firebug is available :
// it is very convenient to log a node in Firebug;
// you get a clickable result that brings you to Firebug inspector.
// The pattern is the following :
//  if (kukit.hasFirebug) {
//     kukit.log(node);
//  }

;;; // check whether the logging stuff of Firebug is available
;;; kukit.hasFirebug = function() {
;;;    var result = typeof console != 'undefined';
;;;    result = result && typeof console.log != 'undefined';
;;;    result = result && typeof console.debug != 'undefined';
;;;    result = result && typeof console.error != 'undefined';
;;;    result = result && typeof console.warn != 'undefined';
;;;    return result;
;;; }();

;;; // Set up logging for FireBug
;;; if (kukit.hasFirebug) {
;;;     kukit._logger._setupHandlers = function() {
;;;         // for debug level we also log as 'info', because we do
;;;         // not want FireBug to display line information.
;;;         this.logDebug = console.log;
;;;         this.log = console.log;
;;;         this.logWarning = console.warn;
;;;         this.logError = console.error;
;;;         this.logFatal = console.error;
;;;     }
;;; }

;;; // check whether the logging stuff of MochiKit is available
;;; kukit.hasMochiKit = function() {
;;;    var result = typeof MochiKit != 'undefined';
;;;    result = result && typeof MochiKit.Logging != 'undefined';
;;;    result = result && typeof MochiKit.Logging.log != 'undefined';
;;;    return result;
;;; }();

;;; // Set up logging for MochiKit
;;; if (! kukit.hasFirebug && kukit.hasMochiKit) {
;;;     kukit._logger._setupHandlers = function() {
;;;         this.logDebug = MochiKit.Logging.logDebug;
;;;         this.log = MochiKit.Logging.log;
;;;         this.logWarning = MochiKit.Logging.logWarning;
;;;         this.logError = MochiKit.Logging.logError;
;;;         this.logFatal = MochiKit.Logging.logFatal;
;;;     }
;;;     // make convenience url
;;;     //    javascript:kukit.showLog();
;;;     // instead of the need to say
;;;     //    javascript:void(createLoggingPane(true));
;;;     kukit.showLog = function() {
;;;         createLoggingPane(true);
;;;     };
;;; }

;;; // check whether the logging stuff of Safari is available
;;; kukit.hasSafari = function() {
;;;    var result = typeof console != 'undefined';
;;;    result = result && typeof console.log != 'undefined';
;;;    return result;
;;; }();

;;; // Set up logging for Safari
;;; if (! kukit.hasFirebug && ! kukit.hasMochiKit && kukit.hasSafari) {
;;;     kukit._logger._setupHandlers = function() {
;;;         this.logDebug = function(str) { console.log('DEBUG: '+str); };
;;;         this.log = function(str) { console.log('INFO: '+str); };
;;;         this.logWarning = function(str) { console.log('WARNING: '+str); };
;;;         this.logError = function(str) { console.log('ERROR: '+str); };
;;;         this.logFatal = function(str) { console.log('FATAL: '+str); };
;;;     }
;;; }

// Initialize the logger with the solution we've just detected
kukit._logger.updateLogLevel();

// log a startup message
;;; kukit.log('Loading KSS engine.');

/* utilities */

kukit.ut = {};


/* 
* class FifoQueue
*/
kukit.ut.FifoQueue = function () {
    this.reset();
};

kukit.ut.FifoQueue.prototype.reset = function() {
    this.elements = new Array();
};

kukit.ut.FifoQueue.prototype.push = function(obj) {
    this.elements.push(obj);
};

kukit.ut.FifoQueue.prototype.pop = function() {
    return this.elements.shift();
};

kukit.ut.FifoQueue.prototype.empty = function() {
    return ! this.elements.length;
};

kukit.ut.FifoQueue.prototype.size = function() {
    return this.elements.length;
};

kukit.ut.FifoQueue.prototype.front = function() {
    return this.elements[0];
};

/*
* class SortedQueue
*/
kukit.ut.SortedQueue = function (comparefunc) {
    // comparefunc(left, right) determines the order by returning 
    // -1 if left should occur before right,
    // +1 if left should occur after right or 
    //  0 if left and right  have no preference as to order.
    // If comparefunc is not specified or is undefined,
    // the default order specified by < used.
    if (comparefunc) {
        this.comparefunc = comparefunc;
    }
    this.reset();
};

kukit.ut.SortedQueue.prototype.comparefunc = function(a, b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return +1;
    } else {
        return 0;
    }
};

kukit.ut.SortedQueue.prototype.reset = function() {
    this.elements = new Array();
};

kukit.ut.SortedQueue.prototype.push = function(obj) {
    // Find the position of the object.
    var i = 0;
    var length = this.elements.length;
    while (i < length && this.comparefunc(this.elements[i], obj) == -1) {
        i ++;
    }
    // and insert it there
    this.elements.splice(i, 0, obj);
};

kukit.ut.SortedQueue.prototype.pop = function() {
    // takes minimal element
    return this.elements.shift();
};

kukit.ut.SortedQueue.prototype.popn = function(n) {
    // takes first n minimal element
    return this.elements.splice(0, n);
};

kukit.ut.SortedQueue.prototype.empty = function() {
    return ! this.elements.length;
};

kukit.ut.SortedQueue.prototype.size = function() {
    return this.elements.length;
};

kukit.ut.SortedQueue.prototype.get = function(n) {
    return this.elements[n];
};

kukit.ut.SortedQueue.prototype.front = function() {
    return this.elements[0];
};

kukit.ut.evalBool = function(value, errname) {
    if (value == 'true' || value == 'True' || value == '1') {
        value = true;
    } else if (value == 'false' || value == 'False' || value == '0'
        || value == '') {
        value = false;
;;;     } else {
;;;         throw 'Bad boolean value "' + value + '" ' + errname;
    }
    return value;
};

kukit.ut.evalInt = function(value, errname) {
;;;     try {
        value = parseInt(value);
;;;     } catch(e) {
;;;         throw 'Bad integer value "' + value + '" ' + errname;
;;;     }
    return value;
};

kukit.ut.evalList = function(value, errname) {
;;;     try {
        // remove whitespace from beginning, end
        value = value.replace(/^ +/, '');
        //while (value && value.charAt(0) == ' ') {
        //    value = value.substr(1);
        //}
        value = value.replace(/ +$/, '');
        // do the splitting
        value = value.split(/ *, */);
;;;     } catch(e) {
;;;         throw 'Bad list value "' + value + '" ' + errname;
;;;     }
    return value;
};



/* 
* class TimerCounter
*
* for repeating or one time timing
*/
kukit.ut.TimerCounter = function(delay, func, restart) {
    this.delay = delay;
    this.func = func;
    if (typeof(restart) == 'undefined') {
        restart = false;
    }
    this.restart = restart;
    this.timer = null;
};

kukit.ut.TimerCounter.prototype.start = function() {
    if (this.timer) {
;;;     kukit.E = 'Timer already started.';

        throw kukit.E;
    }
    var self = this;
    var func = function() {
        self.timeout();
    };
    this.timer = setTimeout(func, this.delay);
};

kukit.ut.TimerCounter.prototype.timeout = function() {
    // Call the event action
    this.func();
    // Restart the timer
    if (this.restart) {
        this.timer = null;
        this.start();
    }
};

kukit.ut.TimerCounter.prototype.clear = function() {
    if (this.timer) {
        window.clearTimeout(this.timer);
        this.timer = null;
    }
    this.restart = false;
};

/*
* class Scheduler
*/
kukit.ut.Scheduler = function(func) {
    this.func = func;
    this.timer = null;
    this.nextWake = null;
};

kukit.ut.Scheduler.prototype.setNextWake = function(ts) {
    // Sets wakeup time, null clears
    if (this.nextWake) {
        this.clear();
    }
    if (! ts) {
        return;
    }
    var now = (new Date()).valueOf();
    if (ts > now) {
        this.nextWake = ts;
        var self = this;
        var func = function() {
            self.timeout();
        };
        this.timer = setTimeout(func, ts - now);
    } else {
        // if in the past, run immediately
        this.func();
    }
};

kukit.ut.Scheduler.prototype.setNextWakeAtLeast = function(ts) {
    // Sets wakeup time, unless it would wake up later than the
    // currently set timeout. Null clears the timer.
    if (! ts || ! this.nextWake || ts < this.nextWake) {
        this.setNextWake(ts);
    } else {
        var now = (new Date()).valueOf();
        // XXX why compute now and not use it ?
    }
};

kukit.ut.Scheduler.prototype.timeout = function() {
    // clear the timer
    this.timer = null;
    this.nextWake = null;
    // Call the event action
    this.func();
};


kukit.ut.Scheduler.prototype.clear = function() {
    if (this.nextWake) {
        window.clearTimeout(this.timer);
        this.timer = null;
        this.nextWake = null;
    }
};

/* 
* class SerializeScheduler
*
* Scheduler for serializing bind and load procedures
*/
kukit.ut.SerializeScheduler = function() {
    this.items = [];
    this.lock = false;
};

kukit.ut.SerializeScheduler.prototype.addPre = function(func, remark) {
    this.items.push({func: func, remark: remark});
    this.execute();
};

kukit.ut.SerializeScheduler.prototype.addPost = function(func, remark) {
    this.items.unshift({func: func, remark: remark});
    this.execute();
};

kukit.ut.SerializeScheduler.prototype.execute = function() {
    if (! this.lock) {
        this.lock = true;
        while (true) {
            var item = this.items.pop();
            if (! item) {
                break;
            }
;;;         kukit.log(item.remark + ' starts.');
;;;         var ts_start = (new Date()).valueOf();
            try {
                item.func();
            } catch(e) {
                this.lock = false;
                throw e;
            }
;;;         var ts_end = (new Date()).valueOf();
;;;         var msg = item.remark + ' finished in ';
;;;         msg += (ts_end - ts_start) + ' ms.';
;;;         kukit.log(msg);
        }
        this.lock = false;
    }
};

/* Browser event binding */

/* extracted from Plone */
// cross browser function for registering event handlers
kukit.ut.registerEventListener = function(elem, event, func) {
    if (elem.addEventListener) {
        elem.addEventListener(event, func, false);
        return true;
    } else if (elem.attachEvent) {
        var result = elem.attachEvent("on"+event, func);
        return result;
    }
    // maybe we could implement something with an array
    return false;
};


/* collecting keys-values into a dict or into a tuple list */

kukit.ut.DictCollector = function() {
    this.result = {};
};

kukit.ut.DictCollector.prototype.add = function(key, value) {
    this.result[key] = value;
};

kukit.ut.TupleCollector = function() {
    this.result = [];
};

kukit.ut.TupleCollector.prototype.add = function(key, value) {
    this.result.push([key, value]);
};

