

/**
  *
  *  Copyright 2005 Marc Bächinger, mbae@bcwin.ch
  *
  *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
  *  file except in compliance with the License. You may obtain a copy of the License at
  *
  *         http://www.apache.org/licenses/LICENSE-2.0
  *
  *  Unless required by applicable law or agreed to in writing, software distributed under the
  *  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  *  either express or implied. See the License for the specific language governing permissions
  *  and limitations under the License.
  **/


var AjaxController = Class.create();

    AjaxController.prototype = {


        /**
         * Bind the name of an action (eg 'search.person') to a url. An action is 
         * hardwired to an 
         *
         * @param actionName name of the action
         * @param url url to call when action is fired
         */
        bindActionToUrl: function( actionName, url ) {
            if ( actionName && url && this._isRegistered( actionName ) == null ) {
                this._bindActionToUrl( actionName, url );
            }
        },

        /**
         * Bind the name of an action (eg 'search.person') to a url. As additonal 
         * parameter this method takes a function which is called before the action
         * is fired. As a single argument the AjaxAction is passed to the interceptor
         * function. If the function returns 0 (zero) the action is NOT fired. It
         * the interceptor method itself which has to notify the user about the 
         * interception as ajapopaja will return quietly without doins something
         *
         * @param actionName name of the action
         * @param url url to call when action is fired
         * @param interceptor a function to be called BEFRoE action is fired
         */
        bindActionToUrlWithInterceptor: function( actionName, url, interceptor ) {
            if ( actionName && url ) {
                var registeredName = this._isRegistered( actionName );
                if ( registeredName != null ) {
                    // bind interceptor to name currently registered
                    registeredName._interceptor = interceptor;
                } else {
                    var actionKey = new Object();
                    // assign interceptor to new name
                    actionKey.name = actionName;
                    actionKey._interceptor = interceptor;
                    // bind new name to url
                    this._bindActionKeyToUrl( actionKey, url );
                }
            }
        },
        
        
        /**
         * fires an action
         */
        fireAction: function( ajaxAction ) {

            // try to get the key of the action
            var registeredActionKey = this._isRegistered( ajaxAction.name );
            if ( registeredActionKey == null ) {

                // if not registered yet, the controller
                // tries to auto-register the action
                this._registerAction( ajaxAction );
            } else if ( registeredActionKey._interceptor ) {

                // let interceptor veto if he whishes to
                if ( registeredActionKey._interceptor( ajaxAction ) == 0 ) {
                    //alert(  'interceptor returned 0. Exit action.' );
                    return;
                }
            } 

            // timestamp
            var sendDate = new Date();
            // create a hopefully unique token
            ajaxAction.token = this._createActionToken();
            // put the action into the pendingActions array
            this.pendingActions.push( ajaxAction );

            ajaxAction.addParam( "_a_token" , ajaxAction.token );
            ajaxAction.addParam( "_a_action" , ajaxAction.name );
            ajaxAction.addParam( "_a_senddate" , sendDate.toString() );

            // send the request by calling the rico ajaxEngine
            ajaxEngine.sendRequest( ajaxAction.name , ajaxAction.createQueryString() );
        },

 /**
         * fires an action
         */
        fireActionWithXml: function( ajaxAction ) {

            // try to get the key of the action
            var registeredActionKey = this._isRegistered( ajaxAction.name );
            if ( registeredActionKey == null ) {

                // if not registered yet, the controller
                // tries to auto-register the action
                this._registerAction( ajaxAction );
            } else if ( registeredActionKey._interceptor ) {

                // let interceptor veto if he whishes to
                if ( registeredActionKey._interceptor( ajaxAction ) == 0 ) {
                    //alert(  'interceptor returned 0. Exit action.' );
                    return;
                }
            } 

            // timestamp
            var sendDate = new Date();
            // create a hopefully unique token
            ajaxAction.token = this._createActionToken();
            // put the action into the pendingActions array
            this.pendingActions.push( ajaxAction );

            ajaxAction.addParam( "_a_token" , ajaxAction.token );
            ajaxAction.addParam( "_a_action" , ajaxAction.name );
            ajaxAction.addParam( "_a_senddate" , sendDate.toString() );

            // send the request by calling the rico ajaxEngine
            ajaxEngine.sendRequestWithData( ajaxAction.name , ajaxAction.xml );
        },
        
        fireActionToTargetFunction: function( ajaxAction , callBackFunction ) {
            ajaxAction._callback = callBackFunction;
            this.fireAction( ajaxAction );
        },
        
        fireActionIntoElement: function( ajaxAction , elementId ) {
            ajaxAction._targetElement = $(elementId);
            this.fireAction( ajaxAction );
        },

        fireActionWithXmlIntoElement: function( ajaxAction , elementId ) {
            ajaxAction._targetElement = $(elementId);
            this.fireActionWithXml( ajaxAction );
        },

        /**
         * called by the ajax engine on an incoming response
         */
        ajaxUpdate: function( xmlResponse ) {

            // get the header element
            var headers = xmlResponse.getElementsByTagName( "responseHeader" );
            if ( headers && headers.length > 0 ) {
                header = headers[0];
            
                // get the token
                var token = header.getAttribute( "token" );
                var action;
                for ( var i = 0; i < this.pendingActions.length;i++ ) {
                    if ( this.pendingActions[i].token = token ) {
                        action = this.pendingActions[i];
                        break;
                    }
                }

                if ( action ) {
                    // removing the action from pendingAction is a mess
                    var newPending = new Array();
                    for ( var i = 0; i < this.pendingActions.length;i++ ) {
                        if ( this.pendingActions[i].token != action.token ) {
                            newPending.push(this.pendingActions[i]);
                        }
                    }
                    // replace by array without current token action
                    this.pendingActions = newPending;

                    var response = new AjaxResponse();
                    response.setXmlResponse( xmlResponse );

                    if ( action._callback ) {
                        action._callback( action, response );
                    } else if (action._targetElement ) {
                        if ( response.isError() ) {
                            action._targetElement.innerHTML = "<div class='error-feedback'>" + response.getErrorMessage() + "</div>";
                        } else {
                            action._targetElement.innerHTML = response.getContentAsString();
                        }
                    } else {
                        alert ( "AjaxController: callback function or element must be defined" );
                    }
                }   
            } else {
                alert( "Kein Header gefunden" );
            }
        },


        /**
         * initializes an instance. Called at creation of an instance. Derived from the
         * prototype.js library Class construct.
         */
        initialize: function() {

            // if constructor is called with a 
            if ( arguments.length > 0 ) {
                this.controllerName = arguments[0];
            } else {
                this.controllerName = "ajaxController";
            }

            this.token = 1; // token to increase
            this.pendingActions = new Array(); // array of AjaxActions
            this.registeredActionNames = new Array(); // array of strings

            // register this controller as ajax object which will be notified 
            // by the ajaxEngine about an incoming response by calling 'ajaxUpdate(xmlResponse)'
            // this controller
            ajaxEngine.registerAjaxObject( this.controllerName , this );
        },

        /**
         * creates a unique token by which an action call could 
         * be identified later when the async response is received.
         *
         * @return a unique token
         */
        _createActionToken: function() {
            return this.token++;
        },

        /**
         * checks if the action is already has been bound to 
         * a url.
         *
         * @param ajaxAction the action
         * @return the actionKey or null
         */
        _isRegistered: function( ajaxActionName ) {   
            var actionKey = null; // false as default
            for ( var i = 0; i < this.registeredActionNames.length;i++ ) {
                if ( this.registeredActionNames[i].name == ajaxActionName ) {
                    actionKey = this.registeredActionNames[i];
                    break;
                }
            }
            return actionKey;
        },


        /**
         * tries to auto registers the action as an request into the ajaxEngine. 
         * 
         * Auto registering means that an action is bound to an url immedeatly
         * before it is fired. Usually binding is done at startup but this is 
         * not a must. To know the url to which the <code>AjaxAction</code> points
         * it must have a parameter with name <i>_a_url</i> assigned to it.
         *
         * @param ajaxAction the ajaxAction to bind to an url on-the-fly
         */
        _registerAction: function( ajaxAction ) {
            for ( var i = 0; i < ajaxAction.parameters.length;i++ ) {
                if ( ajaxAction.parameters[i].name = "_a_url" ) {
                    if ( ajaxAction.parameters[i].value ) {
                        this._bindActionToUrl( ajaxAction.name, ajaxAction.parameters[i].value );
                    }
                    break;
                }
            }
        },

        /**
         * This is an unchecked private method. Clients should use the publix 
         * functional equivalent 'bindActionToUrl'.
         *
         * Binds the name of an action (eg 'search.person') to a url. An action is 
         * hardwired to an. 
         */
        _bindActionToUrl: function( actionName, url ) {
            var actionKey = new Object();
            actionKey.name = actionName;
            this._bindActionKeyToUrl( actionKey, url );
        },

        /**
         * Binds an actionKey to an url. An actionKey is an object with 
         * at least an attribute name. There may bay assigned more attributes
         * or functions for advanced behaviours.
         * 
         * @param actionKey an object with a name attribute to be used as key
         * @param url the url to which the action points to
         */
        _bindActionKeyToUrl: function( actionKey, url ) {
            if ( actionKey.name ) {
                // register request to the ajaxEngine
                ajaxEngine.registerRequest( actionKey.name, url );
                // put actionKey to the registered actions for later lookup
                this.registeredActionNames.push( actionKey );
            }
        }
    };
        
