var aCMS = {
  version: '1.0.0',

  Browser: {
    IE:     !!(window.attachEvent && !window.opera),
    Opera:  !!window.opera,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.product == 'Gecko',
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    QuerySelectors: !!document.querySelector,
    XPath: !!document.evaluate,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div').__proto__ &&
      document.createElement('div').__proto__ !==
        document.createElement('form').__proto__
  }
};

var engine = '';
if (aCMS.Browser.IE)
  engine += ", Internet Explorer";

if (aCMS.Browser.Opera)
  engine += ", Opera";

if (aCMS.Browser.WebKit)
  engine += ", WebKit";

if (aCMS.Browser.Gecko)
  engine += ", Gecko";

if (aCMS.Browser.MobileSafari)
  engine += ", Mobile Safari";

aCMS.Browser.engine = engine.substr(2);
delete engine;

switch (aCMS.Browser.engine) {
case "Internet Explorer":
  if (!window.XMLHttpRequest)
    aCMS.Browser.engineVersion = 6;
  else if (!aCMS.BrowserFeatures.QuerySelectors)
    aCMS.Browser.engineVersion = 7;
  else
    aCMS.Browser.engineVersion = 8;

  break;
case "Gecko":
  if (!aCMS.BrowserFeatures.QuerySelectors)
    aCMS.Browser.engineVersion = "1.9.0";
  else
    aCMS.Browser.engineVersion = "1.9.1";

case "WebKit, Gecko":
  break;

case "Opera":
  if (aCMS.BrowserFeatures.QuerySelectors)
    aCMS.Browser.engineVersion = 10;
}

/******************************************************************************
 *                               Base functions                               *
 ******************************************************************************/
var $;

if (aCMS.BrowserFeatures.ElementExtensions) {
  $ = function (id) {
    if (typeof id == "string")
      return document.getElementById(id);
    else (id instanceof Element)
      return id;

    return null;
  }
} else {
  Element = function() {};
  HTMLElement = function() {};
  HTMLElement.prototype = new Element();

  var __IEcreateElement = document.createElement;

  document.createElement = function (tagName) {
    var element = __IEcreateElement(tagName);

    for (method in Element.prototype)
        element[method] = Element.prototype[method];

    return element;
  }

  $ = function (id) {
    if (typeof id == "string")
      return aCMS.utils.extend(document.getElementById(id), Element);
    else if (aCMS.utils.isElement(id))
      return aCMS.utils.extend(id, Element);

    return null;
  }
}

/******************************************************************************
 *                                  IE fixes                                  *
 ******************************************************************************/

if (aCMS.Browser.IE) {
  Element.prototype._attributeMapping = {'class': 'className',
                                         'for': 'htmlFor',
                                         'colspan': 'colSpan'};

  Element.prototype.getAttribute = function (attribute) {
    if (attribute in this._attributeMapping)
      attribute = this._attributeMapping[attribute];

    return this[attribute];
  }

  Element.prototype.setAttribute = function (attribute, value) {
    if (attribute in this._attributeMapping)
      attribute = this._attributeMapping[attribute];

    this[attribute] = value;
  }
}

/******************************************************************************
 *                                  Array.map                                 *
 ******************************************************************************/
