/******************************************************************************
 *                              getViewportInfo                               *
 ******************************************************************************/

if (typeof window.pageXOffset == 'number') {
    aCMS.utils.getViewportInfo = function() {
        var result = {};

        result.width = window.innerWidth;
        result.height = window.innerHeight;
        result.left = window.pageXOffset;
        result.right = result.left + result.width;
        result.top = window.pageYOffset;
        result.bottom = result.top + result.height;

        return result;
    }
} else if (aCMS.Browser.IE && aCMS.Browser.engineVersion == 6) {
    aCMS.utils.getViewportInfo = function() {
        var result = {};

        result.width  = document.body.clientWidth;
        result.height = document.body.clientHeight;
        result.left   = document.body.scrollLeft;
        result.right  = result.left + result.width;
        result.top    = document.body.scrollTop;
        result.bottom = result.top + result.height;

        return result;
    }
} else {
    aCMS.utils.getViewportInfo = function() {
        var result = {};

        result.width  = document.documentElement.clientWidth;
        result.height = document.documentElement.clientHeight;
        result.left   = document.documentElement.scrollLeft;
        result.right  = result.left + result.width;
        result.top    = document.documentElement.scrollTop;
        result.bottom = result.top + result.height;

        return result;
    }
}

/******************************************************************************
 *                                  setStyle                                  *
 ******************************************************************************/
aCMS.utils.parseURLParams = function() {
    var result = {};

    var params = document.location.search;
    if (params.length > 0) {
        params = params.substring(1, params.length);
    }
    params = params.split("&");
    for (var i = 0; i < params.length; i++) {
        var param = params[i];
        param = param.split("=");
        if (param.length > 1) {
            var key = decodeURIComponent(param[0]);
            var value = decodeURIComponent(param[1]);
            result[key] = value;
        }
    }
    return result;
}

aCMS.utils.preloadImages = function(images, onStepCallback, onFinishCallback) {

    var loadedCount = 0;
    var totalCount = images.length;
    var imagesNotFound = new Array();
    var _incLoadedCount, _notifyError;

    _incLoadedCount = function(e, error) {
        var target = e.currentTarget;
        error = !!error;

        loadedCount++;

        // Remove event listeners
        target.removeEventListener('load', _incLoadedCount, false);
        target.removeEventListener('error', _notifyError, false);
        target.removeEventListener('abort', _notifyError, false);

        // Report
        if (error) {
            imagesNotFound.push(target.src);
        }
        if (onStepCallback)
            onStepCallback(target.src, loadedCount, totalCount, error);

        // Check if we have finished
        if (loadedCount == totalCount && onFinishCallback)
            onFinishCallback(imagesNotFound);
    }

    _notifyError = function(e) {
        _incLoadedCount(e, true);
    }

    for (var i = 0; i < images.length; i++) {
        var img = document.createElement('img');
        img.addEventListener('load', _incLoadedCount, false);
        img.addEventListener('error', _notifyError, false);
        img.addEventListener('abort', _notifyError, false);
        img.src = images[i];
    }
}

/******************************************************************************
 *                                  setStyle                                  *
 ******************************************************************************/
Element.prototype.setStyle = function(changes) {
    for (var key in changes) {
        var value = changes[key];
        if (key == 'opacity') {
            this.setOpacity(value);
        } else {
            this.style[key] = changes[key];
        }
    }
}

if (aCMS.Browser.IE) {
    Element.prototype.setOpacity = function(value) {
        function stripAlpha(filter) {
            return filter.replace(/alpha\([^\)]*\)/gi,'');
        }

        if (value == 1 || value === '' || value == null) {
            this.style.filter = stripAlpha(this.style.filter).strip();
            return;
        } else if (value < 0.00001) {
            value = 0;
        }

        this.style.filter = stripAlpha(this.style.filter) + 'alpha(opacity=' + (value * 100) + ')';
    }
} else {
    Element.prototype.setOpacity = function(value) {
        this.style.opacity = (value == 1) ? '' :
                             (value < 0.00001) ? 0 : value;
    }
}

/**
 * "Disables" javascript. As javascript can not be disabled using javascript
 * What this function does is to ensure that no event listeners are assigned,
 * that noscript elements are showed, etc...
 *
 * No event listener should be assigend before calling this function. Also,
 * calling to this method does not stop current javascript code execution, so
 * you should finish it manually.
 *
 * Example:
 *
 *  disableJavascript();
 *  return;
 */
aCMS.utils.disableJavascript = function() {
    var scriptElements = document.getElementsByTagName('script');
    while (scriptElements.length) {
        var element = scriptElements[0];
        aCMS.utils.removeFromParent(element);
    }

    var onload = function() {
        document.body.removeEventListener('load', onload, true);

        var noscriptElements = document.getElementsByTagName('noscript');
        var tmp = document.createElement('div');
        while (noscriptElements.length) {
            var element = noscriptElements[0];
            tmp.innerHTML = element.innerHTML;
            while (tmp.childNodes.length) {
                var childElement = tmp.childNodes[0];
                element.parentNode.insertBefore(childElement, element);
            }
            aCMS.utils.removeFromParent(element);
        }

        document.close();
    }

    Element.prototype.addEventListener.call(document.body, 'load', onload, true);
    Element.prototype.addEventListener = function() {};
}

/**
 *
 */
