/**
 * Very simple and little replacement for prototype.js. If you do not need
 * too advanced capabilities, you can use proto instead of prototype.
 *
 * Go to http://kolonist.ru/ for new versions.
 *
 * @author Alexander N Zubakov <alexander.zubakov@gmail.com>
 * @copyright © 2007-2008 Alexander N Zubakov
 * @version 2.0
 */


//**************************************************************************
//                              Functions
//**************************************************************************
/**
 * Returns element by id. If element passed by itself, then it is returned
 * itself.
 *
 * @param mixed id ID of the element to get
 * @return Object Element with given ID
 */
function $(id) {
    if (typeof id == 'string') {
        id = document.getElementById(id);
    }

    return id;
}

/**
 * Equal to $() function, but before return this function extends
 * HTMLElements using xElement object.
 *
 * @param mixed id ID of the element to get.
 * @return Object Extended HTMLElement with given ID.
 */
function $X(id) {
    id = $(id);

    //extend functionality of HTMLElement
    extend_object(id, xElement);

    return id;
}

/**
 * Returns value of the element if there is one.
 *
 * @param mixed id ID of the element which value to get.
 * @return string Value of the element.
 */
function $V(id) {
    return $(id).value;
}

/**
 * Gets cookie.
 *
 * @param string name Name of cookie variable to get.
 * @return string Value of the variable we got.
 */
function getcookie(name) {

    //get all variables stored in the cookie
    var aCookie = document.cookie.split(';');

    //search variable we need
    for (var i = 0; i < aCookie.length; i++) {

        //remove spaces
        while(aCookie[i][0] == ' ') {
            aCookie[i] = aCookie[i].substr(1);
        }

        //get array [name, value]
        var aCrumb = aCookie[i].split('=');

        //if name is the same we are looking for, then return it
        if (name == aCrumb[0]) {
            return unescape(aCrumb[1]);
        }
    }

    //no variable with given name found
    return null;
}

/**
 * Sets cookie with given name and value.
 *
 * @param string name Name of cookie variable.
 * @param string value Value of variable.
 * @param string expires Number of seconds cookie will be alive.
 * @param string path Server path to reduce visibility scope.
 * @param string domain Domain name for cookie.
 * @param string secure Secure.
 */
function setcookie(name, value, expires, path, domain, secure) {

    //define expires time
    var today = new Date();
    var expires_date = new Date(today.getTime() + (expires * 1000));

    //set cookie
    document.cookie =
            name + '=' + escape(value) +
            (expires ? ';expires=' + expires_date.toUTCString() : '') +
            (path    ? ';path=' + path : '' ) +
            (domain  ? ';domain=' + domain : '' ) +
            (secure  ? ';secure' : '' );
}

/**
 * Delete cookie. Use exactly the same parameters you used while cookie
 * creation.
 *
 * @param string name Name of cookie variable.
 * @param string path Server path.
 * @param string domain Domain name for cookie.
 */
function deletecookie(name, path, domain) {

    //make cookie expire 1 second ago
    setcookie(name, '', -1, path, domain);
}

/**
 * Function provides extending functionality for objects. Note that function
 * extends objects only and not their prototypes, so it's not real inheritance
 * implimentation but it is just adding new methods and fields to object.
 *
 * @param object target Object to extend.
 * @param object source Object used ad source of fields. All fields of the
 *                      source will be added to target.
 * @return object Extended target object.
 */
function extend_object(target, source) {
    for (field in source) {
        target[field] = source[field];
    }

    return target;
}


//**************************************************************************
//                                AJAX
//**************************************************************************
/**
 * Object to handle AJAX connections
 */
