﻿/*
O script a seguir trata de algo que aqui foi chamado de "restrições de produtos por dimensão".

Para entender o que é uma restrição, considere uma camisa que tem opções em duas dimensões: cor e tamanho.
A camisa azul está disponível nos tamanhos P, M e G.
A camisa vermelha está disponível nos tamanhos P e G.
A camisa verde está disponível no tamanho M, somente.
Quando o usuário selecionar a cor vermelha, somente as opções de tamanho P e G devem estar disponíveis para seleção.
Quando o usuário selecionar o tamanho M, somente as cores verde e azul devem estar disponíveis.

Este script implementa funcionalidade para habilitar ou desabilitar opções (cor, sabor, tamanho) de acordo com uma matriz
de restrições que é criada no servidor e passada para esse script.

Foi criada a classe MatrizRestricoes, que contém métodos que usam funcionalidades implementadas no script ClientGrade.js.
Qualquer alteração no funcionamento do script ClientGrade.js deve levar em consideração o impacto que pode ser causado aqui.

O processo de MatrizRestricoes tem início dentro do método de instanciação de GradeManager (ClientGrade.js). Após ter o método init
invocado, a própria matriz de restrições cuida de fazer seu setup, e nenhum de seus métodos deve ser chamado em qualquer outro lugar.

--

CONVENÇÕES DE NOMENCLATURA:

"Dimensões" são classes de opções de produtos; um sabor, uma cor, um tamanho.

"Opções" são opções de dimensões (vermelho e azul são opções da dimensão Cor). Em termos de código,
são instâncias de uma das respectivas classes (Sabor, Cor, Tamanho), definidas em ClientGrade.js.

--

As restrições são organizadas em um array multi-dimensional. Os valores do enum Enum.Dimensao definem
os índices das opções de cada dimensão no array. Se no enum a dimensão Tamanho tem o valor 0, todas as opções
de tamanho serão sempre alocadas no índice 0 dos arrays internos. Exemplo:

Enum.Dimensao = {Tamanho: 0, Sabor: 1, Cor: 3};

var restricoes = [ // Matriz de restrições para uma camisa.
['P', null, 'azul'], // Na verdade são usados os IDs e não os nomes.
['M', null, 'azul'],
['G', null, 'azul'],
['P', null, 'vermelho'],
['G', null, 'vermelho'],
['M', null, 'verde']
];

Assim, opções devem ser informadas para todas as dimensões. Quando o produto não tiver opções para uma determinada
dimensão (assim como camisa não tem opções de sabor), as opções para aquela dimensão devem ser preenchidas com null.
*/