var CommandQueue = function (initFunc) {
    var context = null;
    var currentCommand = null;
    var running = false;
    var elements = new Array();

    var stepFunc = null;
    var endFunc = null;
    var cancelFunc = null;

    var step = 0;
    var steps = 0;
    var timer = null;

    /**
     * @private
     */
    function doStep() {
        if (steps <= 0 || !stepFunc(context, currentCommand, step++) || (steps-- == 0)) {
            clearInterval(timer);
            timer = null;
            if (endFunc)
                endFunc(context, currentCommand);
            doInit();
        }
    }

    /**
     * @private
     */
    function doInit() {
        var stepInfo;
        do {
            currentCommand = elements.shift();
        } while (currentCommand != undefined && !(stepInfo = initFunc(currentCommand)));

        if (currentCommand != undefined) {
            step = 0;
            steps = stepInfo.steps;

            context    = stepInfo.context;
            stepFunc   = stepInfo.stepFunc;
            endFunc    = stepInfo.endFunc;
            cancelFunc = stepInfo.cancelFunc;

            timer = setInterval(doStep, stepInfo.interval);
        } else {
            running = false;
        }
    }


    /*
     * Public methods
     */

    /**
     * Añade un comando a la cola de procesamiento. El comando será procesado
     * despues de que se procesen todos los comandos añadidos anteriormente.
     *
     * @param command comando a añadir a la cola de procesamiento. El tipo de
     * este párametro tiene que ser compatible con las funciones initFunc y
     * stepFunc pasadas en el constructor.
     */
    this.addCommand = function(command) {
        if (command == undefined)
            return;

        elements.push(command);

        if (!running) {
            running = true;
            doInit();
        }
    }

    this.cancelCurrentCommand = function() {
        if (!running)
            return;

        clearInterval(timer);
        timer = null;
        cancelFunc(context, currentCommand);
        doInit();
    }

    this.cancelAllPendingCommand = function() {
        if (!running)
            return;

        clearInterval(timer);
        timer = null;
        cancelFunc(context, currentCommand);
        currentCommand = null

        while (elements.length > 0) {
            var command, stepInfo;
            do {
                command = elements.shift();
            } while (command != undefined && !(stepInfo = initFunc(context, command)));

            if (stepInfo.cancelFunc)
                stepInfo.cancelFunc(context, command);
        }

        running = false;
    }
}

aCMS.effects = new Object();

/**
 *
 */
aCMS.effects.effect = function() {
}

aCMS.effects.effectManager = function() {
    this._commandQueue = new CommandQueue(this._startFunc);
}

aCMS.effects.effectManager.prototype.run = function(command) {
    this._commandQueue.addCommand(command);
}

aCMS.effects.effectManager.prototype.skip = function(command) {
    this._commandQueue.cancelCurrentCommand();
}

aCMS.effects.effectManager.prototype.cancel = function(command) {
    this._commandQueue.cancelAllPendingCommand();
}

aCMS.effects.effectManager.prototype._startFunc = function(command) {
    if (!command.effect || !(command.effect instanceof aCMS.effects.effect))
        return {'steps': 0};

    var result = command.effect._startFunc(command);

    if (!result['stepFunc'])
        result['stepFunc'] = command.effect._stepFunc;

    return result;
}


/**
 *
 */
aCMS.effects.fade = function() {
}
aCMS.effects.fade.prototype = new aCMS.effects.effect();

aCMS.effects.fade.prototype._startFunc = function(command) {
    var stepFunc = command.type == 'out' ? this._fadeoutStepFunc : this._fadeinStepFunc;
    return {
        'steps':    20,
        'interval': 50,
        'endFunc': this._endFunc,
        'cancelFunc': this._cancelFunc,
        'stepFunc': stepFunc
    };
}

aCMS.effects.fade.prototype._endFunc = function(context, command) {
    if (command.endFunc)
        command.endFunc(command);
}

aCMS.effects.fade.prototype._fadeinStepFunc = function(context, command, step) {
    var opacity = step * 0.05;

    command.fadeLayer.setStyle({'opacity': opacity});

    return true;
}

aCMS.effects.fade.prototype._fadeoutStepFunc = function(context, command, step) {
    var opacity = (19 - step) * 0.05;
    command.fadeLayer.setStyle({'opacity': opacity});

    return true;
}

aCMS.effects.fade.prototype._cancelFunc = function(context, command) {
    var opacity;

    switch (command.type) {
    case 'in':
    default:
        opacity = 1;
        break;
    case 'out':
        opacity = 0;
        break;
    }
    command.fadeLayer.setStyle({'opacity': opacity});

    if (command.cancelFunc)
        command.cancelFunc(command);
}

/**
 * 
 */
aCMS.effects.combination = function() {
}
aCMS.effects.combination.prototype = new aCMS.effects.effect();

aCMS.effects.combination.prototype._startFunc = function(command) {
    var i;
    var context = {
        'effects': []
    };

    for (i = 0; i < command.effects.length; i++) {
        var effectCommand = command.effects[i];
        var currentStepInfo = effectCommand.effect._startFunc(effectCommand);

        currentStepInfo._command = effectCommand;
        context.effects.push(currentStepInfo);
    }

    return {
        'steps':    20,
        'interval': 20,
        'endFunc': this._endFunc,
        'cancelFunc': this._cancelFunc,
        'stepFunc': this._stepFunc,
        'context': context
    };
}

aCMS.effects.combination.prototype._endFunc = function(context, command) {
    for (var i = 0; i < context.effects.length; i++) {
        var effect = context.effects[i];
        if (effect.endFunc)
            effect.endFunc(effect.context, effect._command);
    }

    if (command.endFunc)
        command.endFunc(command);
}

