/**
 * Class Element
 * 
 * Class responsible for the HTML elements handle
 */

TrueWizard.Element = new TrueWizard.Class(function()
{
    /**
     * HTMLElement to be manipulated
     */
    this.element = null;

    /**
     * Class initializator
     * Store in the instance as the last element parameter
     * If the parameter is of type String, is trying to get the item using getElementById
     *
     * @param mixed HTMLElement | String identifier = The HTML object that is sought
     *                                                or id, so it can be obtained
     *
     * @return Object TrueWizard.Element
     */
    this.init = function( identifier )
    {
        this.pressReplace = [];

        if ( identifier.nodeType ) {
            this.element = identifier;
        }
        else {
            this.element = document.getElementById( identifier ) || null;
        }

        return this;
    };

    /**
     * He adds an event to the element previously stored in the instance
     *
     * @param  String type       = The type of event that will be noticed
     * @param  Function callback = Function that will run at the shooting event
     *
     * @return Object TrueWizard.Element
     */

    this.addEvent = function( type, callback )
    {
        if ( null != this.element ) {

            var self = this;

            TrueWizard.addEvent( this.element, type, function( event )
            {
                // If the callback function returns FALSE
                // Break the event
                if ( callback.call( self, event ) === false ) {
                    if ( event.stopPropagation ) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                    else {
                        // Break the event in Internet Explorer
                        event.returnValue = !( event.cancelBubble = true );
                    }
                }
            });
        }

        return this;
    };

    /**
     * Retrieves the parent element of the tree of the document
     * and returns it in a new instance
     * 
     * If you specify the tagName, will continue looking up
     * until you find a parent for that tag
     *
     * @param  String tag = tagName of the element that will be returned
     * @return Object TrueWizard.Element | null
     */
    this.parent = function( tagName )
    {
        if ( null != this.element ) {

            var parent = this.element.parentNode;

            if ( tagName ) {
                while( parent.tagName.toLowerCase() != tagName.toLowerCase() ) {
                    parent = parent.parentNode;
                }
            }

            return new TrueWizard.Element( parent );
        }
        return null;
    };

    /**
     * Adds an element to the class saved in the instance
     *
     * @param  String Class = CSS class name
     * @return Object TrueWizard.Element
     */
    this.addClass = function( Class )
    {
        if ( null != this.element ) {

            var classes = this.element.className.split(' ');

            if ( classes.indexOf( Class ) == -1 ) {
                classes.push( Class );
            }

            this.element.className = classes.join(' ');
        }

        return this;
    };
    
    /**
     * Check if the item contains the class
     *
     * @param  String Class = Name of the CSS class that will be verified
     * @return Number | Boolean
     */

    this.hasClass = function( Class )
    {
        if ( null != this.element ) {
            var classes = this.element.className.split(' ');
            return classes.indexOf( Class ) != -1;
        }

        return false;
    };

    /**
     * Returns the first child node of type Element, and not textNode
     * @return Object TrueWizard.Element | void
     */
    this.first = function()
    {
        if ( null != this.element ) {
            var first = this.element.firstChild;
            while ( null != first && first.nodeType != 1 ) {
                first = first.nextSibling;
            }
            return new TrueWizard.Element( first );
        }
    };


    /**
     * Return the next node of type element, and not textNode
     * @return Object TrueWizard.Element | void
     */
    this.next = function()
    {
        if ( null != this.element ) {
            var next = this.element.nextSibling;
            while ( null != next && next.nodeType != 1 ) {
                next = next.nextSibling;
            }
            return new TrueWizard.Element( next );
        }
    };

    /**
     * Removes all children of the element nodes stored in the instance
     *
     * @param  Function callback = function that is used as a filter
     * @return Object TrueWizard.Element
     */
    this.empty = function( callback )
    {
        if ( null != this.element ) {
            while ( this.element.lastChild ) {
                if ( callback && callback.call( null, this.element.childNodes.length ) === false ) {
                    break;
                }
                this.element.removeChild( this.element.lastChild );
            }
        }
        return this;
    };

    /**
     * Adds a child node to the element, using appendChild
     * If you specify "brother", is used insertBefore
     *
     * @param  HTMLElement child   = The new node to add
     * @param  HTMLElement brother = The existing node brother
     * @return Object TrueWizard.Element
     */
    this.append = function( child, brother )
    {
        if ( null != this.element ) {
            if ( typeof brother != 'undefined' ) {
                this.element.insertBefore( child, brother )
            }
            else  {
                if ( child.constructor == String ) {
                    this.element.appendChild( document.createTextNode( child ) );
                }
                else if ( child.nodeType ) {
                    this.element.appendChild( child );
                }
            }
        }

        return this;
    };

    /**
     * Takes a class to the element stored in the body
     *
     * @param  String Class = CSS class name
     * @return Object TrueWizard.Element
     */
    this.removeClass = function( Class )
    {
        if ( null != this.element ) {
            var classes = this.element.className.split(' '), index;

            if ( ( index = classes.indexOf( Class ) ) != -1 ) {
                classes.splice( index, 1 );
            }

            this.element.className = classes.join(' ');
        }

        return this;
    };

    /**
     * Privated method, performs animation to set the dimensions of the element
     * @param  String               mode = + or -, to indicate if the animation is growing or decreasing
     * @param  String               prop = height or width, for set css property
     * @param  HTMLElement       element = element to be animated
     * @param  Number            current = current height or width of the element
     * @param  Number                end = the end value of the property
     * @param  Function         callback = callback will be executed when animation is complete
     * @param  Object TrueWizard.Element = class instance
     * @return void
     */
    var animate = function( mode, prop, element, current, end, callback, instance )
    {
        if ( mode == '+' ) {
            current += 5;
        }
        else {
            current -= 5;
        }

        if ( ( current < end && mode == '+' ) || ( current > end && mode == '-' ) ) {
            setTimeout(function(){
                animate( mode, prop, element, current, end, callback, instance );
            }, 10);
        }
        else {
            current = end;
        }

        element.style[ prop ] = current + 'px';

        if ( current == end ) {
            callback.call( instance );
        }
    };

    /**
     * Set the width of the element, through a linear animation
     *
     * @param  int         width = The new width of the element
     * @param  Function callback = according to which should be known when the animation is completed
     * @return Object TrueWizard.Element
     */
    this.width = function( width, callback )
    {
        if ( null != this.element ) {
            var current = this.element.offsetWidth;
            var mode    = current < width ? '+' : '-';

            animate( mode, 'width', this.element, current, width, callback, this );
        }

        return this;
    };
    

    /**
     * Stored in an array of regular expression and the replacement callback
     * It applies to the method Accept
     *
     * @param RegExp expr        = Regular expression that must agree to the replacement
     * @param Function fnReplace = The value will be replaced by what this function returns
     * @return Object TrueWizard.Element
     */
    this.Replace = function( expr, fnReplace )
    {
        this.pressReplace.push( [expr, fnReplace] );
        return this;
    };

    /**
     * Adds an event "keypress" to the element
     * and not allowed to enter values that are not permitted
     *
     * @param  RegExp expr = Regular expression that was used to test
     * @return Object TrueWizard.Element
     */

    this.Accept = function( expr )
    {
        this.addEvent('keypress', function( event )
        {
            var code = document.all || window.opera ? event.keyCode : event.charCode;

            if ( 0 == code || event.ctrlKey ) {
                return;
            }

            var sChar = String.fromCharCode( code );

            if ( sChar.length > 0 ) {

                if ( document.selection ) {
                    var range     = document.selection.createRange();
                    var startPos  = Math.abs( range.duplicate().moveStart('character', -1000000) );
                    var endPos    = startPos + range.text.length;
                }
                else {
                    var startPos  = this.element.selectionStart;
                    var endPos    = this.element.selectionEnd;
                }

                var inRange       = false;
                var originalValue = this.element.value;

                if ( startPos != originalValue.length ) {

                    var selected = originalValue.substring( startPos, endPos );

                    if ( selected.length > 0 ) {
                        var value = originalValue.replace( selected, sChar );
                    }
                    else {
                        var value = originalValue.substr( 0, startPos )  + '' + sChar + '' + originalValue.substr( startPos );
                    }

                    inRange = true;
                }
                else {
                    var value = originalValue + '' + sChar;
                }

                var exp = new RegExp( expr );

                if ( exp.test( value ) ) {

                    // Performs replacements
                   if ( this.pressReplace.length > 0 ) {
                        for ( var i = 0, len = this.pressReplace.length; i < len; i++ ) {
                            value = value.replace( this.pressReplace[ i ][ 0 ], this.pressReplace[ i ][ 1 ] );
                        }

                        this.element.value = value;

                        if ( inRange ) {

                            if ( document.selection ) {
                                range.move('character', startPos + 1 );
                                range.moveEnd('character', 0 );
                                range.select();
                            }

                            this.element.selectionStart = startPos + 1;
                            this.element.selectionEnd   = startPos + 1;
                        }
                        return false;
                    }
                    return true;
                }
                return false;
            }
        });

        return this;
    };

    /**
     * Returns the number of child nodes
     * @return Number | Boolean
     */
    this.hasChildren = function()
    {
        if ( null != this.element ) {
            return this.element.childNodes.length;
        }

        return false;
    };

    /**
     * If you are not passing any argument gets the value of the item and return it
     * But it seven this value to property "value"
     *
     * @param  String val = the new value of the element
     * @return String
     */
    this.value = function( val )
    {
        if ( null != this.element ) {

            // ToDo: make support more types of elements, not only input's
            if ( typeof val != 'undefined' ) {
                this.element.value = val;
                return this;
            }

            return TrueWizard.collectFields.getValue( this.element );
        }

        return '';
    };

    /**
     * Remove the element of the DOM ( Document Object Model )
     * @return Object TrueWizard.Element
     */
    this.remove = function()
    {
        if ( null != this.element ) {
            this.element.parentNode.removeChild( this.element );
        }
        return this;
    };
});