//////////////////////////////////////////////////////////////////////////////
//	jax.js
//
//	Description:
//		Javasscript Application Extensions
//////////////////////////////////////////////////////////////////////////////

var Jax = {};

//////////////////////////////////////////////////////////////////////////////
// js class
//
// Defines javascript language extensions.
//////////////////////////////////////////////////////////////////////////////
Jax.js = new function() {

    this.isArray = function(obj) {
        return this.isObject(obj) && obj.constructor == Array;
    };

    this.isBoolean = function(obj) {
        return typeof(obj) === 'boolean';
    };

    this.isDefined = function(obj) {
        return typeof(obj) !== 'undefined';
    };

    this.isDelegate = function(delegate) {
        return delegate.$className === "Delegate";
    };

    this.isEmpty = function(obj) {
        if (this.isObject(obj)) {
            for (var item in obj) {
                return false;
            }
        }
        return true;
    };

    this.isFunction = function(obj) {
        return typeof(obj) === 'function';
    };

    this.isInternal = function(obj) {
        return this.isString(obj) && obj.charAt(0) === '$';
    };

    this.isNull = function(obj) {
        return typeof(obj) === 'undefined' || (obj == null);
    };

    this.isNumber = function(obj) {
        return typeof(obj) === 'number' && isFinite(obj);
    };

    this.isObject = function(obj) {
        return typeof(obj) === 'object';
    };

    this.isRegExp = function(obj) {
        return obj.constructor == RegExp;
    };

    this.isString = function(obj) {
        return typeof(obj) === 'string';
    };
    
    this.functionName = function(func) {
        var funcName = null;
        if (this.isFunction(func)) {
            var strFunc = func.toString();
            var reFuncName = new RegExp(/function\s+(\w+).*/);
            var result = reFuncName.exec(strFunc);
            funcName = result[1];
        }
        return funcName;
    };

    this.toFunction = function(obj) {
        return (this.isObject(obj)) ? obj.constructor : this.isFunction(obj) ? obj : null;
    };
    
    this.entry = function() {
        if (typeof(Main) !== 'undefined') {
            Main();
        }
    };
};    

//////////////////////////////////////////////////////////////////////////////
// oop class
//
// Defines extension method to support oop-like classes.
//////////////////////////////////////////////////////////////////////////////
Jax.oop = new function() {

    this.declare = function(clazz, base)
    {
        if (Jax.oop.isDefined(clazz))
            return;
        
        // Inherit from base classes.
        if (base) {
            clazz.prototype = new base();
            clazz.prototype.constructor = clazz;
            clazz.$baseClass = base;
        }
        else {
            clazz.$baseClass = Object;
            clazz.$baseClass.$className = "Object";
        }
        
        // Initialize the class and return.
        clazz.$className =  this.getName(clazz);
    };
    
    this.isDefined = function(clazz) {
        return clazz.$baseClass != null;
    };
    
    this.instanceOf = function(clazz) {
        var thisName = this.getClassName();
        var targName = Jax.js.isString(clazz) ? clazz : clazz.prototype.getClassName();
        var retval = false;
    
        // Find instance of.
        if (thisName === targName)
            retval = true;
        else if (targName === "Object")
            retval = true;
        else if (clazz.getBaseClass() == null)
            retval = false;
        else 
            retval = clazz.getBaseClass().instanceOf(targName);
            
        return retval;
    };
    
    this.getName = function(clazz) {
        var name;
        if (Jax.js.isFunction(clazz)) {
            var reName = new RegExp(/Jax.oop.declare\(([\w\.]+)[,|\)]/);
            var result = reName.exec(clazz.toString());
            if (result == null) {
                reName = new RegExp(/function\s+(\w+).*/g);
                result = reName.exec(clazz.toString());
                if (result == null) {
                    throw new Error("cannot reference undeclared class");
                }
            }
            name = result[1];
        }
        return name;
    };
    
    //////////////////////////////////////////////////////////////////////////////
    //  reflection namespace.
    //////////////////////////////////////////////////////////////////////////////
    this.reflection = new function() {
    
        this.getAttributes = function(obj) {
            var results = new Array();
            for (var name in obj) {
                if (!Jax.js.isFunction(obj[name])) {
                    var firstChar = name.charAt(0);
                    if (firstChar !== '$' && firstChar !== '_') 
                        results.push(name);
                }
            }
            return results;
        };
    
        this.getMethods = function(obj) {
            var results = new Array();
            var clazz = Jax.js.toFunction(obj);
            for (var name in clazz.prototype) {
                if (Jax.js.isFunction(clazz.prototype[name]))
                    results.push(name);
            }
            return results;
        };
        
        this.getMembers = function(obj) {
            return this.getAttributes(obj).concat(this.getMethods(obj));
        };
    };
};