aCMS.effects.combination.prototype._stepFunc = function(context, command, step) {
    var effectsToRemove = [];

    for (var i = 0; i < context.effects.length; i++) {
        var effect = context.effects[i];
        var _continue = effect.stepFunc(effect.context, effect._command, step);

        if (!_continue && effect.endFunc) {
            effect.endFunc(effect.context, effect._command);
            effectsToRemove.push(i);
        }
    }

    for (var i = 0; i < effectsToRemove.length; i++) {
        context.effects.splice(effectsToRemove[i], 1);
    }

    return context.effects.length != 0;
}

aCMS.effects.combination.prototype._cancelFunc = function(context, command) {
    for (var i = 0; i < context.effects.length; i++) {
        var effect = context.effects[i];
        if (effect.cancelFunc)
            effect.cancelFunc(effect.context, effect._command);
    }

    if (command.cancelFunc)
        command.cancelFunc(command);
}


// Static class
var StyledElements = new Object();

/**
 * Esta clase se encarga de gestionar los eventos que van a manejar los
 * <code>StyledElement</code>s.
 */
StyledElements.Event = function() {
    this.handlers = [];
}

StyledElements.Event.prototype.addEventListener = function(handler) {
    this.handlers.push(handler);
}

StyledElements.Event.prototype.removeEventListener = function(handler) {
    var index = this.handlers.indexOf(handler);
    if (index != -1)
        this.handlers.splice(index, 1);
}

StyledElements.Event.prototype.dispatch = function() {
    for (var i = 0; i < this.handlers.length; i++)
        this.handlers[i].apply(null, arguments);
}

/**
 * @abstract
 */
StyledElements.StyledElement = function(events) {
    events = events ? events : [];

    this.events = {};
    for (var i = 0; i < events.length; i++)
        this.events[events[i]] = new StyledElements.Event();

    this.wrapperElement = null;
}

/**
 * Inserta el elemento con estilo dentro del elemento indicado.
 *
 * @param element Este será el elemento donde se insertará el elemento con
 * estilo.
 * @param refElement Este parámetro es opcional. En caso de ser usado, sirve
 * para indicar delante de que elemento se tiene que añadir este elemento con
 * estilo.
 */
StyledElements.StyledElement.prototype.insertInto = function (element, refElement) {
    if (element instanceof StyledElements.StyledElement) {
        element = element.wrapperElement;
    }

    if (refElement instanceof StyledElements.StyledElement) {
        refElement = refElement.wrapperElement;
    }

    if (refElement)
        element.insertBefore(this.wrapperElement, refElement);
    else
        element.appendChild(this.wrapperElement);
}

/**
 * Reemplaza el elemento indicado con este componente.
 *
 * @param element Este será el elemento que será remplazado con este componente.
 */
StyledElements.StyledElement.prototype.replace = function (element) {
    if (element instanceof StyledElements.StyledElement) {
        element = element.wrapperElement;
    }

    var parentElement = element.parentNode;
    this.insertInto(parentElement, element);
    aCMS.utils.removeFromParent(element);
    this.wrapperElement.setAttribute('id', element.getAttribute('id'));
}

/**
 * Esta función sirve para repintar el componente.
 *
 * @param {Boolean} temporal Indica si se quiere repintar el componente de
 * forma temporal o de forma permanente. Por ejemplo, cuando mientras se está
 * moviendo el tirador de un HPaned se llama a esta función con el parámetro
 * temporal a <code>true</code>, permitiendo que los componentes intenten hacer
 * un repintado más rápido (mejorando la experiencia del usuario); y cuando el 
 * usuario suelta el botón del ratón se ejecuta una última vez esta función con
 * el parámetro temporal a <code>false</code>, indicando que el usuario ha
 * terminado de mover el tirador y que se puede llevar a cabo un repintado más
 * inteligente. Valor por defecto: <code>false</code>.
 */
StyledElements.StyledElement.prototype.repaint = function (temporal) {
}

/**
 * 
 */
StyledElements.StyledElement.prototype.addClassName = function(className) {
    this.wrapperElement.addClassName(className);
}

/**
 * 
 */
StyledElements.StyledElement.prototype.removeClassName = function(className) {
    this.wrapperElement.removeClassName(className);
}

/**
 * Añade un listener para un evento indicado.
 */
StyledElements.StyledElement.prototype.addEventListener = function(event, handler) {
    if (this.events[event] === undefined)
        throw new Exception(aCMS.utils.interpolate("Unhandled event \"%(event)s\"", {event: event}));

    this.events[event].addEventListener(handler);
}

/**
 * Elimina un listener para un evento indicado.
 */
StyledElements.StyledElement.prototype.removeEventListener = function(event, handler) {
    if (this.events[event] === undefined)
        throw new Exception(aCMS.utils.interpolate("Unhandled event \"%(event)s\"", {event: event}));

    this.events[event].removeEventListener(handler);
}













/**
 * Canvas
 */