var MatrizRestricoes = {

    /*
    Mensagens de erro/excessão.
    */
    erros: {
        addArgsLength: 'A quantidade IDs de dimensões passadas como argumento deve corresponder à quantidade de dimensões em Enum.Dimensao.',
        gradeMgrUndefined: 'MatrizRestricoes precisa de uma instância de GradeManager para funcionar.'
    },

    /*
    Lida com restrições entre as dimensões dos produtos.
    @gradeManager: Object: Uma instância de GradeManager, que contém métodos necessário para manipulação da interface.
    @matriz: Array: Opcional. A matriz de restrições.
    */
    init: function(gradeManager, matrizRestricoes) {
        if (!gradeManager)
            throw MatrizRestricoes.erros.gradeMgrUndefined;

        this.gradeManager = gradeManager;
        this.restricoes = matrizRestricoes || [];

        // Adiciona handlers necessários.
        var opcoes = gradeManager.GetAllEsp();
        var todosIndisponiveis = true;
        for (var i = 0, l = opcoes.length; i < l; i++) {
            var el = opcoes[i].GetElement();
            this.adicionarHandler(el, 'click', this.clickOpcao, opcoes[i]);
            this.adicionarHandler(el, 'mouseout', this.mouseOutOpcao, opcoes[i]);
            this.adicionarHandler(el, 'mouseover', this.mouseOverOpcao, opcoes[i]);
            var restricoesOpcaoEvento = this.pesquisarRestricoesPorOpcao(opcoes[i]);
            if (restricoesOpcaoEvento == "") {
                el.className = "LinkDisable";
            } else {
                todosIndisponiveis = false;
            }
        }


        //inicialização trazida do ClientGrade.js para cá.
        //Motivo -> atualizar informações depois de todo processamento do INIT.
        this.setarInicial();
        var liProds = GetProdutosByPreSelecionados(this.gradeManager.Produtos);
        if (liProds == null || liProds.length == 0) {
            var liProds = new Array();
            liProds[0] = GetProdutoByPrincipal(this.gradeManager.Produtos);
            liInfoprod = this.gradeManager.Produtos;
        }
        else {
            liInfoprod = liProds;
        }
        this.gradeManager.BindInfoProds(liInfoprod);
        this.gradeManager.SetImgProduto(liProds);
        this.gradeManager.SetImgMiniProduto(liProds);

        for (var i = 0, l = liProds.length; i < l; i++) {
            var prod = liProds[i];
            if (prod.ProdDisponivel)
                todosIndisponiveis = false;
        }

        //Se todas as opções forem indisponíveis, esconde o box de opções
        if (todosIndisponiveis) {
            var parse = this;
            jQuery(function() {
                var divDivOpc = jQuery('div[id*=DivOpcoes]').get(0);
                if (divDivOpc) {
                    divDivOpc.style.display = 'none';
                }

                var divProdValido = document.getElementById('divProdValido');
                var divProdInvalido = document.getElementById('divProdInvalido');
                var divImgIndisponivel = GetById("divBoxNotAvailable");
                var SpInfo = GetById("SpInfo");
                var eleDisponibilidade = GetById(parse.gradeManager.PrefixId + "Disponibilidade");
                // Produto está indisponível.
                divProdValido.style.display = 'none';
                divProdInvalido.style.display = 'block';
                divImgIndisponivel.style.display = 'block';
                eleDisponibilidade.innerHTML = "Indisponível "
                jQuery('.divTextNotAvailable').html("Desculpe, este item não está disponível");
            });
        }
    },
    /*
    Caso só exista uma opção em cada dimensão, seleciona esta opção, 
    isto se ela estiver disponível
    */
    setarInicial: function() {
        //Enum.Dimensao = {Tamanho: 0, Sabor: 1, Cor: 3};
        var opcoes = this.gradeManager.GetAllEsp();
        //Busca array de cada tipo
        var Cor = this.filtrarArray(opcoes, function() {
            return this.Type == 3;
        });
        var Sabor = this.filtrarArray(opcoes, function() {
            return this.Type == 1;
        });
        var Tamanho = this.filtrarArray(opcoes, function() {
            return this.Type == 0;
        });

        //Faz verificação de cada tipo para ver se está em restrição
        //Se estiver seleciona
        var contagem, contagem2, contagem3, indice, quantidade;
        contagem = 0;
        quantidade = 0;
        //COR
        for (var i = 0; i < Cor.length; i++) {
            var restricoesOpcao = this.pesquisarRestricoesPorOpcao(Cor[i]);
            if (this.opcaoEstaEmRestricoes(Cor[i], restricoesOpcao)) {
                contagem++;
                indice = i;
            }
        }
        if (contagem == 1) {
            Cor[indice].Selecionar();
            quantidade++;
        }
        //SABOR
        contagem2 = 0;
        indice = 0;
        for (var i = 0; i < Sabor.length; i++) {
            var restricoesOpcao = this.pesquisarRestricoesPorOpcao(Sabor[i]);
            if (this.opcaoEstaEmRestricoes(Sabor[i], restricoesOpcao)) {
                contagem2++;
                indice = i;
            }
        }
        if (contagem2 == 1) {
            Sabor[indice].Selecionar();
            quantidade++;
        }
        //Tamanho
        contagem3 = 0;
        indice = 0;
        for (var i = 0; i < Tamanho.length; i++) {
            var restricoesOpcao = this.pesquisarRestricoesPorOpcao(Tamanho[i]);
            if (this.opcaoEstaEmRestricoes(Tamanho[i], restricoesOpcao)) {
                contagem3++;
                indice = i;

            }
        }
        if (contagem3 == 1) {
            Tamanho[indice].Selecionar();
            quantidade++;
        }
        var eleDisponibilidade = GetById(this.gradeManager.PrefixId + "Disponibilidade");
        if (quantidade == 1 || (quantidade == 2 &&
                            ((contagem == 1 && contagem2 == 1)) ||
                            ((contagem2 == 1 && contagem3 == 1)) ||
                            ((contagem == 1 && contagem3 == 1))
                           )
       ) {
            var Prod = this.gradeManager.Produtos[0];
            if (Prod.Disponibilidade != null && Prod.Disponibilidade != "" && Prod.Disponibilidade.length > 0) {
                eleDisponibilidade.innerHTML = Prod.Disponibilidade;
            }
            else {
                eleDisponibilidade.innerHTML = "Envio imediato ";
            }
        }
        else if ((Tamanho.length == 0 && Sabor.length == 0) || (Tamanho.length == 1 && Sabor.length == 1)) {
            var Prod = this.gradeManager.Produtos[0];
            if (Prod.Disponibilidade != null && Prod.Disponibilidade != "" && Prod.Disponibilidade.length > 0) {
                eleDisponibilidade.innerHTML = Prod.Disponibilidade;
            }
            else {
                eleDisponibilidade.innerHTML = "Envio imediato ";
            }
        }
        else {
            eleDisponibilidade.innerHTML = "Escolha sua preferência abaixo ";
        }
    },
    /*
    Filtra um vetor de acordo com uma função.
    @array: Array: O vetor a ser filtrado.
    @fn: Function: A função que filtra o array. A função é chamada uma vez para cada item no array e o contexto (this) é o item. A função deve retornar true caso o elemento deva permanecer no array; do contrário, deve retornar false.
    @return: Array: Uma versão do array onde os elementos foram filtrados segundo os critérios da função.
    */
    filtrarArray: function(array, fn) {
        var arrayFiltrado = [];
        for (var i = 0, l = array.length; i < l; i++) {
            if (fn.call(array[i])) {
                arrayFiltrado.push(array[i]);
            }
        }
        return arrayFiltrado;
    },

    foreach: function(array, fn) {
        for (var i = 0, l = array.length; i < l; i++) {
            fn.call(array[i], i);
        }
    },

    /*
    Verifica se uma opção se encontra na lista de restrições informada.
    @opcao: Object: Uma opção (instância de Tamanho, Sabor ou Cor).
    @restricoes: Array: A lista de restrioções a ser verificada.
    @return: Bool: true se a opção está na lista de restrições; do contrário, false.
    */
    opcaoEstaEmRestricoes: function(opcao, restricoes) {
        for (var i = 0; i < restricoes.length; i++) {
            if (restricoes[i][opcao.Type] == opcao.Id) return true;
        }
        return false;
    },

    /*
    Pesquisa a matriz de restrições buscando todos os registros relacionados à opção informada.
    @opcao: Object: Uma opção (instância de Tamanho, Sabor, ou Cor).
    @return: Array: As restrições relacionadas com a opção informada.
    */
    pesquisarRestricoesPorOpcao: function(opcao) {
        return this.filtrarArray(this.restricoes, function() {
            return this[opcao.Type] == opcao.Id;
        });
    },

    /*
    Pesquisa a matriz de restrições buscando os registros relacionados às opções informadas.
    @opcao: Array: As opções para as quais procurar restrições relacionadas (instâncias de Tamanho, Sabor, ou Cor).
    @return: Array: As restrições relacionadas com as opções informada.
    */
    pesquisarRestricoesPorOpcoes: function(opcoes) {
        var restricoes = [];
        var self = this;
        self.foreach(opcoes, function() {
            var opcao = this;
            var restricoesOpcao = self.pesquisarRestricoesPorOpcao(opcao);
            self.foreach(restricoesOpcao, function() {
                var restricaoOpcao = this;
                restricoes.push(restricaoOpcao);
            });
        });
        return restricoes;
    },

    // HELPERS DE MANIPULAÇÃO DE INTERFACE E DOM.

    /*
    Adiciona um handler de envento a um elemento.
    @el: Object: O elemento alvo.
    @type: String: O nome do evento, sem o prefixo "on". Exemplo: "click", "mouseover", "keypress".
    @fn: Function: A função responsável por tratar o evento. Recebe uma referência ao objeto do evento como parâmetro.
    @dadosAdicionais: Object: Opcional Dados adicionais a serem passados para a função, além do objeto do evento.
    */
    adicionarHandler: function(el, type, fn, dadosAdicionais) {
        if (!el)
            return;

        var self = this;
        var _fn = function(e) {
            fn.call(self, el, dadosAdicionais);
        }

        if (el.addEventListener)
            el.addEventListener(type, _fn, false);
        else if (el.attachEvent)
            el.attachEvent('on' + type, _fn);
    },

    /*
    Handler do evento de click dos elementos de opções de dimensão.
    @e: Event: Uma referência ao objeto do evento click.
    @opcao: Object: Uma referência ao objeto de opção (Sabor, Tamanho, Cor) relacionada ao elemento que disparou o evento.
    */
    clickOpcao: function(e, opcao) {
        opcao.Selecionar();
        this.mouseOutOpcao(e, opcao);
        this.setarIndisponivel(opcao);
        this.initiDisable();
    },

    /*
    Handler do evento de mouseout dos elementos de opções de dimensão.
    @e: Event: Uma referência ao objeto do evento mouseout.
    @opcao: Object: Uma referência ao objeto de opção (Sabor, Tamanho, Cor) relacionada ao elemento que disparou o evento.
    */
    mouseOutOpcao: function(e, opcao) {

        /*
        1. Se não houver opções selecionadas, todas as opções ficam habilitadas;
        do contrário, ficam habilitadas somente as opções que se encontram na lista de restrições de todas as opções selecionadas.
        */

        var self = this;
        var opcoes = this.gradeManager.GetAllEsp();
        var opcoesSelecionadas = this.filtrarArray(opcoes, function() { return this.Selecionado; });
        var haOpcoesSelecionadas = opcoesSelecionadas.length > 0;

        // Habilita todas as opções.
        this.foreach(opcoes, function() {
            this.Habilitar();
        });

        if (haOpcoesSelecionadas) {
            this.foreach(opcoesSelecionadas, function() {
                var opcaoSelecionada = this;
                var restricoes = self.pesquisarRestricoesPorOpcao(opcaoSelecionada);

                for (var i = 0; i < opcoes.length; i++) {
                    if (opcoes[i].Type != opcaoSelecionada.Type && !self.opcaoEstaEmRestricoes(opcoes[i], restricoes)) {
                        opcoes[i].Desabilitar();
                    }
                }
            });
        }

        this.setarIndisponivel();
        this.initiDisable();

    },

    /*
    Handler do evento de mouseover dos elementos de opções de dimensão.
    @e: Event: Uma referência ao objeto do evento mouseover.
    @opcao: Object: Uma referência ao objeto de opção (Sabor, Tamanho, Cor) relacionada ao elemento que disparou o evento.
    */
    mouseOverOpcao: function(e, opcao) {

        /*
        1. Opções da mesma dimensão não têm seus status alterados.
        2. As opções das outras dimensões que estão fora da lista de restrições da opção que recebeu mouse over ficam desabilitadas.
        */

        var opcoes = this.gradeManager.GetAllEsp();
        var opcoesSelecionadas = this.filtrarArray(opcoes, function() { return this.Selecionado; });
        var haOpcoesSelecionadas = opcoesSelecionadas.length > 0;

        var restricoesOpcaoMouseOver = this.pesquisarRestricoesPorOpcao(opcao);
        var restricoesOpcoesSelecionadas = this.pesquisarRestricoesPorOpcoes(opcoesSelecionadas);

        for (var i = 0; i < opcoes.length; i++) {
            if (opcoes[i].Type != opcao.Type) {
                if (!this.opcaoEstaEmRestricoes(opcoes[i], restricoesOpcaoMouseOver) && !this.opcaoEstaEmRestricoes(opcoes[i], restricoesOpcoesSelecionadas))
                    opcoes[i].Desabilitar();
                else
                    opcoes[i].Habilitar();
            }
        }
        this.setarIndisponivel(opcao);
        this.initiDisable();
        e.className = "LinkOver";

    },

    /*
    Mostra os indicadores de que o produto está indisponível de acordo com a opção informada.
    @opcaoEvento: Object: Opcional. A opção (uma instância de Tamanho, Sabor ou Cor) alvo de um evento mouseover, mouseout, click.
    */
    setarIndisponivel: function(opcaoEvento) {
        //Verifica Lista de disponíveis.. 
        //Se existir um disponível então a grade esta disponível.
        var liProds = GetProdutosByPreSelecionados(this.gradeManager.Produtos);
        var disponivel = false;
        for (var i = 0, l = liProds.length; i < l; i++) {
            if (liProds[i].ProdDisponivel) {
                disponivel = true;
            }
        }

        var self = this;

        var opcoes = this.gradeManager.GetAllEsp();
        var opcoesSelecionadas = this.filtrarArray(opcoes, function() { return this.Selecionado; });

        var opcoesSelecionadasSaoValidas = true;

        // Verifica se opcaoEvento foi informada. Se não, passa a ser a primeira opção selecionada.
        var opcaoEvento = opcaoEvento || (opcoesSelecionadas.length > 0 ? opcoesSelecionadas[0] : undefined);

        // Se existe opção de evento, então deve ser considerado os selecionados e a opção do mouse over
        if (opcaoEvento) {
            var restricoesOpcaoEvento = this.pesquisarRestricoesPorOpcao(opcaoEvento);
            //Se existem o~ções selecionadas
            if (opcoesSelecionadas.length > 0)
                this.foreach(opcoesSelecionadas, function() {
                    var opcaoSelecionada = this;
                    if (opcaoSelecionada.Type != opcaoEvento.Type && !self.opcaoEstaEmRestricoes(opcaoSelecionada, restricoesOpcaoEvento)) {
                        opcoesSelecionadasSaoValidas = false;
                    } else {
                        if (restricoesOpcaoEvento == "") {
                            opcoesSelecionadasSaoValidas = false;
                        }
                    }
                });
            //se não existir, então deve verificar se a opção do evento tem restriçoes
            else {
                this.foreach(opcoes, function() {
                    if (restricoesOpcaoEvento == "") {
                        opcoesSelecionadasSaoValidas = false;
                    }
                });
            }
        }
        //Se não existir opção de evento 
        else {
            opcoesSelecionadasSaoValidas = false;
            var self = this;
            this.foreach(opcoes, function() {
                var opcaoSelecionada = this;
                var restricoesOpcaoEvento = self.pesquisarRestricoesPorOpcao(opcaoSelecionada)
                if (self.opcaoEstaEmRestricoes(opcaoSelecionada, restricoesOpcaoEvento)) {
                    opcoesSelecionadasSaoValidas = true;
                }
            });
        }

        var divProdValido = document.getElementById('divProdValido');
        var divProdInvalido = document.getElementById('divProdInvalido');
        var divImgIndisponivel = GetById("divBoxNotAvailable");
        var SpInfo = GetById("SpInfo");
        var eleDisponibilidade = GetById(this.gradeManager.PrefixId + "Disponibilidade");

        if (opcoesSelecionadasSaoValidas == false) {
            // Produto está indisponível.
            if (divProdValido != null)
                divProdValido.style.display = 'none';
            if (divProdInvalido != null)
                divProdInvalido.style.display = 'block';
            if (divImgIndisponivel != null)
                divImgIndisponivel.style.display = 'block';
            if (eleDisponibilidade != null)
                eleDisponibilidade.innerHTML = "Indisponível "

            //Enum.Dimensao = {Tamanho: 0, Sabor: 1, Cor: 3};
            var cor = this.filtrarArray(opcoesSelecionadas, function() { return this.Type == 3; });
            var sabor = this.filtrarArray(opcoesSelecionadas, function() { return this.Type == 1; });
            var tamanho = this.filtrarArray(opcoesSelecionadas, function() { return this.Type == 0; });

            //Limpa os Nós do box indisponível que aparece em cima da imagem do produto.
            if (SpInfo.hasChildNodes()) {
                while (SpInfo.childNodes.length >= 1) {
                    SpInfo.removeChild(SpInfo.firstChild);
                }
            }

            // Se existir um tamanho selecionado
            // Mostra o tamanho no box
            if (tamanho.length == 1) {
                var div = document.createElement('div');
                var text;

                if (opcaoEvento.Type == 0) {
                    text = document.createTextNode("Tamanho: " + opcaoEvento.Nome);

                } else {
                    text = document.createTextNode("Tamanho: " + tamanho[0].Nome);
                }
                div.appendChild(text);
                SpInfo.appendChild(div);
            } else {
                // Se não existir um tamanho selecionado
                // Mostra o tamanho do evento no box
                if (opcaoEvento.Type == 0) {
                    var div = document.createElement('div');
                    var text;

                    text = document.createTextNode("Tamanho: " + opcaoEvento.Nome);
                    div.appendChild(text);
                    SpInfo.appendChild(div);
                }
            }

            // Se existir um sabor selecionado
            // Mostra o sabor no box
            if (sabor.length == 1) {
                var div2 = document.createElement('div');
                var text;
                if (opcaoEvento.Type == 1) {
                    text = document.createTextNode("Sabor: " + opcaoEvento.Nome);
                } else {
                    text = document.createTextNode("Sabor: " + sabor[0].Nome);
                }
                div2.appendChild(text);
                SpInfo.appendChild(div2);
            } else {
                // Se não existir um sabor selecionado
                // Mostra o sabor do evento no box
                if (opcaoEvento.Type == 1) {
                    var div = document.createElement('div');
                    var text;

                    text = document.createTextNode("Sabor: " + opcaoEvento.Nome);
                    div.appendChild(text);
                    SpInfo.appendChild(div);
                }
            }

            // Se existir uma cor selecionada
            // Mostra a cor no box
            if (cor.length == 1) {
                var div3 = document.createElement('div');
                var text = "";
                if (opcaoEvento.Type == 3) {
                    text = document.createTextNode("Cor: " + opcaoEvento.Nome);
                } else {
                    text = document.createTextNode("Cor: " + cor[0].Nome);
                }
                div3.appendChild(text);
                SpInfo.appendChild(div3);
            } else {
                // Se não existir uma cor selecionada
                // Mostra a cor do evento no box
                if (opcaoEvento.Type == 3) {
                    var div = document.createElement('div');
                    var text;
                    text = document.createTextNode("Cor: " + opcaoEvento.Nome);
                    div.appendChild(text);
                    SpInfo.appendChild(div);
                }
            }
        }
        else {
            // Produto está disponível.
            if (divProdValido != null)
                divProdValido.style.display = 'block';
            if (divProdInvalido != null)
                divProdInvalido.style.display = 'none';
            if (divImgIndisponivel != null)
                divImgIndisponivel.style.display = 'none';

            //Prazo de Disponibilidade do Produto
            var liProds = GetProdutosByPreSelecionados(this.gradeManager.Produtos);
            if (liProds == null || liProds.length == 0) {
                var liProds = new Array();
                liProds[0] = GetProdutoByPrincipal(this.gradeManager.Produtos);
                liInfoprod = this.gradeManager.Produtos;
            }
            else {
                liInfoprod = liProds;
            }

            var Prod = liProds[0];

            if (Prod != null) {

                if (Prod.Disponibilidade != null && Prod.Disponibilidade != "") {
                    eleDisponibilidade.innerHTML = Prod.Disponibilidade;
                }
                else {
                    eleDisponibilidade.innerHTML = "Envio imediato ";
                }
            }
            if (opcoesSelecionadas == 0 && typeof (opcaoEvento) == 'undefined' && this.gradeManager.Produtos.length > 1) {
                eleDisponibilidade.innerHTML = "Escolha sua preferência abaixo ";
            }
        }

    },
    //Disabilita os que não tem restrições
    initiDisable: function() {
        var opcoes = this.gradeManager.GetAllEsp();
        for (var i = 0, l = opcoes.length; i < l; i++) {
            var el = opcoes[i].GetElement();
            var restricoesOpcaoEvento = this.pesquisarRestricoesPorOpcao(opcoes[i]);

            if (restricoesOpcaoEvento == "" && !opcoes[i].Selecionado) {
                el.className = "LinkDisable";
            }

        }
    }


};
