// ============================================================================
// GENERAL-PURPOSE JAVASCRIPT LIBRARY
// Dave Herman
// ============================================================================


// ============================================================================
// Global Functions
// ============================================================================

function membersOf(o) {
    var result = new Array();
    var i = 0;
    for (var name in o) {
        result[i++] = name;
    }
    return result;
};


// ============================================================================
// Array
// ============================================================================

/*

 interface Indexed {
     property length : [0, 2^32)
     property 0 : Object
     ...
     property [[this.length]] : Object
 }

 interface List {
     method concat : List -> List
     method reverse : List -> List
 }

 // TODO: which of the new methods can go in List?

 interface Array : List, Indexed {
     method join : [String] -> String
     method pop : -> Object
     method push : Object -> Object
     method shift : -> Object
     method slice : Number Number -> Array
     method sort : (Object Object -> ???) -> Array
     method unshift : Object -> Number

     // new methods:
     method copy : -> Array
     method cons : Object -> Array
     method first : -> Object
     method rest : -> Array
     method foreach : (Object -> Object) -> Null
     method filter : (Object -> Boolean) -> Array
     method map : (Object -> Object) -> Array
     method contains : Object -> Boolean
     method without : Object -> Array
 }

 function makeArray : Indexed -> Array

*/

// The DOM often produces objects that are a lot *like* arrays, but for some
// reason they aren't arrays. I wanted to extend the functionality of Array,
// so I have to copy such objects into a true array before using them.

// I could just copy all these new methods into NodeList, but IE doesn't give
// access to the prototypes for any built-in DOM objects.

function makeArray(indexed)
{
    var result = new Array(indexed.length);
    for (var i = 0; i < indexed.length; i++) {
        result[i] = indexed[i];
    }
    return result;
}

// TODO: understand the dynamic dispatch semantics of Javascript and make a
//       base List object for Array and String to derive from

Array.prototype.copy = function() {
    return makeArray(this);
};

Array.prototype.cons = function(x) {
    var result = this.copy();
    result.push(x);
    return result;
};

Array.prototype.first = function() {
    return this[0];
};

Array.prototype.rest = function() {
    var result = this.copy();
    result.pop();
    return result;
};

Array.prototype.foreach = function(f) {
    for (var i = 0; i < this.length; i++) {
        f(this[i]);
    }
    return this;
};

Array.prototype.filter = function(p) {
    var result = new Array();
    for (var i = 0, j = 0; i < this.length; i++) {
        if (p(this[i])) {
            result[j++] = this[i];
        }
    }
    return result;
};

Array.prototype.map = function(f) {
    var result = new Array(this.length);
    for (var i = 0; i < this.length; i++) {
        result[i] = f(this[i]);
    }
    return result;
};

Array.prototype.contains = function(e) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == e) {
             return true;
        }
    }
    return false;
};

Array.prototype.without = function(x) {
    return this.filter(function(y) { return (x != y); });
};

// ============================================================================
// String
// ============================================================================

/*

 interface String : Indexed {
     ...

     // new methods
     method toTitleCase : -> String
     method startsWith : String -> Boolean
     method endsWith : String -> Boolean
     method suffixOf : String -> String
     method prefixOf : String -> String
     method containsWord : String -> Boolean
     method removeWord : String -> String
     method addWord : String -> String
     method swapWords : String String -> String
     method getWords : -> Array
 }

*/

String.prototype.toTitleCase = function () {
    if (this.length == 0) return this;
    if (this.length == 1) return this.toUpperCase();
    var words = this.getWords().map(function (w) {
        return w[0].toTitleCase() + w.substring(1);
    });
    return words.join('');
};
    
String.prototype.startsWith = function(prefix) {
    if (this.length < prefix.length) return false;
    return (this.substr(0, prefix.length) == prefix);
};
 
String.prototype.endsWith = function(suffix) {
    if (this.length < suffix.length) return false;
    return (this.substring(this.length - suffix.length) == suffix);
};

String.prototype.suffixOf = function(s) {
    var i = this.indexOf(s);
    return (i == -1 ? "" : this.substring(i + s.length));
};

String.prototype.prefixOf = function(s) {
    var i = this.indexOf(s);
    return (i == -1 ? "" : this.substring(0, i));
};

String.prototype.containsWord = function(word) {
    return this.getWords().contains(word);
};

String.prototype.removeWord = function(word) {
    return this.getWords().without(word).join(' ');
};

String.prototype.addWord = function(word) {
    if (this.containsWord(word))
        return this;
    else
        return this.getWords().cons(word).join(' ');
};

String.prototype.swapWords = function(word1, word2) {
    function swap(w) {
        if (w == word1) return word2;
        else if (w == word2) return word1;
        else return w;
    }
    return this.getWords().map(swap).join(' ');
}

String.prototype.getWords = function() {
    return this.split(/\W+/);
};

//function wordRegExp(word) {
//    return new RegExp('(^|\\W+)' + word + '(\\W+|$)', 'g');
//}


// ============================================================================
// Namespace
// ============================================================================

var global = this;

var Namespace = {
    declare: function() {
        var prefix = global;
        for (var i = 0; i < arguments.length; i++) {
            var pieces = arguments[i].split('.');
            for (var j = 0; j < pieces.length; j++) {
                // TODO: if it exists but it's a non-object, error
                if (prefix[pieces[j]] == undefined) {
                    prefix[pieces[j]] = {};
                }
                prefix = prefix[pieces[j]];
            }
        }
    }
};
