// This file is for routines to be included by multiple parts of Passpet.

// Debugging (label may be followed by any number of arguments).
function print(label) {
    dump(label);
    if (arguments.length > 1) dump(': ');
    for (var i = 1; i < arguments.length; i++) {
        if (i > 1) dump(', ');
        dump(arguments[i]);
    }
    dump('\n');
}

// Shorthand for getting XPConnect interfaces, classes, objects, and services.
const XPI = Components.interfaces, XPC = Components.classes;
function XPC_(c) { return XPC[c.match(/^@/) ? c : '@mozilla.org/' + c]; }
function XPO(c, i) { return XPC_(c) ? XPC_(c).createInstance(i) : null; }
function XPS(c, i) { return XPC_(c) ? XPC_(c).getService(i) : null; }
function XQI(object, i) { return object.QueryInterface(i); }

// ---------------------------------------------------------------- strings

// String localization.
var localeService = XPS('intl/nslocaleservice;1', XPI.nsILocaleService)
var locale = localeService.getApplicationLocale()
var bundleService = XPS('intl/stringbundle;1', XPI.nsIStringBundleService)
var bundleURL = 'chrome://passpet/locale/passpet.properties'
var bundle = bundleService.createBundle(bundleURL, locale)
function localize(name) { return bundle.GetStringFromName(name) || name; }

// ----------------------------------------------------------------- arrays

function randomChoice(array) {
    return array[Math.floor(Math.random() * array.length)];
}

function contains(array, element) {
    for (var i = 0; i < array.length; i++) {
        if (array[i] == element) return true;
    }
    return false;
}

function filter(array, predicate) {
    var results = [];
    for (var i = 0; i < array.length; i++) {
        if (predicate(array[i])) results.push(array[i]);
    }
    return results;
}

function map(array, operation) {
    var results = [];
    for (var i = 0; i < array.length; i++) {
        results.push(operation(array[i]));
    }
    return results;
}

// ---------------------------------------------------------- continuations

function Cont(id, proceed, abort, error) {
    var pfunc = (proceed == null) ? (function() { }) :
                (proceed.proceed == null) ? proceed : proceed.proceed;
    this.proceed = function() {
        var args = '';
        for (var i = 0; i < arguments.length; i++) args += ', ' + arguments[i];
        dump('Cont ' + id + ' proceed: ' + args.substring(2) + '\n');
        pfunc.apply(null, arguments);
    }
    var afunc = (abort == null) ? (function() { }) :
                (abort.abort == null) ? abort : abort.abort;
    if (error == null) {
        this.abort = function(e) {
            dump('Cont ' + id + ' abort: ' + e + '\n');
            afunc(e);
        }
    } else {
        this.abort = function(e) {
            dump('Cont ' + id + ' abort: ' + e + ' -> ' + error + '\n');
            afunc(error);
        }
    }
    this.cancel = function() {
        this.proceed = function() { };
        this.abort = function() { };
    };
}

const NULL_CONTINUATION = new Cont();
NULL_CONTINUATION.cancel();

// -------------------------------------------------------------------- DOM

// Handy shorthand for getting XUL elements.
function $(id) { return document.getElementById(id); }

const dom = new function() {
    // Element retrieval.
    this.tags = function(name, class) {
        var ns = 'http://www.w3.org/1999/xhtml';
        var tags = document.getElementsByTagNameNS(ns, name);
        if (class != null) tags = filter(tags, function(element) {
            return contains(element.className.split(/\s+/), class);
        });
        return tags;
    }

    // Element creation.
    this.create = function(name, class) {
        var element = document.createElement(name);
        if (class != null) element.setAttribute('class', class);
        return element;
    };

    this.append = function(parent, element) {
        parent.appendChild(element);
    };

    this.add = function(parent, name, class) {
        var element = this.create(name, class);
        parent.appendChild(element);
        return element;
    };

    this.write = function(parent, text) {
        var element = document.createTextNode(text);
        parent.appendChild(element);
        return element;
    };

    this.remove = function(element) {
        element.parentNode.removeChild(element);
    };

    this.put = function(element, text) {
        while (element.hasChildNodes()) {
            element.removeChild(element.firstChild);
        }
        return this.write(element, text);
    };

    // Attributes.
    this.set = function(element, name, value) {
        element.setAttribute(name, value);
    };

    this.get = function(element, name) {
        return element.getAttribute(name);
    };

    this.classify = function(element, name) {
        element.setAttribute('class', name);
    };

    this.disable = function(element) {
        element.setAttribute('disabled', true);
    };

    this.enable = function(element, parity) {
        if (parity == null) parity = true;
        element.setAttribute('disabled', !parity);
    };

    this.tip = function(element, text) {
        if (text) element.setAttribute('tooltiptext', text);
        else element.removeAttribute('tooltiptext');
    };

    // Appearance.
    this.hide = function() {
        for (var i = 0; i < arguments.length; i++) {
            arguments[i].style.display = 'none';
        }
    };

    this.show = function() {
        for (var i = 0; i < arguments.length; i++) {
            arguments[i].style.display = '-moz-box';
        }
    };

    this.showBlock = function() {
        for (var i = 0; i < arguments.length; i++) {
            arguments[i].style.display = 'block';
        }
    };

    // Events.
    this.listen = function(element, type, handler) {
        element.addEventListener(type, handler, true);
    };

    this.unlisten = function(element, type, handler) {
        element.removeEventListener(type, handler, true);
    };
}

// Set the visibility of some widgets.
function display(value) {
    for (var i = 1; i < arguments.length; i++) {
        arguments[i].style.display = value;
    }
}

// Set the opacity of some widgets.
function fade(opacity) {
    for (var i = 1; i < arguments.length; i++) {
        arguments[i].style.opacity = opacity;
    }
}