//////////////////////////////////////////////////////////////////////////////
//	Dom
//
//	// Browser identity function.
//	IsIE();
//	IsNetscape();
//	IsMozilla();
//	
//	// Document form function.
//	GetForm();
//
//	// Document element functions.
//	GetElementById();
//	GetElementTerms();
//
//	// Document image functions.
//	FindImages();
//	GetImage();
//
//////////////////////////////////////////////////////////////////////////////
Jax.dom = new function() {
    this.isIE = function() {
        var isApp = navigator.appName.indexOf("Microsoft") != -1;
        var isVersion = (arguments.length) ?
                         navigator.appVersion.substring(0, 1).valueOf() >= arguments[0]
                         : true;
        return isApp && isVersion;
    };

    this.isMozilla = function() {
        var isApp = this.IsNetscape() && !document.all && document.getElementById;
        var isVersion = (arguments.length) ?
                         navigator.appVersion.substring(0, 1).valueOf() >= arguments[0]
                         : true;
        return isApp && isVersion;
    };

    this.isNetscape = function() {
        var isApp = navigator.appName === "Netscape";
        var isVersion = ( arguments.length ) ?
                         navigator.appVersion.substring(0, 1).valueOf() >= arguments[0]
                         : true;
        return isApp && isVersion;
    };

    //////////////////////////////////////////////////////////////////////////////
    //	getElementById(id)
    //////////////////////////////////////////////////////////////////////////////
    this.getElementById = function(id) {
        return document.getElementById(id);
    };

    //////////////////////////////////////////////////////////////////////////////
    //	GetElementTerms
    //	
    //	Usage:
    //		GetElementTerms( argElement )
    //		GetElementTerms( argElement, splitChar )
    //////////////////////////////////////////////////////////////////////////////
    this.getElementTerms = function(argElement, splitChar) {
        var elementId = null;
        var elementTerms = null;
        var splitCharDefault = "_";

        // Assume default split character is underscore;
        if (!splitChar) splitChar = splitCharDefault;

        if (Jax.js.IsObject(argElement))
            elementId = argElement.id;
        else if (Jax.js.IsString(argElement))
            elementId = argElement;
        else
            throw new Error("invalid element parameter");

        // Perform string split if split character is not underscore.
        if (splitChar !== "_")
            return thisElement.id.split(splitChar);
            
        // Split the element id with underscore as split character.
        while (elementId !== "") {
            var results = elementId.match(/(_?[a-zA-Z\d]+_?)(\d+_?)?/);
            if (results != null) {
                var term = results[0];
                var termLen = term.length;
                if (term.charAt(termLen - 1) === "_" )
                    term = term.slice(0, -1);

                if (elementTerms == null)
                    elementTerms = new Array();

                elementTerms[elementTerms.length] = term;
                elementId = elementId.substr(termLen);
            }
        }

        return elementTerms;
    };

    //////////////////////////////////////////////////////////////////////////////
    //	getImage( imageId )
    //////////////////////////////////////////////////////////////////////////////
    this.getImage = function(imageId) {
        var image;
        try { 
            image = document.images[imageId];
        }
        catch (e) {
            image = null;
        }
        return image;
    };

    //////////////////////////////////////////////////////////////////////////////
    //	getForm( argForm )
    //////////////////////////////////////////////////////////////////////////////
    this.getForm = function(argForm) {
        var theForm = null;
        if (!argForm || argForm == null) {
            theForm = document.forms[0];
        }
        else if (this.isForm(argForm)) {
            theForm = argForm;
        }
        else if (Jax.js.IsNumber(argForm)) {
            theForm = document.forms[argForm];
        }
        else if (Jax.js.IsString(argForm)) {
            document.forms.forEach(function(form) {
                if (form.id == argForm) {
                    theForm = form;
                    return true;
                }
            });
        }

        // Return result.
        return theForm;
    };

    //////////////////////////////////////////////////////////////////////////////
    //	isForm( object )
    //////////////////////////////////////////////////////////////////////////////
    this.isForm = function(obj) {
        return Jax.js.isObject(obj) && (obj.tagName && obj.tagName === "FORM");
    };
};		