function Ajax() {

    /**
     * Perform AJAX request.
     *
     * @param string url URI where to send request.
     * @param Function ondata Callback function which will be called when data
     *                        came. Function can have 2 optional parameters:
     *                        XMLHttpRequest object with data from server and
     *                        returned from server status code.
     * @param object parameters Object with fields, which represent data to send.
     * @param string method Method to use while data sending. Can be POST or GET.
     * @param string async Async flag. If set to true (default), data will be
     *                     sent asynchronously, if set to false, then data will
     *                     be sent synchronously.
     */
    this.request = function(url, ondata, parameters /*[, method [, async]]*/) {

        //create XMLHttpRequest object
        var ajaxObj;
        try {
            ajaxObj = new XMLHttpRequest();
        } catch(e) {
            try {
                ajaxObj = new ActiveXObject('Msxml2.XMLHTTP');
            } catch(e) {
                ajaxObj = new ActiveXObject('Microsoft.XMLHTTP');
            }
        }


        //when we get data, we need to call callback function
        ajaxObj.onreadystatechange = function() {
            if (ajaxObj.readyState == 4) {
                ondata(ajaxObj, ajaxObj.status);
            }
        };


        //form query string
        var query = this.toQueryString(parameters);


        //assign method to use while sendong
        var method = 'POST';
        if (arguments.length >= 4) {
            method = arguments[3];
        }


        //assign asynch flag
        var asynch = true;
        if (arguments.length >= 5) {
            asynch = arguments[4];
        }


        //open connection and send data
        ajaxObj.open(method, url, asynch);
        ajaxObj.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        ajaxObj.send(query);
    },

    /**
     * Send form to the server through AJAX.
     *
     * @param string id ID of form to send.
     * @param Function ondata Callback function which will be called when data
     *                        came. Function can have 2 optional parameters:
     *                        XMLHttpRequest object and status code as in the
     *                        request() method.
     */
    this.sendform = function(id, ondata) {

        //get parameters we need
        var url = $(id).action;
        var method = $(id).method;


        //collect data to send
        var parameters = {};
        for (var i = 0; i < $(id).length; i++) {

            //simplify work by defining element variable
            var el = $(id).elements[i];

            //get type of the element and perform proper action
            switch (el.type) {
                case 'select-multiple':

                    //view option elements and add selected to the query string
                    for (var j = 0; j < el.length; j++) {
                        var opt = el.options[j];

                        if (opt.selected) {
                            this._assign(parameters, el.name, $V(opt));
                        }
                    }
                break;

                case 'radio':
                case 'checkbox':

                    //if box is checked, then send it, otherwise, do not send
                    if (!el.checked) {
                        break;
                    }

                case 'text':
                case 'password':
                case 'hidden':
                case 'textarea':
                case 'select-one':
                    this._assign(parameters, el.name, $V(el));
                break;

                default:
                    //probably error, do nothing
                break;
            }
        }


        //perforn request
        this.request(url, ondata, parameters, method);
    }

    /**
     * This private function just assigns variable to parameters object. Never
     * call it manually.
     *
     * @param object parameters Object to wich variable will be assigned.
     * @param string name Name of the new variable.
     * @param mixed value Value of the variable to add.
     */
    this._assign = function(parameters, name, value) {

        //there is no such parameter yet
        if (!parameters[name]) {

            //assign new variable
            parameters[name] = value;

        //there is parameter with this name and it is array
        } else if (typeof parameters[name] == 'object') {

            //parameter already exists, append it
            parameters[name].push(value);

        //there is parameter with this name and it is not array yet
        } else {

            //parameter already exists, append it
            parameters[name] = [parameters[name], value];
        }
    }

    /**
     * Form query string from the fields of the object. Function can be called
     * statically.
     *
     * @param object parameters Object to convert to the query string.
     * @return string Resulting query-encoded string.
     */
    this.toQueryString = function(parameters) {

        //here will be query string
        var query = '';

        //inspect fields of the object
        for (var name in parameters) {

            //parameter is array, so we need to add every element
            if (typeof parameters[name] == 'object') {

                //add array to the query string
                for (var name_arr in parameters[name]) {

                    //add '&' after previous parameter
                    if (query !== '') {
                        query += '&';
                    }

                    //add variable to array
                    query += name + '=' + parameters[name][name_arr];
                }

            //parameter is string, add it
            } else {

                //add '&' after previous parameter
                if (query !== '') {
                    query += '&';
                }

                //add parameter to the query string
                query += name + '=' + encodeURIComponent(parameters[name]);
            }
        }

        return query;
    }
}


//**************************************************************************
//                                xElement
//**************************************************************************
/**
 * This object will be used to extend HTMLElements when call it throught $X()
 * function.
 */