if (aCMS.Browser.IE) {

  /* Enable vml support */
  document.namespaces.add('vml', 'urn:schemas-microsoft-com:vml', "#default#VML");

  var s = document.createStyleSheet();
  s.addRule('vml\\: polyline', 'behavior:url(#default#VML);display:inline-block;');
  s.addRule('vml\\: shape', 'behavior:url(#default#VML);display:inline-block;');

  var Canvas = function() {
    this.canvasElement = document.createElement('div');
    this.ownerDocument = this.canvasElement.ownerDocument;
    this.resetContext();
  }

  Canvas.prototype.addClassName = function(className) {
    this.canvasElement.addClassName(className);
  }

  Canvas.prototype._propMapping = {
      'fill': 'fillcolor',
      'stroke': 'strokecolor',
      'stroke-width': 'strokeweight'
  };

  Canvas.prototype.setContext = function(context) {
    this.context = {};
    for (var key in context) {
      var value = context[key];

      if (key in this._propMapping)
        key = this._propMapping[key];

      this.context[key] = value;
    }
  }

  Canvas.prototype._applyStyle = function(style, element) {
    var key;
    for (key in style) {
      var value = style[key];

      if (this._propMapping[key] != undefined)
        key = this._propMapping[key];

      if (key == 'fillcolor' && value == 'none')
        element.filled = false;
      else
        element[key] = value;
    }
  }

  Canvas.prototype.drawPolyLine = function(points, style) {
    var pointsAttr = "";
    for (var i = 0; i < points.length; i++)
      pointsAttr += "," + points[i].posX + "," + points[i].posY;

    pointsAttr = pointsAttr.substr(1);
    var polyline = this.ownerDocument.createElement("<vml:polyline points=\"" + pointsAttr + "\" />"); // IE 6
    polyline.style.position = "absolute";
    polyline.style.top = "0px";
    polyline.style.left = "0px";

    this._applyStyle(style ? style : this.context, polyline);

    this.canvasElement.appendChild(polyline);
    polyline.outerHTML = polyline.outerHTML; // IE bug
    polyline = this.canvasElement.childNodes[this.canvasElement.childNodes.length - 1];

    return polyline;
  }

  Canvas.prototype.drawArrow = function(from, to, style) {

    var middleX = Math.floor((from.posX + to.posX) / 2);

    var path = "m " + from.posX + "," + (from.posY - 1) + " " +
               "c " + middleX + "," + (from.posY - 1) + " " + middleX + "," + (to.posY - 1) + " " +
               to.posX + "," + (to.posY - 1) + " e";

    var arrow = this.ownerDocument.createElement("<vml:shape path=\"" + path + "\" />"); // IE6
    this.canvasElement.appendChild(arrow);
    arrow.style.width = "10000px";
    arrow.style.height = "10000px";
    arrow.style.top = "0px";
    arrow.style.left = "0px";
    arrow.style.position = "absolute";
    arrow.coordorigin = "0 0";
    arrow.coordsize = "10000 10000";

    this._applyStyle(style ? style : this.context, arrow);

    arrow.outerHTML = arrow.outerHTML; // IE bug
    arrow = this.canvasElement.childNodes[this.canvasElement.childNodes.length - 1];

    return arrow;
  }

  Canvas.prototype.drawRect = function(x, y, width, height, style) {
    return this.drawPolyLine([{posX: x, posY: y},
                              {posX: x + width, posY: y},
                              {posX: x + width, posY: y + height},
                              {posX: x, posY: y + height},
                              {posX: x, posY: y}],
                              style);
  }

} else {
  var Canvas = function() {
    this.canvasElement = document.createElementNS(this.SVG_NAMESPACE, 'svg:svg');
    this.ownerDocument = this.canvasElement.ownerDocument;
    this.resetContext();
  }

  Canvas.prototype.SVG_NAMESPACE = "http://www.w3.org/2000/svg";

  Canvas.prototype.addClassName = function(className) {
    this.canvasElement.setAttribute('class', className);
  }

  Canvas.prototype.setContext = function(context) {
    this.context = context;
  }

  Canvas.prototype._applyStyle = function(style, element) {
    var styleAttr = element.getAttribute('style');
    if (styleAttr == null)
      styleAttr = "";

    for (var key in style) {
      styleAttr += key + ":" + style[key] + ";";
    }
    element.setAttribute('style', styleAttr);
  }

  Canvas.prototype.drawPolyLine = function(points, style) {
    var polyline = this.ownerDocument.createElementNS(this.SVG_NAMESPACE, "svg:polyline");
    var pointsAttr = "";
    for (var i = 0; i < points.length; i++)
      pointsAttr += " " + points[i].posX + "," + points[i].posY;

    polyline.setAttribute('points', pointsAttr);
    this._applyStyle(aCMS.utils.merge(this.context, style), polyline);
    this.canvasElement.appendChild(polyline);
    return polyline;
  }

  Canvas.prototype.drawArrow = function(from, to, style) {
    var arrow = this.ownerDocument.createElementNS(this.SVG_NAMESPACE, "svg:path");

    var middleX = (from.posX + to.posX) / 2;

    arrow.setAttribute("d", "M " + from.posX + "," + from.posY + " " +
                            "C " + middleX + "," + from.posY + " " + middleX + "," + to.posY + " " +
                            to.posX + "," + to.posY);
    this._applyStyle(aCMS.utils.merge(this.context, style), arrow);
    this.canvasElement.appendChild(arrow);
    return arrow;
  }

  Canvas.prototype.drawRect = function(x, y, width, height, style) {
    var rect = this.ownerDocument.createElementNS(this.SVG_NAMESPACE, "svg:rect");

    rect.setAttribute("x", x);
    rect.setAttribute("y", y);
    rect.setAttribute("width", width);
    rect.setAttribute("height", height);
    this._applyStyle(aCMS.utils.merge(this.context, style), rect);
    this.canvasElement.appendChild(rect);
    return rect;
  }
}

Canvas.prototype.resetContext = function() {
    this.setContext({'stroke': '#00F',
                     'fill': 'none',
                     'stroke-width': '2px'});
}

Canvas.prototype.clear = function() {
  while (this.canvasElement.childNodes.length > 0)
    this.canvasElement.removeChild(this.canvasElement.childNodes[0]);
}

Canvas.prototype.getHTMLElement = function() {
  return this.canvasElement;
}