//////////////////////////////////////////////////////////////////////////////
// Function extensions.
//////////////////////////////////////////////////////////////////////////////
Function.prototype.$className = "Function";
Function.prototype.$baseClass = null;
Function.prototype.instanceOf = Jax.oop.instanceOf;
Function.prototype.reflection = Jax.oop.reflection;

Function.prototype.getBaseClass = function() { return this.$baseClass; };
Function.prototype.getClassName = function() { return this.$className; };
Function.prototype.isDefined = function()	{ return Jax.oop.isDefined(this); };
Function.prototype.getCallback = function() { return Jax.js.isDelegate(this) ? this.invoke : this; };
Function.prototype.sequence = function(g) {
    var f = this;
    return function() { 
        f.apply(this, arguments);
        g.apply(this, arguments);
    };
};

//////////////////////////////////////////////////////////////////////////////
// Object extensions.
//////////////////////////////////////////////////////////////////////////////
Object.prototype.$className = "Object";
Object.prototype.$baseClass = null;
Object.prototype.getBaseClass = Function.getBaseClass;
Object.prototype.getClassName = Function.getClassName;
Object.prototype.instanceOf = Function.instanceOf;

Object.prototype.setInstance = function() {

    // Get the caller.
    var clazz = Object.setInstance.caller;
    
    // Set instance base.
    if (this.$baseClass == null)
        this.$baseClass = clazz.$baseClass;
        
    // Set up base properties.
    clazz.$baseClass.call(this);
    
    // Set instance class name.	
    this.$className = clazz.$className;
};

Object.prototype.setMethod = function(func) {
    this[Jax.js.functionName(func)] = func;
};

//////////////////////////////////////////////////////////////////////////////
// Array extensions.
//////////////////////////////////////////////////////////////////////////////
Array.prototype.$className = "Array";
Array.prototype.$baseClass = Object;

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

Array.prototype.forEach = function(lambda) {
    for (var ii = 0; ii < this.length; ++ii) {
        if (lambda(this[ii], ii))
            break;
    }
};

Array.prototype.isEmpty = function() {
    return this.length == 0;
};

Array.prototype.remove = function() {
    if (arguments.length > 0) {
        var items = Jax.js.isArray(arguments[0]) ? arguments[0] : arguments;
        for (ii = 0; ii < items.length; ++ii) {
            for (jj = 0; this.length; ++jj) {
                if (this[jj] == items[ii]) {
                    this.splice(jj, 1);
                    break;
                }
            }
        }
    }
};

Array.prototype.removeAt = function(index) {
    this.splice(index, 1);
};

//////////////////////////////////////////////////////////////////////////////
// Date extensions.
//////////////////////////////////////////////////////////////////////////////
Date.prototype.$className = "Date";
Date.prototype.$baseClass = Object;
Date.prototype.$monthNames = 
    ["January", "February", "March", "April", "May", "June",
     "July", "August", "September", "October", "November", "December"];
   
Date.prototype.$monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

Date.prototype.$weekdayNames = 
    ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

Date.prototype.isLeapYear = function(year) {
    year = (year) ? year : this.getYear();
    return ((year % 400) === 0) ? true : 
           ((year % 100) === 0) ? false :
           ((year % 4)   === 0) ? true : false;
}

Date.prototype.getMonthFullName = function(month) {
    month = ( month ) ? month : this.getMonth();
    return this.$monthNames[month]; 
}

Date.prototype.getMonthShortName = function(month) {
    month = (month) ? month : this.getMonth();
    return this.getMonthFullName(month).substring(0, 3);
}

Date.prototype.getDaysInMonth = function(month, year) {
    month = (month) ? month : this.getMonth();
    year = (year) ? year : this.getYear();
    var leap = ((month === 1) && this.isLeapYear(year)) ? 1 : 0;
    return this.$monthDays[month] + leap;
}

Date.prototype.getWeekdayFullName = function(weekday) {
    weekday = (weekday) ? weekday : this.getDay();
    return this.$weekdayNames[weekday]; 
}

Date.prototype.getWeekdayShortName = function(weekday) {
    weekday = (weekday) ? weekday : this.getDay();
    return this.getWeekdayFullName(weekday).substring(0, 3);
}

//////////////////////////////////////////////////////////////////////////////
// Error extensions.
//////////////////////////////////////////////////////////////////////////////
Error.prototype.$className = "Error";
Error.prototype.$baseClass = Object;
Error.prototype.target = null;

