var isIE     = navigator.userAgent.toLowerCase().indexOf('msie') > -1;
var isOpera  = navigator.userAgent.toLowerCase().indexOf('opera') > -1;
var isMoz    = document.implementation && document.implementation.createDocument;
var isFF     = (isMoz && navigator.userAgent.toLowerCase().indexOf('firefox')!=-1);
var isSafari = (navigator.userAgent.indexOf("AppleWebKit") !=-1); // Detecting not only Safari but WebKit-based browsers
var isKonqueror = navigator.userAgent.toLowerCase().indexOf('khtml') > -1;

if (isIE){
   verIE = parseInt(navigator.appVersion.split('MSIE')[1]);
}

/**
 *    Usage
 *    element.addBehavior('behavior.htc');
 */

function addBehaviorToElements(elements,_url) {
   try {
      for (var i=0; i<elements.length; i++) {
         if (!addBehavior(elements[i], _url)) {
            return;
         }
      }
   } catch(e) {
      alert('Failed to add behavior "'+_url+'" to elements\n\n'+e.message);
   }
}

var behaviorUrns = {
   __private : []
}

function createEventObject() {
   return document.createEvent("Events");
}

function addBehavior(elem, _url) {
   if (isIE) {
      elem.addBehavior(_url);
   } else {
      try {
         // calling this method with the first argument as zero
         //  initialises the object's behaviors
         if (_url === 0) {
            // grab the htc's url from the css setting
            var $binding = getComputedStyle(elem, null).getPropertyValue("-moz-binding");
            $binding = $binding.replace(/^url\(([^)]*)\)$/, "$1").split("#");
            if ($binding) {
               var $path = $binding[0].replace(/[^\/]+$/, "");
               // support multiple behaviors
               var $htcs = $binding[1].split("|");
               var i = $htcs.length;
               while (i--) addBehavior(elem, $path + $htcs[i]);
            }
         }
         
         if (!_url) return;

         // check the cache
         if (!behaviorUrns[_url]) {
            // constants
            var $SEPARATOR = ";";

            function _getTagName($node) {
               var $tagName = $node.tagName.toLowerCase();
               // this fixes a bug(?) in Mozilla 1.6b that includes the
               //  namespace prefix in the tagName
               return $tagName.slice($tagName.indexOf(":") + 1);
            };

            function _getAttribute($node, $attribute) {
               return $node.getAttribute($attribute) || $node.getAttribute($attribute.toUpperCase());
            };

            // this function converts elements in a behavior to a program
            //  declaration, for example:
            //    <public:attach for="window" event="onload" handler="init"/>
            //  becomes:
            //    window.addEventListener("load", init);
            function _asDeclaration($behaviorNode) {
               switch (_getTagName($behaviorNode)) {
                  case "event":
                     var id = _getAttribute($behaviorNode, "id");
                     return (id) ? "var " + id + "={fire:function(event){fireEvent(element,'" +
                        _getAttribute($behaviorNode, "name") + "',event)}}" : "";
                  case "property":
                     var $name = _getAttribute($behaviorNode, "name");
                     var $get = _getAttribute($behaviorNode, "get") || "";
                     if ($get) $get = "__defineGetter__('" + $name + "'," + $get + ")";
                     var $put = _getAttribute($behaviorNode, "put") || "";
                     if ($put) $put = ";__defineSetter__('" +  $name + "'," + $put + ")";
                     var id = _getAttribute($behaviorNode, "id") || "";
                     if (id) id = "var " + id + "={fireChange:new Function};";
                     return id + $get + $put;
                  case "method":
                     return "element." + _getAttribute($behaviorNode, "name") + "=" +
                        _getAttribute($behaviorNode, "name");
                  case "attach":
                     var $handler = _getAttribute($behaviorNode, "handler") || "";
                     $handler += ($handler) ? "()" : _getAttribute($behaviorNode, "onevent");
                     $handler = "function(event){window.event=event;return " + $handler + "}";
                     var $event = _getAttribute($behaviorNode, "event");
                     switch ($event) {
                        case "oncontentready": return "window.setTimeout(" + $handler + ",1)";
                        case "ondocumentready": return "behaviorUrns.__private.push(" + $handler + ")";
                     }
                     return (_getAttribute($behaviorNode, "for")||"element") +
                        ".addEventListener('" + $event.slice(2) + "'," + $handler + ",false)";
                  case "defaults":
                     // not implemented
                  default:
                     return "";
               }
            };

            function _asDefault($node) {
               return (_getAttribute($node, "put")) ? ";var __tmp=getAttribute('" + _getAttribute($node, "name") + "')||" +
                  (_getAttribute($node, "value") || "null") +
                  ";if(__tmp!=null)element['" + _getAttribute($node, "name") + "']=__tmp" : "";
            };

            // extract the body of a function
            function _getFunctionBody($function) {
               with (String($function)) return slice(indexOf("{") + 1, lastIndexOf("}"));
            };

            // behaviors are defined as xml documents, so we can use
            //  the http request object to load them and the dom parser
            //  object to parse them into a dom tree
            var _httpRequest = new XMLHttpRequest;
            function _loadFile($url) {
            try {
               // load the behavior
               _httpRequest.open("GET", $url, false);
               _httpRequest.send(null);
               return _httpRequest.responseText;
            } catch (e) {
               // ignore (but don't crash)
            }};

            // analyse the dom tree, build the interface and create the script
            var _declarations = [];
            var _defaults = "";
            var _script = "";
            function _load() {
               // build a dom representation of the loaded xml document
               var $xmlText = _loadFile(_url);
               if ($xmlText) {
                  var $dom = (new DOMParser).parseFromString($xmlText, "text/xml");
                  var $childNodes = $dom.documentElement.childNodes, $node;
                  for (var i = 0; ( $node = $childNodes[i]); i++) {
                     if ($node.nodeType == Node.ELEMENT_NODE) {
                        if (_getTagName($node) == "script") {
                           var $src = _getAttribute($node, "src");
                           if ($src) {
                              _script += _loadFile($src);
                           } else {
                              // build the script from the text nodes of the script element
                              for (var j = 0; j < $node.childNodes.length; j++)
                                 _script += $node.childNodes[j].nodeValue;
                           }
                        } else {
                           // convert the dom node representation of a
                           //  <public:declaration/>  to a javascript statement
                           //  and store it in our declarations collection
                           _declarations.push(_asDeclaration($node));
                           if (_getTagName($node) == "property") {
                              _defaults += _asDefault($node);
                           }
                        }
                     }
                  }
                  _defaults += ";delete __tmp";
               }
            };
            _load();
            // we've finished collecting interface declarations.
            //  they are now held as an array of strings.

            // build a function from the script and extract the function body
            //  this has the effect of formatting the script (removing comments etc)
            _script = _getFunctionBody(new Function(_script));

            // support: new ActiveXObject
            var $ACTIVEX = /\bnew\s+ActiveXObject\s*\(\s*(["'])\w\.XMLHTTP\1\s*\)/gi;
            _script = _script.replace($ACTIVEX, "new XMLHttpRequest()");

            // begin: annoying parse of script to "shuffle" declarations
            //        and inline code.
            // microsoft dhtml behaviors add the interface first, then
            //  apply inline script.
            // to achieve this, we have to strip out all of the inline
            //  code, leaving only function declarations. the inline code
            //  then gets appended to the script block for later
            //  execution.
            // in between the function declarations and inline script, we
            //  sandwich the property getters and setters.
            // this is a real nuisance actually...

            // on the upside regular expressions are really quick...

            // i'm using "#" as a placeholder, so i'll have to escape these out
            _script = _script.replace(/#/g, "\\x23");

            // parse out strings, regexps and program
            //  blocks - anything between curly braces {..}
            var $ = [_declarations.join($SEPARATOR)];
            var $BLOCKS_REGEXPS_STRINGS = /(\s*\"[^\"\n]+\")|(\s*\/[^\/\n]+\/)|(\s*(\n\s*)?\{[^\{\}]*\}(\s*;)?)/g;
            var _ENCODED = /#(\d+)\b/g;
            // store a string and return a unique id
            function _encode($match) {return "#" + $.push($match)};
            function _decode($match, $index) {return $[$index - 1]};
            while ($BLOCKS_REGEXPS_STRINGS.test(_script)) {
               _script = _script.replace($BLOCKS_REGEXPS_STRINGS, _encode);
            }
            // we are now left with function declarations and inline statements

            // remove function declarations and save them
            var $FUNCTIONS = /\s*function[^\n]*\n/g
            var _functions = _script.match($FUNCTIONS) || [];
            _script = _script.replace($FUNCTIONS, "");

            // re-assemble the encoded script, in the following
            //  order: function declarations, interface definition
            //  (getters and setters), inline script
            _script = _functions.concat("#1", _script).join($SEPARATOR);

            // decode the script
            var i = $.length;
            $ = $.join("\x01").replace(/\$\$/g, "$$$$$$$$").split("\x01"); // Scott Shattuck
            do _script = _script.replace("#" + i, $[--i]); while (i);
            // end: annoying parse of script

            // build the final script
            _script += _defaults;

            // create an anonymous function in the global namespace.
            // this function will add the interface defined by the dhtml behavior.
            // after we've built this function we'll store it so that we don't
            //  have to go through this process again.
            behaviorUrns[_url] = new Function("element", "with(this){" + _script + "}");
         }

         try {
            elem.document = document;
         } catch(e) {
            // Do nothing
         }

         // because we loaded synchronously (or got it from the cache)
         //  we can apply the behavior immediately...
         behaviorUrns[_url].call(elem, elem);

         // this might mean somthing later
         return _cookie;
      } catch ($error) {
         alert($error.message);
         return 0;
      }
   }
   return true;
}

function removeBehavior(elem, $cookie) {
   // mmm, not in a hurry to write this
}

if (!isIE) {
   var _cookie = -1; // no support for removeBehavior yet

   if (typeof(Element) !== 'undefined' && typeof(Element.prototype) !== 'undefined') {
      // implement the addBehavior method for all elements
      Element.prototype.addBehavior = function(_url) {
         addBehavior(this, _url);
      };
   
      // implement the removeBehavior method for all elements
      Element.prototype.removeBehavior = function($cookie) {
         removeBehavior(this, $cookie);
      };
   }

   // cache for previously loaded behaviors
   // -also store some "default" behaviors
   document.behaviorUrns = behaviorUrns;

   // support multiple behaviors and ondocumentready
   window.addEventListener("load", function() {
      try {
         var $handlers = behaviorUrns.__private;
         var i = $handlers.length;
         while (i) $handlers[--i]();
         delete behaviorUrns.__private;
      } catch ($ignore) {
      }
   }, false);

   if (typeof(HTMLDocument) !== 'undefined' && typeof(HTMLDocument.prototype) !== 'undefined') {
      // mimic the "createEventObject" method for the document object
      HTMLDocument.prototype.createEventObject = function() {
          return createEventObject();
      };
      // mimic the "createEventObject" method
      HTMLElement.prototype.createEventObject = function() {
          return this.ownerDocument.createEventObject();
      };
      // mimic the "fireEvent" method
      HTMLElement.prototype.fireEvent = function($name, $event) {
          fireEvent(this, $name, $event);
      };
   }
}

// Returns the style value for the property specfied
function get_style(obj,property,propertyNS) {
   var returnVal;

   try {
      if (obj.currentStyle && !isOpera) {
         var returnVal = obj.currentStyle[property];
      } else {
         /*
         Safari does not expose any information for the object if display is
         set to none is set so we temporally enable it.
         */
         if (isSafari && obj.style.display == 'none') {
            obj.style.display = '';
            var wasHidden = true;
         }

         var returnVal = document.defaultView.getComputedStyle(obj,'').getPropertyValue(propertyNS);

         // Rehide the object
         if (isSafari && wasHidden) {
            obj.style.display = 'none';
         }
      }
   } catch(e) {
       // Do nothing
   }

   if (typeof(returnVal) == 'undefined') {
      returnVal = '';
   }

   return returnVal;
}

function addEvent(obj,evType,fn,useCapture) {
   if (window.opera && obj.addEventListener) {
      // Prevent an Opera bug where event added in capture mode to form elements do not get triggered
      useCapture = false;
   }
   if (obj.addEventListener) {
      obj.addEventListener(evType,fn,useCapture);
      return true;
   } else if (obj.attachEvent) {
      var r = obj.attachEvent('on'+evType,fn);
      return r;
   } else {
      return false;
   }
}