/**
 *
 */
function PhotoViewer(options) {
  options = aCMS.utils.merge({
      'class': '',
      'width': '302px',
      'height': '200px',
      'portraitWidth': '302px'
    },
    options);
  StyledElements.StyledElement.call(this, ['photoLoaded', 'errorLoadingPhoto']);

  this.wrapperElement = document.createElement('div');
  this.wrapperElement.addClassName('photoViewer');
  this.wrapperElement.setStyle({
    "position": "relative",
    "zoom":     1
  });

  this.photo = document.createElement('img');
  this.photo.setStyle({
    "display": "block",
    "width":   options.width,
    "height":  options.height,
    "opacity": 0
  });
  this.wrapperElement.appendChild(this.photo);
  this.width = options.width;
  this.portraitWidth = options.portraitWidth;

  this.photoLoader = document.createElement('img');
  this.photoLoader.setStyle({
    'opacity': 0,
    'width': options.width,
    'height': options.height,
    'position': 'absolute',
    'top': '0px',
    'left': '0px',
    'zoom': 1      // force hasLayout on IE
  });
  this.photoLoader.addEventListener('load', aCMS.utils.bind(this._photoLoaded, this), false);
  this.photoLoader.addEventListener('error', aCMS.utils.bind(this._errorLoadingPhoto, this), false);
  this.wrapperElement.appendChild(this.photoLoader);

  this.loadingIndicator = document.createElement('img');
  this.loadingIndicator.setAttribute('src', '/files/loading.gif');
  this.loadingIndicator.setAttribute('alt', 'Loading...');
  this.loadingIndicator.addClassName('loading hidden');
  this.wrapperElement.appendChild(this.loadingIndicator);

  // centering horizontally and vertically the loading indicator
  this.loadingIndicator.style.position = "absolute";

  this._effectManager = new aCMS.effects.effectManager();
  this._onTransitionFinished = aCMS.utils.bind(this._onTransitionFinished, this);
}
PhotoViewer.prototype = new StyledElements.StyledElement();

PhotoViewer.prototype._centerLoadingIndicator = function() {
  // centering horizontally and vertically the loading indicator
  var top;
  top = this.wrapperElement.clientHeight - this.loadingIndicator.offsetHeight;
  top /= 2;
  this.loadingIndicator.style.top = top + "px";

  var left;
  left = this.wrapperElement.clientWidth - this.loadingIndicator.offsetWidth;
  left /= 2;
  this.loadingIndicator.style.left = left + "px";
}

PhotoViewer.prototype.loadPhoto = function(photoDesc) {
  var desc = 'desc' in photoDesc ? photoDesc.desc : "";

  this._effectManager.cancel();
  this.photoLoader.setAttribute("alt", desc);
  this.photoLoader.setAttribute("title", desc);

  this.nextPhoto = photoDesc;

  if (this.photoLoader.src != photoDesc.url) {
    this.loadingIndicator.removeClassName('hidden');
    this._centerLoadingIndicator();

    this.photoLoader.src = photoDesc.url;
  }
}

PhotoViewer.prototype._photoLoaded = function() {
    this.loadingIndicator.addClassName('hidden');

    if (this.nextPhoto.portrait) {
        this.photoLoader.style.width = this.portraitWidth;
    } else {
        this.photoLoader.style.width = this.width;
    }
    var left = (this.wrapperElement.clientWidth - this.photoLoader.offsetWidth) /2;
    this.photoLoader.style.left =  left + 'px';

    var fadeEffect = new aCMS.effects.fade();
    var command1 = {
        'effect': fadeEffect,
        'type': 'in',
        'fadeLayer': this.photoLoader
    };

    var command2 = {
        'effect': fadeEffect,
        'type': 'out',
        'fadeLayer': this.photo
    };
    var command = {
        'effect': new aCMS.effects.combination(),
        'effects': [command1, command2],
        'endFunc': this._onTransitionFinished,
        'cancelFunc': this._onTransitionFinished
    };

    this._effectManager.run(command);
}

PhotoViewer.prototype._onTransitionFinished = function() {
    this.photo.setAttribute("alt", this.photoLoader.getAttribute("alt"));
    this.photo.setAttribute("title", this.photoLoader.getAttribute("title"));
    this.photo.src = this.photoLoader.src;

    this.currentPhoto = this.nextPhoto;
    this.nextPhoto = null;

    if (this.currentPhoto.portrait) {
        this.photo.style.width = this.portraitWidth;
    } else {
        this.photo.style.width = this.width;
    }
    this.photo.setOpacity(1);
    this.photoLoader.setOpacity(0);

    this.events['photoLoaded'].dispatch(this.currentPhoto);
}

PhotoViewer.prototype._errorLoadingPhoto = function() {
  this.loadingIndicator.addClassName('hidden');

  var photo = this.nextPhoto;
  this.nextPhoto = null;

  this.events['errorLoadingPhoto'].dispatch(photo);
}


/**
 * Photo Browser
 */