var AjaxAction = Class.create();

    AjaxAction.prototype = {

        initialize: function() {
            this.parameters = new Array();
        },
        
        setName: function( name ) {
            this.name = name;
        },

        getName: function() {
            return this.name;
        },

        /**
         * creates a parameter from the name and value and adds it
         */
        addParam: function( paramName, paramValue ) {
            if ( paramName ) {
                var param = new AjaxParameter();
                param.setName( paramName );
                param.setValue( paramValue );
                this.addParameter( param );
             }
        },

        /**
         * adds a parameter object to the action
         */
        addParameter: function( parameter ) {
            this.parameters.push( parameter );
        },

        /**
         * creates a query string out ouf the current parameters
         * Format: ( 'name1=value1&name2=value2&...' ). The query
         * string is NOT URL ENCODED.
         * 
         * @return query string (not url encoded!!)
         */
        createQueryString: function() {
            var buf = "";
            for ( var i = 0; i < this.parameters.length;i++ ) {
                if ( i > 0 ) {
                    buf += "&";
                }
                buf += this.parameters[i].getPair();
            }
            return buf;
        }
    };

var AjaxResponse = Class.create();

    AjaxResponse.prototype = {

        initialize: function() {
            this.error = false;
            this.parameters = new Array();
        },

        /**
         * sets the raw xmlResponse as returned from the ajaxEngine
         */
        setXmlResponse: function( xmlResponse ) {

            // assign response
            this.xmlResponse = xmlResponse;

            // parses the response
            this._parseResponse();
        },

        /**
         * returns the raw xmlResponse from the ajaxEngine
         */
        getXmlResponse: function() {
            return this.xmlResponse;
        },

        /**
         * returns the content of the xmlResponse as String
         */
        getContentAsString: function() {
            if ( this.body ) {
                return RicoUtil.getContentAsString(this.body);
            } else if ( this.xmlResponse ) {
                return RicoUtil.getContentAsString(this.xmlResponse);
            } else {
                return "keine Antwort vefügbar";
            }
        },
        
        /**
         * returns the content of the xmlResponse as dom nodes
         */
        getContentAsNodes: function() {
            if ( this.body ) {
                return this.body.childNodes;
            } 
        },

        /**
         * get a reponse parameters value by name
         *
         * @param name the name of the param of interest
         */
        getParameter: function( name ) {
            for ( var i =0; i < this.parameters.length;i++ ) {
                if ( this.parameters[i].name == name ) {
                    return this.parameters[i].value;
                }
            }
        },

        /**
         * returns the token which relates an action to a response
         * 
         * @return the token created while firing the action which initiates this response
         */
        getToken: function() {
            return this.token;
        },

        /**
         * returns true if response has received an error
         */
        isError: function() {
            if ( this.error ) {
                return this.error;
            }
            return false;
        },

        /**
         * returns the error message if available
         */
         getErrorMessage: function() {
            if ( this.errorMessage ) {
                return this.errorMessage;
            } else {
                var msg;
                if ( this.xmlResponse ) {
                    var errorNodes = this.xmlResponse.getElementsByTagName( "errorMessage" );
                    if ( errorNodes && errorNodes.length > 0 ) {
                        msg = RicoUtil.getContentAsString( this.errorNodes[0] );
                    } else {
                        msg = "keine Fehlermeldung vorhanden";
                    }
                } else {
                    msg = "keine XML-Antwort vorhanden";
                }
                return msg;
            }      
         },

         /**
          * parses the response 
          */
         _parseResponse: function( ) {
            if ( this.xmlResponse ) {
                
                var errorElements = this.xmlResponse.getElementsByTagName( "errorMessage" ) ;
                if ( errorElements && errorElements.length > 0) {
                    this.error = true;
                    return;
                }

                var headers = this.xmlResponse.getElementsByTagName( "responseHeader" );
                if ( headers.length > 0 ) {
                    var header = headers[0];
                    
                    this.token = header.getAttribute( "token" );

                    var types = header.getElementsByTagName( "responseType" );
                    var parameters = header.getElementsByTagName( "parameter" );
                    for ( var i =0; i < parameters.length;i++ ) {
                        var param = new AjaxParameter();
                        param.setName( parameters[i].getAttribute( "name" ) );
                        param.setValue( parameters[i].getAttribute( "value" ) );
                        this.parameters.push( param );
                    }
                } else {
                    this.errorMessage = "kein response Header gefunden";
                    this.error = true;
                }

                var bodies = this.xmlResponse.getElementsByTagName( "responseBody" );
                if ( bodies.length > 0 ) {
                    this.body = bodies[0];
                } else {
                    this.errorMessage = "kein response Body gefunden";
                    this.error = true;
                }
            }
         }
    };


var AjaxParameter = Class.create();

    AjaxParameter.prototype = {

        initialize: function() {
            this.send = true;
        },

        setName: function( name ){
            this.name = name;
            // if parameter name starts with _a_ its hidden parameter
            // which is not send to the server. Override this by calling
            // setDoSend after setting the name
            if ( this.name.length > 3 && this.name.substr(0,3) == "_a_" )  {
                this.send = false;
            }   
        },

        getName: function(){
            return this.name;
        },

        setValue: function( value ){
            this.value = value;
        },

        getValue: function(){
            return this.value;
        },

        getPair: function() {
            var v;
            if ( this.value ) {
                v = this.value;
            } else {
                v = "";
            }
            return this.name + "=" + v;
        },

        doSend: function() {
            return this.send;
        },
        
        setDoSend: function( send ) {
            this.send = send;
        }
    };
