/********************************************************************
 *    .: JavaScript ShowCase :.
 * < http://www.mottaweb.com.br/ >
 * 
 * Este script permite a criação de showcases, slideshows, galerias
 * de imagens e outros efeitos legais no seu site.
 * 
 * @author  Quildreen <quildreen@gmail.com>
 * @version 0.1.0 beta
 * 
 * Copyright (c) 2009 Quildreen <quildreen@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************/

/**
 * Retorna o valor de um elemento xml.
 * @parm {NodeList} parent: o elemento que deve ser pesquisado
 * @parm {String} elm: o elemento que deve ter o valor retornado
 * @return {String} o valor do elemento xml passado
 * @public
 */
function getXMLValue(parent, elm){
	var result = "";
	var elm = parent.getElementsByTagName(elm);
	if (elm.length > 0){
		var childs = elm[0].childNodes;
		for (var i=0; i<childs.length; i++){
			result += childs[i].nodeValue;
		}
	}
	return result;
}

/**
 * Implementa a showcase.
 * @class ShowCase 
 * @constructor
 */
var ShowCase = new Class.create({
	// --------------------------------------------------------------
	// Private Section
	/**
	 * Controla a transição de imagens em períodos regulares
	 * @private
	 */
    __handle_delay: null,
	
	// --------------------------------------------------------------
	// Public Section
	/**
	 * Uma matriz com os items mostrados pela showcase.
	 * @public
	 */
    items: [],
	
	/**
	 * O frame mostrado atualmente.
	 * @public
	 */
    current_frame: 0,
	
	/**
	 * Uma lista com todos os frames da showcase.
	 * Frames são objetos HTMLElement;
	 * @public
	 */
    frames: [],
	
	/**
	 * Inicializa a showcase. Essa função é chamada automaticamente quando
	 * a classe é criada.
	 * @param {HTMLElement | String} container: o objeto que conterá a showcase
	 * [@param {Hash} opções do objeto]
	 * 
	 * @private
	 */
    initialize: function(container){
        // Opções do objeto
        var options = Object.extend({
			// o tempo de espera entre um frame e outro, em segundos
			// um valor igual a 0 indica sem transição.
            delay: 5,
			
			// o efeito de transição usado para o próximo frame
            forward_transition: "fade",
			
			// o efeito de transição usado para o frame anterior
            backward_transition: "fade",
            
			// se verdadeiro e o container especificado não existir, cria o
			// container automaticamente
			auto_create_container: true,
			
			// se verdadeiro, faz uma chamada ao método "draw" depois de terminar
			// o carregamento
            auto_draw: true,
			
			// a url do arquivo xml com os dados da showcase
            filename: null,
			
			// se verdadeiro, carrega os dados do DOM
			load_from_dom: true,
			
			// uma lista com os items da showcase
            items: [],
			
			// o frame inicial
            start_frame: 0,
			
			// a disposição dos ícones no container. Pode ser "horizontal" ou
			// "vertical".
            icons_flow: "vertical",
			
			// se verdadeiro, a showcase moverá os ícones para que o ícone atual
			// fique sempre ao centro.
			center_icons: true
        }, arguments[1] || {});
        
		// inicializa o objeto com as opções especificadas
        this.delay = options.delay;
        this.items = options.items;
        this.current_frame = options.start_frame;
        this.icons_flow = options.icons_flow;
        this.forward_transition = options.forward_transition;
        this.backward_transition = options.backward_transition;
		this.center_icons = options.center_icons;
		
		// verifica se o container existe. Caso não exista e auto_create_container
		// seja verdadeiro, cria o container automaticamente. Se não, gera um erro.
        this.container = $(container);
        if (!this.container && options.auto_create_container) {
            document.write("<div class=\"showcase\" id=\""+container+"\"></div>");
			this.container = $(container);
        }
        else if (!this.container) {
        	alert("Erro: Não foi possível criar o container do showcase.")
        }
		
		// se um arquivo de dados xml for definido, carrega os dados deste arquivo.
        if (options.filename) {
            this.load_from_file(options.filename, {
                auto_draw: options.auto_draw
            });
        }
		// se precisar carregar do DOM
		else if (options.load_from_dom){
			this.load_from_dom(this.container, options.auto_draw)
		}		
		// do contrário, se for preciso desenhar automaticamente, chama a função.
        else if (options.auto_draw) {
        	this.draw();
        }
    },
	
	/**
	 * Carrega os dados de um arquivo xml via ajax.
	 * @param {String} filename: a url do arquivo xml
	 * [@param {Hash} opções da função]
	 * 
	 * @public
	 */
    load_from_file: function(filename){
		this.stopDelay();
		// Opções da função
        var options = Object.extend({
			// se verdadeiro, desenha automaticamente depois de carregar os dados.
            auto_draw: true,
			
			// uma função chamada após os dados terem sido carregados.
            onFinish: null
        }, arguments[1] || {});
		
		this.items = [ ];
		var sc = this;
		
		// Abre o arquivo xml usando Ajax
        new Ajax.Request(filename, {
            method: "get",
            contentType: "application/xml",
            onComplete: function(t){
                var xmlDoc = t.responseXML;
				
				// pega todos os items no arquivo
                var items = xmlDoc.getElementsByTagName("item");
				
				// varre os items e adiciona as informações na matriz de items.
                for (var i = 0; i < items.length; i++) {
					var title = getXMLValue(items[i], "title");
					var image = getXMLValue(items[i], "image");
					var sdesc = getXMLValue(items[i], "short");
					var link = getXMLValue(items[i], "link");
					var icon = getXMLValue(items[i], "icon");
					sc.items.push({
                        title: title,
                        image: image,
                        desc: sdesc,
                        link: link,
                        icon: icon
                    });
                }
				
				// Se a função onFinish estiver definida, chama ela.
                if (Object.isFunction(options.onFinish)) {
                    options.onFinish();
                }
				
				sc.current_frame = 0;
				
				// Se precisar desenhar automaticamente, chama a função de desenho.
                if (options.auto_draw) {
                    sc.draw();
                }
            }
        });
    },
	
	/**
	 * Carrega a partir de um elemento HTML
	 * 
	 * @param {String} element: O elemento HTML que contem os dados.
	 * @param {Boolean} auto_draw: se verdadeiro, desenha automaticamente depois de carregar os dados.
	 * 
	 * @public
	 */
    load_from_dom: function(element, auto_draw){
		this.stopDelay();
		
		auto_draw = (auto_draw === false)?false:true;
		
		this.items = [ ];
		
		var frames = $(element).select(".frame");
		for (var i=0; i<frames.length; i++){
			var title = frames[i].select("h2");
			var images = frames[i].select("img");
			var link = frames[i].select("a");
			var desc = frames[i].select("p");
			if (title.length > 0){ title = title[0].innerHTML }
			if (link.length > 0){ link = link[0].href }
			if (desc.length > 0){ desc = desc[0].innerHTML }
			var icon = "";
			var image = "";
			for (var x=0; x <images.length; x++){
				if (images[x].hasClassName("icon")){
					icon = images[x].src;
				}
				else{
					image = images[x].src;
				}
			}
			this.items.push({
                title: title,
                image: image,
                desc: desc,
                link: link,
                icon: icon
			});
			
			this.current_frame = 0;
			
			if (auto_draw){
				this.draw();
			}
		}
    },
	
	/**
	 * Desenha a showcase na página.
	 * 
	 * @public
	 */
    draw: function(){
		// apaga o conteúdo do container
        this.container.update("");
		
		// varre os items e cria os frames
        for (var i = 0; i < this.items.length; i++) {
            var frame = new Element("div", {
                "class": "showcase-frame"
            });
            frame.setStyle({
                backgroundImage: "url(" + this.items[i].image + ")",
				backgroundRepeat: "no-repeat",
				backgroundPosition: "center center",
                zIndex: (i == this.current_frame) ? 3 : 1
            })
            frame.update("<div class=\"showcase-shadow\">" + this.items[i].title + "</div>" +
            "<div class=\"showcase-subtitle\">" +
            this.items[i].title +
            "</div>" +
            "<div class=\"showcase-description\">" +
            this.items[i].desc +
            "</div>");
			
			// altera a opacidade da descrição
            frame.select("div.showcase-description")[0].setOpacity(0.8);
			
			// seta a função onclick do frame para que o link seja aberto ao se clicar nele
            frame.showcase = this;
            frame.onclick = function(){
                this.showcase.open_link();
            }
			
			// armazena o frame na matriz e insere ele no container
            this.frames[i] = frame;
            this.container.insert(frame);
        }
		
		// cria o container para os ícones e setas
        this.icons = new Element("div", {
            id: "showcase_icons",
            "class": "showcase-icons"
        });
        this.arrow_left = new Element("a", {
            "class": "showcase-arrow-left"
        })//.setOpacity(0.5);
        this.arrow_left.showcase = this;
        this.arrow_left.onclick = function(){
            this.showcase.prior();
        }
        this.arrow_right = new Element("a", {
            "class": "showcase-arrow-right"
        })//.setOpacity(0.5);
        this.arrow_right.showcase = this;
        this.arrow_right.onclick = function(){
            this.showcase.next();
        }
        var nav_container = new Element("div", {
            "class": "showcase-navigation"
        })
        nav_container.insert(this.arrow_left).insert(this.icons).insert(this.arrow_right);
        this.container.insert(nav_container)
        
		// se houver apenas um item ou menos, esconde os ícones e setas
        if (this.items.length <= 1) {
            this.icons.hide();
            this.arrow_left.hide();
            this.arrow_right.hide();
        }
        
        // desenha os ícones
        for (var i = 0; i < this.items.length; i++) {
            var icon = new Element("a", {
                "href": "javascript: void(-1)",
                "class": "showcase-icon"
            });
            icon.setStyle({
                backgroundImage: "url(" + this.items[i].icon + ")",
                opacity: 0.7//,
                //position: (this.center_icons)?"absolute":""
            });
            icon.itemID = i;
            icon.showcase = this;
            icon.onclick = function(){
                this.showcase.goTo(this.itemID);
            }
            this.icons.insert(icon);
        }
		// atualiza a posição dos ícones
        this.update_icons();
        
		// mostra o frame atual
        this.goTo(this.current_frame);
    },
	
	/**
	 * Controla a transição de frames em períodos regulares
	 * 
	 * @private
	 */
    doDelay: function(){
		// se não existir delay, retorna
		if (this.delay <= 0){
			return;
		}
		
		// cria um novo intervalo e deleta o antigo
		this.stopDelay();
        this.__handle_delay = setInterval(this.next.bind(this), this.delay * 1000);
    },
	
	/**
	 * Para a transição de frames em períodos regulares
	 * 
	 * @private
	 */
	stopDelay: function(){
		clearTimeout(this.__handle_delay)
	},
	
	/**
	 * Mostra o próximo frame.
	 * 
	 * @public
	 */
    next: function(){
		this.doDelay();
        this.current_frame++;
        if (this.current_frame >= this.items.length) {
            this.current_frame = 0;
        }
        this.update();
    },
	
	/**
	 * Mostra o frame anterior.
	 * 
	 * @public
	 */
    prior: function(){
		this.doDelay();
        this.current_frame--;
        if (this.current_frame < 0) {
            this.current_frame = this.items.length - 1;
        }
        this.update();
    },
	
	/**
	 * Mostra o frame no índice especificado. Se não existir um frame no
	 * índice especificado, gera um erro.
	 * 
	 * @param {Number} index
	 * @public
	 */
    goTo: function(index){
        this.doDelay();
        if (index == this.current_frame) {
            return
        }
        if (index >= 0 && index < this.items.length) {
            this.current_frame = index;
            this.update();
        }
        else {
            alert("Erro: Índice fora da faixa permitida.")
        }
    },
	
	/**
	 * Mostra o frame especificado usando a transição especificada no objeto.
	 * 
	 * @param {Number} last: o último frame exibido.
	 * @private
	 */
    display_frame: function(last){
		// verifica qual efeito a ser usado
        var use_effect = (last < this.current_frame) ? this.forward_transition : this.backward_transition;
        
		var frame = this.frames[this.current_frame];
        
		// pega a lista de efeitos
		var effects = use_effect.split(" ");
		
		// mostra o frame automaticamente
		var auto_show = true;
        for (var i = 0; i < effects.length; i++) {
            switch (effects[i]) {
                case "fade":
                    new Effect.Appear(frame);
					auto_show = false;
                    break;
                case "move_in_left":
                    frame.setStyle({
                        left: frame.getWidth() + "px"
                    });
                    new Effect.Move(frame, {
                        x: 0,
                        y: 0,
                        mode: 'absolute'
                    });
                    break;
                case "move_in_right":
                    frame.setStyle({
                        left: -frame.getWidth() + "px"
                    });
                    new Effect.Move(frame, {
                        x: 0,
                        y: 0,
                        mode: 'absolute'
                    });
                    break;
                case "move_in_top":
                    frame.setStyle({
                        top: frame.getHeight() + "px"
                    });
                    new Effect.Move(frame, {
                        x: 0,
                        y: 0,
                        mode: 'absolute'
                    });
                    break;
                case "move_in_bottom":
                    frame.setStyle({
                        top: -frame.getHeight() + "px"
                    });
                    new Effect.Move(frame, {
                        x: 0,
                        y: 0,
                        mode: 'absolute'
                    });
                    break;
                case "grow":
                    new Effect.Grow(frame);
					auto_show = false;
                    break;
            }
        }
		if (auto_show) {
			frame.show();
		}
    },
	
	/**
	 * Atualiza a showcase
	 * 
	 * @private
	 */
    update: function(){
        var last = 0;
		// verifica qual foi o último frame mostrado e normaliza os  outros
        for (var i = 0; i < this.frames.length; i++) {
            if (i != this.current_frame && this.frames[i].getStyle("z-index") == 3) {
                last = i;
            }
            else {
                this.frames[i].setStyle({
                    zIndex: 1
                })
            }
        }
		
		// se o último frame for diferente do atual, mostra o novo frame
        if (last != this.current_frame) {
            var frame = this.frames[this.current_frame];
            frame.hide().setStyle({
                zIndex: 3
            });
            this.frames[last].setStyle({
                zIndex: 2
            });
            this.display_frame(last);
        }
		
		// atualiza os ícones
        this.update_icons();
    },
	
	/**
	 * Atualiza os ícones
	 * 
	 * @private
	 */
    update_icons: function(){
		// pega todos os ícones
        var childs = this.icons.select("a.showcase-icon");
		
		// varre os ícones e atualiza
        for (var i = 0; i < childs.length; i++) {
            if (childs[i].itemID == this.current_frame) {
                childs[i].className = "showcase-icon selected";
                childs[i].setOpacity(0.9);
            }
            else {
                childs[i].className = "showcase-icon";
                childs[i].setOpacity(0.7);
            }
        }
		
		if (this.center_icons){
			// altera a posição do ícone para que o ícone atual fique no centro.
			var child_dim = childs[this.current_frame].getDimensions();
			var icons_dim = this.icons.getDimensions();
			
			if (this.icons_flow == "vertical"){
				var off_y = childs[this.current_frame].offsetTop;
				var pos_y = off_y - (icons_dim.height - child_dim.height)/2;
				this.icons.scrollTop = pos_y;
			}
			if (this.icons_flow == "horizontal"){
				var off_x = childs[this.current_frame].offsetLeft;
				var pos_x = off_x - (icons_dim.width - child_dim.width)/2;
				this.icons.scrollLeft = pos_x;
			}
		}
    },
	
	/**
	 * Abre o link do frame atual. Comandos em javascript devem ser precedidos
	 * por um sinal de "$".
	 * 
	 * @private
	 */
    open_link: function(){
		var link = this.items[this.current_frame].link
		if (link) {
			if (link.charAt(0) == "$"){
				eval(link.substr(1));
			}
			else{
            	location = this.items[this.current_frame].link;
			}
        }
    }
});