function PhotoBrowser(options) {
  options = aCMS.utils.merge({
      'class': '',
      'photoViewer': {},
      'navigationEnabled': true,
      'titleEnabled': true,
      'photoTitleEnabled': true,
      'authorEnabled': true,
      'descEnabled': true
    },
    options);
  StyledElements.StyledElement.call(this, []);

  this.wrapperElement = document.createElement('div');
  this.wrapperElement.addClassName('photoBrowser');

  this.titleElement = document.createElement('h1');
  if (options.titleEnabled)
    this.wrapperElement.appendChild(this.titleElement);

  this.photoTitleElement = document.createElement('h2');
  if (options.photoTitleEnabled)
    this.wrapperElement.appendChild(this.photoTitleElement);

  this.viewer = new PhotoViewer(options.photoViewer);
  this.viewer.insertInto(this.wrapperElement);
  this.viewer.addEventListener('photoLoaded', aCMS.utils.bind(this._photoLoaded, this));

  this.authorMessage = document.createElement('p');
  if (options.authorEnabled)
    this.wrapperElement.appendChild(this.authorMessage);

  this.previousButton = document.createElement('div');
  this.previousButton.addClassName('previousButton');
  this.previousButton.addEventListener('click', aCMS.utils.bind(this.previous, this), true);
  this.viewer.wrapperElement.appendChild(this.previousButton); // TODO

  this.nextButton = document.createElement('div');
  this.nextButton.addClassName('nextButton');
  this.nextButton.addEventListener('click', aCMS.utils.bind(this.next, this), true);
  this.viewer.wrapperElement.appendChild(this.nextButton); // TODO

  this.previousButton.setTextContent('previous');
  this.nextButton.setTextContent('next');

  // Initialization
  if ('gallery' in options) {
    this.changeGallery(options.gallery);
  }

  this.enableNavigation(options.navigationEnabled);
}
PhotoBrowser.prototype = new StyledElements.StyledElement();

PhotoBrowser.prototype.changeGallery = function(newGallery, initialPhoto) {
  initialPhoto = initialPhoto == undefined ? 0 : initialPhoto;

  this.titleElement.setTextContent(newGallery.title);
  this.gallery = newGallery;
  this.loadPhoto(initialPhoto);

  if (this.navigationEnabled)
    this._updateButtons();
}

PhotoBrowser.prototype.authorFormat = "Foto realizada por %(author)s.";

PhotoBrowser.prototype._loadPhoto = function(index) {
  var photo = this.gallery.photos[index];

  this.viewer.loadPhoto(photo);

  if (('desc' in photo) && (photo.desc != '')) {
    this.photoTitleElement.setTextContent(photo.desc);
    this.photoTitleElement.removeClassName('hidden');
  } else {
    this.photoTitleElement.addClassName('hidden');
  }

  if ('author' in photo) {
    var msg = aCMS.utils.interpolate(this.authorFormat,
                   {author: photo.author});
    this.authorMessage.setTextContent(msg);
    this.authorMessage.removeClassName('hidden');
  } else {
    this.authorMessage.addClassName('hidden');
  }

  if (this.navigationEnabled)
    this._updateButtons();
}

PhotoBrowser.prototype._photoLoaded = function(photo) {
}

PhotoBrowser.prototype.loadPhoto = function(index) {
  if (index < 0) {
    index = 0;
  } else if (index >= this.gallery.photos.length) {
    index = this.gallery.photos.length - 1;
  }

  this._loadPhoto(index);
  this.currentPhotoIndex = index;
}

PhotoBrowser.prototype.isFirst = function() {
  return this.currentPhotoIndex == 0;
}

PhotoBrowser.prototype.isLast = function() {
  return (this.currentPhotoIndex + 1) == this.gallery.photos.length;
}

PhotoBrowser.prototype.next = function() {
  if (this.isLast())
    return;

  this._loadPhoto(++this.currentPhotoIndex);
}

PhotoBrowser.prototype.previous = function() {
  if (this.isFirst())
    return;

  this._loadPhoto(--this.currentPhotoIndex);
}

PhotoBrowser.prototype._updateButtons = function() {
  if (this.isFirst())
    this.previousButton.addClassName("hidden");
  else
    this.previousButton.removeClassName("hidden");

  if (this.isLast())
    this.nextButton.addClassName("hidden");
  else
    this.nextButton.removeClassName("hidden");
}

PhotoBrowser.prototype.enableNavigation = function(enable) {
  this.navigationEnabled = enable;
  if (enable) {
    this._updateButtons();
  } else {
    this.previousButton.addClassName("hidden");
    this.nextButton.addClassName("hidden");
  }
}


function ImageMapManagerArea(nodeDesc, canvasElement, box) {
  this.nodeDesc = nodeDesc;
  this.canvasElement = $(canvasElement);
  this.box = box;
  this.mapElement = $(nodeDesc.id);

  this._init();
}

if (aCMS.Browser.engine == "Internet Explorer") {
  ImageMapManagerArea.prototype._init = function() {
  }
} else if (aCMS.Browser.engine == "Opera") {
  ImageMapManagerArea.prototype._init = function() {
    this.canvasElement.style.pointerEvents = 'all';
  }
} else {
  ImageMapManagerArea.prototype._init = function() {
    this.canvasElement.setAttribute('pointer-events', 'all');
  }
}

if (aCMS.Browser.engine != "Internet Explorer") {
  ImageMapManagerArea.prototype.addEventListener = function(event, handler) {
    this.canvasElement.addEventListener(event, handler, true);
  }
} else {
  ImageMapManagerArea.prototype.addEventListener = function(event, handler) {
    this.mapElement.addEventListener(event, handler, true);
    if (event == "mouseout") {
      this.canvasElement.addEventListener(event, handler, true);
    }
  }
}

ImageMapManagerArea.prototype.getGallery = function() {
  return this.nodeDesc.gallery;
}

ImageMapManagerArea.prototype.getInitialPhoto = function() {
  return this.nodeDesc.photo;
}

ImageMapManagerArea.prototype.hidden = function() {
  this.canvasElement.style.visibility = "hidden";
}

ImageMapManagerArea.prototype.show = function() {
  this.canvasElement.style.visibility = "visible";
}