var xElement = {
    /**
     * Hides element with given id. It uses display CSS property and sets it to
     * none.
     */
    hide: function() {

        //save previous display parameter to unhide element in the future
        if (this.style.display != '' && this.style.display != 'none') {
            this.prev_display = this.style.display;
        }

        //hides element
        this.style.display = 'none';
    },

    /**
     * Shows element with given id. If it was previously hidden by hide()
     * function, then its display property will be restored. Otherwise it will
     * be set to block. It does not touch visibility CSS property.
     */
    show: function() {
        if (this.prev_display) {
            this.style.display = this.prev_display;
        } else {
            this.style.display = 'block';
        }
    },

    /**
     * Tells about visibility of the element. Function tests only display
     * CSS property and it doesn't tests visibility property.
     *
     * @return boolean true if element is visible, false otherwise.
     */
    visible: function() {
        return this.style.display == 'none' ? false : true;
    },

    /**
     * Switches element visibility. If object is visible, it will be hidden,
     * if object was hidden before, it should became visible. Function uses
     * display CSS property, not visibility.
     *
     *
     * @return boolean true if element is visible after this function applied,
     *                 false if it became hidden after function.
     */
    toggle: function() {

        //change visibility
        if (this.visible()) {
            this.hide();
        } else {
            this.show();
        }

        //tell about visibility
        return this.visible();
    },

    /**
     * Sets position of the element. Uses CSS position, top and left properties.
     *
     * @param number x X coordinate of the top left corner.
     * @param number y Y coordinate of the top left corner.
     */
    move: function(x, y) {

        //make element to be absolutely positioned and set new coordinates
        this.style.position = 'absolute';
        this.style.top = x + 'px';
        this.style.left = y + 'px';
    },

    /**
     * Allocate HTMLElement on the center of the screen.
     */
    move_center: function() {

        //calculate top left corner coordinates
        var top = Math.round((document.documentElement.clientHeight - this.clientHeight) / 2);
        var left = Math.round((document.documentElement.clientWidth - this.clientWidth) / 2);

        //move to the center of the screen
        this.move(left, top);
    },

    /**
     * Makes object to react on events.
     *
     * @param string eventType Type of event to handle.
     * @param Function callback Function to call when event occurs.
     * @param boolean useCapture Flag shows if we need to capture event.
     * @return boolean true, if event is successfully set, false otherwise.
     */
    addEvent: function(eventType, callback, useCapture) {

        //set default value
        if (!useCapture) {
            useCapture = false;
        }

        //try to use DOM capability
        if (this.addEventListener) {
            this.addEventListener(eventType, callback, useCapture);
            return true;

        //use IE capability
        } else if (this.attachEvent) {
            return this.attachEvent('on' + eventType, callback);

        //just set onXXX function
        } else {
            this['on' + evType] = callback;
            return true;
        }
    },

    /**
     * Grabs elements by class name.
     *
     * @param string class_ Name of class
     * @return Array Collection of the elements which has class class_
     */
    getElementsByClass: function(class_) {

        //future collection of elements
        var classElements = [];

        //get children where to search
        var els = this.childNodes;
        var elsLen = els.length;

        //form pattern to search elements by class name
        var pattern = new RegExp('(^|\\s)' + class_ + '(\\s|$)');

        //view all children to match our pattern
        for (var i = 0; i < elsLen; i++) {
            if (els[i].className && pattern.test(els[i].className)) {
                classElements.push(els[i]);
            }
        }

        return classElements;
    },

    /**
     * Send form to the server through AJAX
     *
     * @param Function ondata Callback function which will be called when data
     *                        came. Function can have 2 optional parameters:
     *                        XMLHttpRequest object and status code
     */
    send: function(ondata) {
        new Ajax().sendform(this, ondata);
    }
};


//**************************************************************************
//                                String
//**************************************************************************
/**
 * Gets string as query and returns Object of pairs {name: value}.
 *
 * @param string name Name of the parameter to retrieve.
 * @return mixed {name: value} pairs as parameters, if name is not defined or
 *               string with parameter we requested
 */
String.prototype.query = function() {

    //get query part
    var query = this.substring(this.indexOf('?') + 1);
    query = query.split('#')[0];

    //get array of parameters as name=encodedValue
    var pairs_enc = query.split('&');

    //explode each parameter to pair {name: value} and add it to object pairs
    var pairs = {};
    for (var i = 0; i < pairs_enc.length; i++) {
        var param = pairs_enc[i].split('=');
        pairs[param[0]] = decodeURIComponent(param[1].replace(/\+/g, '%20'));
    }

    if (arguments.length > 0) {

        //return only one parameter value
        return pairs[arguments[0]];
    } else {

        //Return result as whole query
        return pairs;
    }
}