//////////////////////////////////////////////////////////////////////////////
// RegExp extensions.
//////////////////////////////////////////////////////////////////////////////
RegExp.prototype.$className = "RegExp";
RegExp.prototype.$baseClass = Object;

//////////////////////////////////////////////////////////////////////////////
// String extensions.
//////////////////////////////////////////////////////////////////////////////
String.prototype.$className = "String";
String.prototype.$baseClass = Object;
String.prototype.trim = function() {
    return this.replace(/^\s+/, "").replace(/\s+$/, "");
};
String.prototype.isEmpty = function() {
    return !Jax.js.isDefined(this) || this == null || this == "";
};

//////////////////////////////////////////////////////////////////////////////
// Delegate class.
//////////////////////////////////////////////////////////////////////////////
Jax.oop.declare(Delegate, Function);

function Delegate(obj, callback) {
    if (!Jax.js.isObject(obj) || !Jax.js.isFunction(callback))
        throw new Error("Invalid Delegate arguments");

    var	m_obj = obj;
    var m_callback = callback;
    
    this.invoke = function() {
        if (m_obj && m_callback)
            return 	(arguments)
                    ? m_callback.apply(m_obj, arguments)
                    : m_callback.apply(m_obj);
    };

    this.attach = function(delegate) {
        if (Jax.js.isDelegate(delegate))
            m_callback = m_callback.sequence(delegate);
        else
            throw new Error("Argument is not a delegate");
    };

    // Make Invoke function a Delegate type.
    this.invoke.$className = "Delegate";
    this.invoke.$baseClass = Function;
    this.invoke.invoke = this.invoke;
    this.invoke.attach = this.attach;

    // Return the invocation method as the Delegate object.
    return this.invoke;
}

//////////////////////////////////////////////////////////////////////////////
// Hashtable.
//////////////////////////////////////////////////////////////////////////////
function Hashtable() {
    var m_dictionary = [];
    var m_list = [];
    
    Jax.oop.declare(Hashtable);
    this.setInstance();
       
    this.initialize = function(args) {
        for (var ii = 0; ii < args.length; ++ii) {
                m_list.push(args[ii][1]);
                m_dictionary[args[ii][0]] = args[ii][1];
        }
    };
    
    this.contains = function(value) {
        for (var ii = 0; ii < m_list.length; ++ii) {
            if (item == value) {
                return true;
            }
        }
        return false;
    };
    
    this.count = function() {
        return m_list.length;
    }
    
    this.forEach = function(lambda) {
        for (var ii = 0; ii < m_list.length; ++ii) {
            if (lambda(m_list[ii], ii))
                break;
        }
    };
    
    this.isEmpty = function() {
        return this.count() == 0;
    }
    
    this.getItem = function(key) {
        check(key);
        return m_dictionary[key];
    };
    
    this.setItem = function(key, value) {
        check(key);
        if (Jax.js.isDefined(value)) {
            if (!Jax.js.isDefined(m_dictionary[key])) {
                m_list.push(value);
            }
            m_dictionary[key] = value;
        }
    };
    
    this.itemAt = function(index) {
        return m_list[index];
    }
    
    this.removeItem = function(key) {
        check(key);
        m_list.remove(m_dictionary[key])
        if (!Jax.js.isDefined(m_dictionary[key])) {
            delete m_dictionary[key];
        }
    };
    
    //////////////////////////////////////////////////////////////////////////////
    // check(key)
    //////////////////////////////////////////////////////////////////////////////
    function check(key) {
        if (Jax.js.isNull(key)) {
            throw new Error("invalid key");
        }
    }   
    
    //////////////////////////////////////////////////////////////////////////////
    // initialize object.
    //////////////////////////////////////////////////////////////////////////////
    this.initialize(arguments);
}

//////////////////////////////////////////////////////////////////////////////
// Exception.
//////////////////////////////////////////////////////////////////////////////
function Exception() {
    Jax.oop.declare(Exception, Error);
    this.setInstance();
    
    for (var ii = 0; ii < arguments.length; ++ii) {
        if (Jax.js.isString(arguments[ii])) {
            if (!this.message) {
                this.message = arguments[ii];
            }
        }
        else if (arguments[ii].instanceOf(Error)) {
            var e = arguments[ii];
            for (item in e) {
                if (typeof(this[item]) === 'undefined') {
                    this[item] = e[item];
                }
            }
        }
        else if (Jax.js.isObject(arguments[ii])) {
            this.target = arguments[ii];
        }
        else if (Jax.js.isNumber(arguments[ii])) {
            this.number = arguments[ii];
        }
    }
}