ImageMapManagerArea.prototype.getTopPosition = function() {
  return this.box.topPosition;
}

ImageMapManagerArea.prototype.getBottomPosition = function() {
  return this.box.bottomPosition;
}

ImageMapManagerArea.prototype.getLeftPosition = function() {
  return this.box.leftPosition;
}

ImageMapManagerArea.prototype.getRightPosition = function() {
  return this.box.rightPosition;
}

/**
 * ImageMapManager
 */
function ImageMapManager(conf) {
  $(document.body);
  this.conf = conf;

  this.canvas = new Canvas();
  var canvasStyle = this.getCanvasElement().style;
  canvasStyle.position = "absolute";
  canvasStyle.left     = "0px";
  canvasStyle.top      = "0px";
  this.canvas.setContext({'fill': "#00F",
                          'fill-opacity': 0.4,
                          'stroke': '#000',
                          'stroke-width': "2px"});

  this.mapImage = $(this.conf.mapImage);
  var cssStyle = document.defaultView.getComputedStyle(this.mapImage, null);
  this.offsetTop = cssStyle.getPropertyCSSValue('margin-top').getFloatValue(CSSPrimitiveValue.CSS_PX);
  this.offsetLeft = cssStyle.getPropertyCSSValue('margin-left').getFloatValue(CSSPrimitiveValue.CSS_PX);

  this._process();
  this._resize();
  this.mapImage.addEventListener('load', aCMS.utils.bind(this._resize, this), true);


  // TODO Temporal
  this.photoBrowser = new PhotoBrowser({'navigationEnabled': false});
  this.photoBrowser.addClassName('popupBrowser');
  this.photoBrowser.addClassName('hidden');

  this.photoBrowser.insertInto(document.body);
}

ImageMapManager.prototype._resize = function(element) {
  var canvasElement = this.canvas.getHTMLElement();
  canvasElement.style.width  = this.mapImage.parentNode.offsetWidth + "px";
  canvasElement.style.height = this.mapImage.parentNode.offsetHeight + "px";
}

ImageMapManager.prototype._processRect = function(node) {
  var coords = node.coords;
  var points = coords.split(',');
  points = points.map(function(a){return parseInt(a)});

  var canvasElement = this.canvas.drawRect(points[0] - 0.5 + this.offsetLeft,  // x
                                           points[1] - 0.5 + this.offsetTop,   // y
                                           points[2] - points[0] + 2,  // width
                                           points[3] - points[1] + 2); // height

  canvasElement.style.visibility = "hidden";
  var box = {
    'leftPosition':   points[0] - 10,
    'rightPosition':  points[2] + 10,
    'topPosition':    points[1] - 10,
    'bottomPosition': points[3] + 10
  };
  return new ImageMapManagerArea(node, canvasElement, box);
}

ImageMapManager.prototype._processPoly = function(node) {
  var coords = node.coords.split(',');
  coords = coords.map(function(a){return parseInt(a)});
  var points = new Array();
  var len = coords.length / 2;

  var box = {
    'leftPosition':   coords[0],
    'rightPosition':  coords[0],
    'topPosition':    coords[1],
    'bottomPosition': coords[1]
  };

  for (var i = 0; i < len; i++) {
    if (coords[i*2] < box.leftPosition) {
      box.leftPosition = coords[i*2];
    } else if (coords[i*2] > box.rightPosition) {
      box.rightPosition = coords[i*2];
    }

    if (coords[i*2 + 1] < box.topPosition) {
      box.topPosition = coords[i*2 + 1];
    } else if (coords[i*2 + 1] > box.bottomPosition) {
      box.bottomPosition = coords[i*2 + 1];
    }

    points.push({posX: coords[i*2], posY: coords[i*2 + 1]});
  }

  box.topPosition -= 10;
  box.rightPosition += 10;
  box.bottomPosition += 10;
  box.leftPosition -= 10;

  var canvasElement = this.canvas.drawPolyLine(points);
  canvasElement.style.visibility = "hidden";

  return new ImageMapManagerArea(node, canvasElement, box);
}

ImageMapManager.prototype._process = function() {
  var len = this.conf.map.length;
  for (var i = 0; i < len ; i++) {
    var node = this.conf.map[i];

    var mapArea;
    switch (node.shape) {
    case "rect":
      mapArea = this._processRect(node);
      break;
    case "poly":
      mapArea = this._processPoly(node);
      break;
    case "circle":
      mapArea = this._processCircle(node);
      break;
    }

    if ('gallery' in node) {
      var context = {manager: this, mapArea: mapArea};
      mapArea.addEventListener('mouseover',
                               aCMS.utils.bind(function(e) {
                                 this.manager._enterArea(this.mapArea, e);
                               }, context),
                               true);

      mapArea.addEventListener('mouseout',
                               aCMS.utils.bind(function(e) {
                                 if (e.relatedTarget != this.mapArea.mapElement && e.relatedTarget != this.mapArea.canvasElement)
                                   this.manager._exitArea(this.mapArea, e);
                               }, context),
                               true);
    } else {
      area.href = node.href;
      area.addEventListener('click',
                            function(e) {
                              window.location = e.target.href;
                            },
                            true);
    }
  }
}