if (!Array.prototype.map) {
  Array.prototype.map = function(fun) {
    var len = this.length >>> 0;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++) {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}

/******************************************************************************
 *                                String.strip                                *
 ******************************************************************************/
String.prototype.strip = function() {
    return this.replace(new RegExp("^\\s+|\\s+$", "g"), "");
}

/******************************************************************************
 *                             Element className                              *
 ******************************************************************************/
Element.prototype.hasClassName = function (className) {
  return this.className.search(new RegExp("(^|\\s+)" + className + "(\\s+|$)")) != -1;
}

Element.prototype.addClassName = function (className) {
  if (!this.hasClassName(className))
    this.className = (this.className + ' ' + className).strip();
}

Element.prototype.removeClassName = function (className) {
  this.className = this.className.replace(
    new RegExp("(^|\\s+)" + className + "(\\s+|$)", "g"), ' ').strip();
}

Element.prototype.toggleClassName = function (className) {
  if (!this.hasClassName(className))
    this.className += ' ' + className;
  else
    this.className = this.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
}

/******************************************************************************
 *                              Element events                                *
 ******************************************************************************/

if (!Element.prototype.addEventListener) {
  Element.prototype.addEventListener = function(eventName, callback, capture) {
    var currentTarget = this;
    var extraAdaptations = function() {};
    switch (eventName) {
    case 'mouseover':
      extraAdaptations = function(e) {
        e.target = e.toElement;
        e.relatedTarget = e.fromElement;
      }
      break;
    case 'mouseout':
      extraAdaptations = function(e) {
        e.target = e.fromElement;
        e.relatedTarget = e.toElement;
      }
      break;
    }

    var wrapper = function() {
      var e = window.event;
      e.stopPropagation = function() {
        this.cancelBubble = true;
      }
      e.currentTarget = currentTarget;
      extraAdaptations(e);
      callback(e);
    }

    if (!capture) {
      wrapper.callback = callback;
      this.attachEvent('on' + eventName, wrapper);
    } else {
      if (this['on' + eventName]) {
        var tmp = wrapper;
        var prevWrapper = this['on' + eventName];
        wrapper = function() {
          prevWrapper();
          if (!window.event.cancelBubble)
            tmp();
        }
        wrapper.prevWrapper = prevWrapper;
        prevWrapper.nextWrapper = wrapper;
      }

      this['on' + eventName] = wrapper;
    }
  }
}

if (!Element.prototype.removeEventListener) {
  Element.prototype.removeEventListener = function(eventName, callback, capture) {
    if (!capture) {
      this.detachEvent('on' + eventName, callback);
    } else {
      var curWrapper = this['on' + eventName];

      if (curWrapper.callback == callback)
        this['on' + eventName] = curWrapper.nextFunc;
      else {
        var prevWrapper;

        if (!curWrapper.nextFunc)
          this['on' + eventName] = null;

        while (curWrapper != null && curWrapper.callback != callback) {
          prevWrapper = curWrapper;
          curWrapper = curWrapper.nextWrapper;
        }

        if (curWrapper != null)
          prevWrapper.nextFunc = curWrapper.nextFunc;
      }
    }
  }
}

/**
 * Returns the offsets of the given element using this element as base.
 *
 * @param {Element} element Must be a descendant node of this element.
 */
Element.prototype.getRelativePosition = function(element) {
  var coordinates = {posX: element.offsetLeft, posY: element.offsetTop};

  var parentNode = element.offsetParent;
  while (parentNode != this) {
    var cssStyle = document.defaultView.getComputedStyle(parentNode, null);
    var p = cssStyle.getPropertyValue('position');
    if (p != 'static') {
      coordinates.posY += parentNode.offsetTop + cssStyle.getPropertyCSSValue('border-top-width').getFloatValue(CSSPrimitiveValue.CSS_PX);
      coordinates.posX += parentNode.offsetLeft + cssStyle.getPropertyCSSValue('border-left-width').getFloatValue(CSSPrimitiveValue.CSS_PX);
      coordinates.posY -= parentNode.scrollTop;
      coordinates.posX -= parentNode.scrollLeft;
    }
    parentNode = parentNode.offsetParent;
  }
  return coordinates;
}

if (document.documentElement.textContent != undefined) {

  /**
   * Changes the inner content of an Element treating it as pure text. If
   * the provided text contains HTML special characters they will be encoded.
   */
  Element.prototype.setTextContent = function(text) {
    this.textContent = text;
  }

  /**
   * Return the inner content of an Element treating it as pure text. All
   * encoded characters will be decoded.
   */
  Element.prototype.getTextContent = function() {
    return this.textContent;
  }

} else if (document.documentElement.innerText != undefined) {

  Element.prototype.setTextContent = function(text) {
    this.innerText = text;
  }

  Element.prototype.getTextContent = function() {
    return this.innerText;
  }

} else {

  Element.prototype.setTextContent = function(text) {
    this.innerHTML = '';
    this.appendChild(this.ownerDocument.createTextNode(text));
  }

  Element.prototype.getTextContent = function() {
    var len = this.childNodes.length;
    var result = '';
    for (var i = 0; i < len; i++) {
      var node = this.childNodes[i];
      if (!aCMS.utils.isElement(node)) {
        result += node.nodeValue;
      } else {
        result += node.getTextContent();
      }
    }
    return result;
  }
}


/******************************************************************************
 *                                 tmp funcs                                  *
 ******************************************************************************/

if (aCMS.Browser.IE) {
  document.createImage = function(width, height) {
    var element;
    switch (arguments.length) {
    case 2:
      element = new Image(width, height);
      break;
    case 1:
      element = new Image(width);
      break;
    case 0:
      element = new Image();
      break;
    }

    for (method in Element.prototype)
        element[method] = Element.prototype[method];

    return element;
  }
} else {
  document.createImage = function(width, height) {
    switch (arguments.length) {
    case 2:
      return new Image(width, height);
    case 1:
      return new Image(width);
    case 0:
      return new Image();
    }
  }
}

/******************************************************************************
 *                      Document.getElementsByClassName                       *
 ******************************************************************************/

if (window.Document == undefined) {
  Document = function() {};
}

// Check if the current navigator implements it
if (Document.prototype.getElementsByClassName == undefined) {

  if (aCMS.BrowserFeatures.querySelectors) {
    // getElementsByClassName using query selectors
  } else if (aCMS.BrowserFeatures.XPath) {
    // getElementsByClassName using XPATH
    Document.prototype.getElementsByClassName = function (className) {
      // Added spaces for searching the full word
      var query = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
      var results = [];
      query = this.evaluate(query, this, null,
                            XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
      for (var i = 0, length = query.snapshotLength; i < length; i++)
        results.push(query.snapshotItem(i));
      return results;
    }
  } else {
    // Slow fallback implementation.
    Document.prototype.getElementsByClassName = function (className) {
      var children = this.getElementsByTagName('*');
      var elements = [], child;
      for (var i = 0, length = children.length; i < length; i++) {
        child = children[i];
        if (child.hasClassName(className))
          elements.push(child);
      }
      return elements;
    }
  }
}

/******************************************************************************
 *                          Document.createDocument                           *
 ******************************************************************************/

if (document.implementation && document.implementation.createDocument) {
  // This is the W3C standard way to do it
  Document.prototype.createDocument = function (namespaceURL, rootTagName, doctype) {
    return document.implementation.createDocument(namespaceURL, rootTagName, null);
  }
} else {
  Document.prototype.createDocument = function (namespaceURL, rootTagName, doctype) {
    var doc = new ActiveXObject("MSXML2.DOMDocument");
    // TODO take into account namespaceURL, rootTagName and doctype
    return doc;
  }
}

/******************************************************************************
 *                              getComputedStyle                              *
 ******************************************************************************/

var useInternalComputedStyle = window.getComputedStyle === undefined;
if (!useInternalComputedStyle) {
  try {
    var computedStyle = document.defaultView.getComputedStyle(document.documentElement, null);
    var width = computedStyle.getPropertyCSSValue('width');
    width = width.getFloatValue(CSSPrimitiveValue.CSS_PX);
  } catch (e) {
    useInternalComputedStyle = true;

    var _nativeGetComputedStyle = document.defaultView.getComputedStyle;
    var _internalGetCurrentStyle = function(element, property, ieProperty) {
      var computedStyle = _nativeGetComputedStyle.call(document.defaultView, element, null);
      return computedStyle.getPropertyValue(property);
    }
  }
} else {
  var _internalGetCurrentStyle = function(element, property, ieProperty) {
    var value = element.currentStyle[ieProperty];

    if (value == 'auto')
      value = element.runtimeStyle[ieProperty];

    return value;
  }
}

if (useInternalComputedStyle) {
  /**
    * Partial implementation of CSSPrimitiveValue.
    */
  function CSSPrimitiveValue(element, property, ieProperty) {
    if (arguments.length == 0)
      return;

    this._element = element;
    this._property = property;
    this._ieProperty = ieProperty;

    this.cssText = _internalGetCurrentStyle(this._element,
                                            this._property,
                                            this._ieProperty);
  }
  CSSPrimitiveValue.CSS_PX = 1;
  CSSPrimitiveValue._ValueRegExp = new RegExp('\(\\d+|\\d+.\\d+\)\(\\w+\)');

  CSSPrimitiveValue.prototype.getFloatValue = function(unit) {
    switch (unit) {
    case CSSPrimitiveValue.CSS_PX:
      if (this.cssText == "")
        return 0;

      var parentNode = this._element.parentNode;
      var testElement = this._element.ownerDocument.createElement('div');
      testElement.style.visibility = "hidden";
      testElement.style.padding = "0";
      testElement.style.margin = "0";
      testElement.style.border = "0";

      var matching = /border-(top|right|bottom|left)-width/.exec(this._property);
      var side = matching !== null ? matching[1] : null;

      // Test if the css value has a basic value (px, em, ex)
      matching = CSSPrimitiveValue._ValueRegExp.exec(this.cssText);
      if (matching != null) {
        var value = matching[1];
        var units = matching[2];
        if (units == "px") {
          // Value is already in pixels
          return parseInt(value);
        } else {
          testElement.style.height = this.cssText;
        }
      } else if ((matching == null) && (side != null)) {
        var property = 'border-' + side + '-style';
        var ieProperty = ComputedCSSStyleDeclaration.prototype._getIEProperty(property);
        var borderStyle = _internalGetCurrentStyle(this._element, property, ieProperty);
        if (borderStyle == "none")
          return 0;

        // border width accepts special values: medium, normal, ...
        var extraElement = this._element.ownerDocument.createElement('div');
        testElement.style.fontSize = "0";
        testElement.style.lineHeight = "0";
        extraElement.style.padding = "0";
        extraElement.style.margin = "0";
        extraElement.style.border = "0";
        extraElement.style.width = "1px";
        extraElement.style.borderTopWidth = this.cssText;
        extraElement.style.borderTopStyle = borderStyle;
        testElement.appendChild(extraElement);
      } else {
        throw new Error();
      }

      parentNode.appendChild(testElement);

      if (testElement.offsetHeight != null) {
        var result = testElement.offsetHeight;
      } else {
        throw new Error();
      }

      parentNode.removeChild(testElement);

      return result;
    default:
      throw new Error();
    }
  }

  CSSPrimitiveValue._rgbaColorParser = new RegExp('rgba\\\(\\s*\(\\d+\)\\s*,\\s*\(\\d+\)\\s*,\\s*\(\\d+\)\\s*\\\,\\s*\(\\d+\)\\s*\\\)');
  CSSPrimitiveValue._rgbColorParser = new RegExp('rgb\\\(\\s*\(\\d+\)\\s*,\\s*\(\\d+\)\\s*,\\s*\(\\d+\)\\s*\\\)');
  CSSPrimitiveValue._hexColorParser = new RegExp('#\([0-9A-F]{2}\)\([0-9A-F]{2}\)\([0-9A-F]{2}\)', 'i');
  /**
    *
    */
  CSSPrimitiveValue.prototype.getRGBColorValue = function() {
  switch (this._property) {
    case 'background-color':
    case 'color':
      var red, green, blue, alpha = '1';
      var matching = CSSPrimitiveValue._rgbColorParser.exec(this.cssText);
      if (matching !== null) {
        red = matching[1];
        green = matching[2];
        blue = matching[3];
      } else {
        matching = CSSPrimitiveValue._rgbaColorParser.exec(this.cssText);
        if (matching !== null) {
          red = matching[1];
          green = matching[2];
          blue = matching[3];
          alpha = matching[4];
        } else {
          matching = CSSPrimitiveValue._hexColorParser.exec(this.cssText);
          if (matching === null) {
            var parentNode = this._element.parentNode;
            var testElement = this._element.ownerDocument.createElement('table');
            testElement.setAttribute('bgcolor', this.cssText);
            testElement.style.visibility = "hidden";
            parentNode.appendChild(testElement);
            var bgColor = testElement.bgColor;
            matching = CSSPrimitiveValue._hexColorParser.exec(bgColor);
            if (matching === null)
              throw new Error('Error on getRGBColorValue');

            parentNode.removeChild(testElement);
          }

          function hex2value(hex) {
            hex = hex.toUpperCase();
            return ("0123456789ABCDEF".indexOf(hex.substr(0,1)) * 16) +
                    "0123456789ABCDEF".indexOf(hex.substr(1,1));
          }

          // Build the result
          var red = hex2value(matching[1]);
          var green = hex2value(matching[2]);
          var blue = hex2value(matching[3]);
        }
      }

      var result = new Object();
      result.red = new CSSColorComponentValue(red);
      result.green = new CSSColorComponentValue(green);
      result.blue = new CSSColorComponentValue(blue);
      result.alpha = new CSSColorComponentValue(alpha);
      return result;
    default:
      throw new Error();
    }
  }

  /**
    *
    */
  function CSSColorComponentValue(value) {
    this.cssText = "" + value;
  }
  CSSColorComponentValue.prototype = new CSSPrimitiveValue();

  CSSColorComponentValue.prototype.getFloatValue = function(unit) {
    return parseInt(this.cssText);
  }

  /**
    * Partial implementation of ComputedCSSStyleDeclaration
    */
  function ComputedCSSStyleDeclaration(element) {
    this._element = element;
  }

  ComputedCSSStyleDeclaration.prototype._getIEProperty = function(property) {
    switch (property) {
    case 'float':
      return "styleFloat";
    default:
      return property.replace(/-\w/g, function(a){return a.substr(1,1).toUpperCase()});
    }
  }

  ComputedCSSStyleDeclaration.prototype.getPropertyCSSValue = function(property) {
    return new CSSPrimitiveValue(this._element, property, this._getIEProperty(property));
  }

  ComputedCSSStyleDeclaration.prototype.getPropertyValue = function(property) {
    return _internalGetCurrentStyle(this._element,
                                    property,
                                    this._getIEProperty(property));
  }

  /**
    * WARNING This is not a full implementation of the getComputedStyle, some
    * things will not work.
    *
    * @param element
    * @param context not used by this implementation
    */
  window.getComputedStyle = function(element, context) {
    return new ComputedCSSStyleDeclaration(element);
  }

  if (document.defaultView === undefined)
    document.defaultView = window;
}

/******************************************************************************
 *                                    AJAX                                    *
 ******************************************************************************/

function redirectForm(form, baseURL, selector) {
  window.location = baseURL + '/' + form[selector].value;
  return false;
}

function redirectFormById(formId, baseURL, selector) {
  redirectForm($(formId), baseURL, selector);
}

function makeRequest(method, url, data, callback) {
  var httpRequest;

  if (window.XMLHttpRequest) {
    // Mozilla, Safari, ...
    httpRequest = new XMLHttpRequest();

    if (httpRequest.overrideMimeType) {
      httpRequest.overrideMimeType('application/xml');
    }
  } else if (window.ActiveXObject) {
    // IE 6
    try {
      httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) {}
    }
  }

  if (!httpRequest)
    return false;

  httpRequest.onreadystatechange = function() {
    switch (httpRequest.readyState) {
    case 4:
      callback(httpRequest);
      break;
    }
  };
  httpRequest.open(method, url, true);

  if (method == "POST") {
    httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    httpRequest.send(data);
  } else {
    httpRequest.send(null);
  }
}

/******************************************************************************
 *                                 Acms utils                                 *
 ******************************************************************************/
aCMS.utils = {};
aCMS.utils.extend = function(object, _class) {
  for (var attribute in _class.prototype)
    object[attribute] = _class.prototype[attribute];

  return object;
}

if (aCMS.BrowserFeatures.ElementExtensions) {
  aCMS.utils.isElement = function(node) {
    return node instanceof Element;
  }
} else {
  aCMS.utils.isElement = function(node) {
    return node && 'nodeType' in node && node.nodeType === 1;
  }
}

/**
 * Permite obtener un objeto a partir de la mezcla de los atributos de dos
 * objetos. Para ello, se pasarán los dos objetos que se usarán de fuente,
 * siendo el primero de los objetos sobreescrito con el resultado. En caso de
 * que exista un mismo atributo en los dos objetos, el valor final será el del
 * segundo objeto, perdiendose el valor del primer objeto.
 *
 * @param {Object} obj1 objeto base.
 * @param {Object} obj2 objeto modificador. En caso de que este argumento sea
 * null, esta función no hará nada.
 *
 * @return obj1 modificado
 */
aCMS.utils.merge = function (obj1, obj2) {
  if (obj2 != null) {
    for (var key in obj2)
      obj1[key] = obj2[key];
  }

  return obj1;
}

/**
 * Permite forzar el valor que tendrá la variable <code>this</code> cuando se
 * llame a la función indicada.
 *
 * @param {Object} func Función a la que se le forzará el valor de la variable
 * <code>this</code>
 * @param {Object} _this valor que tendrá la variable <code>this</code>.
 * @return a new function that forces the value of <code>this</code> and calls
 * the given function.
 */
aCMS.utils.bind = function (func, _this) {
    return function() {return func.apply(_this, arguments)};
}

/**
 * Rellena los parámetros usados en un patrón. Los campos a rellenar en el
 * patrón vienen indicados mediante sentencias "%(nombre)s". Por ejemplo,
 * al finalizar la ejecución del siguiente código:
 * <code>
 *     var date = {year: 2009, month: 3, day: 27};
 *
 *     var pattern1 = "%(year)s/%(month)s/%(day)s";
 *     var result1 = aCMS.utils.interpolate(pattern, date);
 *
 *     var pattern2 = "%(day)s/%(month)s/%(year)s";
 *     var result2 = aCMS.utils.interpolate(pattern, date);
 * </code>
 *
 * obtendríamos "2009/3/27" en result1 y "27/3/2009" en result2
 */
aCMS.utils.interpolate = function(pattern, attributes) {
    return pattern.replace(/%\(\w+\)s/g,
                           function(match) {
                               return String(attributes[match.slice(2,-2)])
                           });
}

/**
 * Elimina un nodo DOM de su elemento padre. Esta funcion no comprueba que el
 * nodo DOM tenga un padre, por lo que en caso de no ser así el código lanzaría
 * una excepción.
 */
aCMS.utils.removeFromParent = function (domNode) {
    domNode.parentNode.removeChild(domNode);
}

/**
 * Importa la librería Javascript indicada por la URL pasada. Esta función
 * no controla si el script fue cargado satisfactoriamente.
 *
 * @param {String} url
 * @param {Function} onloadCallback indica el callback al que habrá que llamar
 *                   en caso de que el script sea cargado exitosamente.
 */
aCMS.utils.importJS = function(url, onloadCallback) {
    // Create the Script Object
    var script = document.createElement('script');
    script.setAttribute("type", 'text/javascript');

    // onload callback
    if (onloadCallback)
      script.addEventListener("load", onloadCallback, true);

    // Load script
    script.setAttribute("src", url);

    // Insert the created object to the html head element
    var head = document.getElementsByTagName('head').item(0);
    head.appendChild(script);
}


/******************************************************************************
 *                                  IE Fixes                                  *
 ******************************************************************************/

if (aCMS.Browser.engine == "Internet Explorer") {

  //stylesheet.addRule("body *", 'behavior: url(/jscripts/ie-fixes.htc)');
  if (aCMS.Browser.engineVersion == 6) {
    var stylesheet = document.createStyleSheet();
    stylesheet.addRule('.transparentPNG', 'behavior: url(/jscripts/ie-pngfix.htc)');
  }
}