ImageMapManager.prototype._enterArea = function(mapArea, e) {
  if (this.previous == mapArea)
    return;

  if (this.previous)
    this.previous.hidden();

  mapArea.show();
  this.previous = mapArea;


  this.photoBrowser.removeClassName('hidden');
  this.photoBrowser.changeGallery(this.conf.galleries[mapArea.getGallery()], mapArea.getInitialPhoto());

  var rel  = document.body.getRelativePosition(this.mapImage);

  // Vertical position
  var verticalMiddle = (mapArea.getTopPosition() + mapArea.getBottomPosition()) / 2;
  var top = rel.posY + verticalMiddle - (this.photoBrowser.wrapperElement.offsetHeight / 2);

  var viewport = aCMS.utils.getViewportInfo();

  var sobresalePorArriba = top < viewport.top;
  var sobresalePorAbajo = (top + this.photoBrowser.wrapperElement.offsetHeight) > viewport.bottom;
  if (sobresalePorArriba && !sobresalePorAbajo) {
    //top = rel.posY + mapArea.getTopPosition();
    top = viewport.top + 10;
  } else if (!sobresalePorArriba && sobresalePorAbajo) {
    top = viewport.bottom - this.photoBrowser.wrapperElement.offsetHeight - 10;
    //top = top = rel.posY + mapArea.getBottomPosition() - this.photoBrowser.wrapperElement.offsetHeight;
  }

  // Horizontal position
  var left = rel.posX + mapArea.getRightPosition();

  if ((left + this.photoBrowser.wrapperElement.offsetWidth) > document.body.offsetWidth) {
    left = rel.posX + mapArea.getLeftPosition() - this.photoBrowser.wrapperElement.offsetWidth;
  }

  this.photoBrowser.wrapperElement.style.top  = top + 'px';
  this.photoBrowser.wrapperElement.style.left = left + 'px';
}

ImageMapManager.prototype._exitArea = function(mapArea, e) {
  if (this.previous) {
    this.previous.hidden();
    this.previous = null;
  }

  this.photoBrowser.addClassName('hidden');
}

ImageMapManager.prototype.getCanvasElement = function() {
  return this.canvas.getHTMLElement();
}

/**
 *
 */
function SlideshowBrowser(conf) {
  this.conf = conf;

  this.wrapperElement = document.createElement('div');
  this.wrapperElement.addClassName('slideshow');

  this.photoBrowser = new PhotoBrowser({'navigationEnabled': false,
      'titleEnabled': false,
      'photoTitleEnabled': false,
      'authorEnabled': false,
      'photoViewer': {
              'width':  '360px',
              'portraitWidth': '160px',
              'height': '240px'
          }
      });
  if (this.conf.photoBrowserElement) {
      this.photoBrowser.replace($(this.conf.photoBrowserElement));
  } else {
      this.photoBrowser.insertInto(this.wrapperElement);
  }
  this.photoBrowser.changeGallery(this.conf.gallery);

  if (this.conf.listElement) {
    this.list = $(this.conf.listElement);
    this.list.innerHTML = "";
  } else {
    this.list = document.createElement('div');
    this.wrapperElement.appendChild(this.list);
  }
  this.list.addClassName('photoList');

  for (var key = 0; key < this.conf.gallery.photos.length; key++) {
    var photo = this.conf.gallery.photos[key];
    var miniature = document.createElement('img');
    miniature.addClassName('thumb');
    if (photo.portrait) {
      miniature.style.width = '45px';
      miniature.style.height = '67px';
    } else {
      miniature.style.width = '100px';
      miniature.style.height = '67px';
    }
    miniature.src = photo.thumb;
    this.list.appendChild(miniature);

    miniature.addEventListener('click',
        aCMS.utils.bind(function() {
                this.browser.changeCurrentPhoto(this.index);
            }, {
                browser: this,
                index: key
               }),
        true);
  }

  this._currentPhoto = 0;
  this.list.childNodes[0].addClassName('selected');
}
SlideshowBrowser.prototype = new StyledElements.StyledElement();

SlideshowBrowser.prototype.changeCurrentPhoto = function(index) {
    this.list.childNodes[this._currentPhoto].removeClassName('selected');
    this.photoBrowser.loadPhoto(index);
    this._currentPhoto = index;
    this.list.childNodes[index].addClassName('selected');
}


/**
 *
 */
function ProgressIndicator(options) {
    options = aCMS.utils.merge({
        'class': '',
        'totalSteps': 100,
        'initialWidth': 208,
        'initialHeight': 13,
        'progressImage': '/files/progress.gif',
        'progressbgImage': '/files/progressbg.png'
    },
    options);

    this._currentStep = 0;
    this.totalSteps = options.totalSteps;

    this.wrapperElement = document.createElement('div');
    this.wrapperElement.addClassName('progressIndicator');
    this.wrapperElement.style.position = 'relative';
    this.wrapperElement.style.width = options.initialWidth + 'px';
    this.wrapperElement.style.height = options.initialHeight + 'px';
    this.wrapperElement.style.background = 'transparent url("' + options.progressbgImage + '")';

    this.progressElement = document.createElement('img');
    this.progressElement.style.position = 'absolute';
    this.progressElement.style.left = '0px';
    this.progressElement.src = options.progressImage;

    this.wrapperElement.appendChild(this.progressElement);
    this._updateProgress();
}
ProgressIndicator.prototype = new StyledElements.StyledElement();

ProgressIndicator.prototype._updateProgress = function() {
    var percentage = this._currentStep / this.totalSteps;
    var width = Math.round(this.progressElement.offsetWidth * percentage);
    this.progressElement.style.clip =
        'rect(0px,' + width + 'px,' + this.progressElement.offsetHeight + 'px,0px)';
}

ProgressIndicator.prototype.incProgress = function() {
    this._currentStep++;
    this._updateProgress();
}

ProgressIndicator.prototype.reset = function() {
    this._currentStep = 0;
    this._updateProgress();
}
