/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define */

define({
    /**
     * Errors
     */

    // General file io error strings
    "GENERIC_ERROR"                     : "(error {0})",
    "NOT_FOUND_ERR"                     : "O arquivo não pôde ser encontrado.",
    "NOT_READABLE_ERR"                  : "O arquivo não pôde ser lido.",
    "NO_MODIFICATION_ALLOWED_ERR"       : "O diretório de destino não pode ser modificado.",
    "NO_MODIFICATION_ALLOWED_ERR_FILE"  : "As permissões não permitem que você faça modificações.",
    "FILE_EXISTS_ERR"                   : "O arquivo já existe.",

    // Project error strings
    "ERROR_LOADING_PROJECT"             : "Erro ao carregar o projeto",
    "OPEN_DIALOG_ERROR"                 : "Ocorreu um erro ao mostrar o diálogo de abertura de arquivo. (error {0})",
    "REQUEST_NATIVE_FILE_SYSTEM_ERROR"  : "Ocorreu um erro ao tentar carregar a pasta {0}. (error {1})",
    "READ_DIRECTORY_ENTRIES_ERROR"      : "Ocorreu um erro ao ler o conteúdo da pasta {0}. (error {1})",

    // File open/save error string
    "ERROR_OPENING_FILE_TITLE"          : "Erro ao abrir arquivo",
    "ERROR_OPENING_FILE"                : "Ocorreu um erro ao tentar abrir o arquivo {0}. {1}",
    "ERROR_RELOADING_FILE_TITLE"        : "Erro recarregando as mudanças a partir do disco",
    "ERROR_RELOADING_FILE"              : "Ocorreu um erro ao tentar recarregar o arquivo {0}. {1}",
    "ERROR_SAVING_FILE_TITLE"           : "Erro ao salvar arquivo",
    "ERROR_SAVING_FILE"                 : "Ocorreu um erro ao tentar salvar o arquivo {0}. {1}",
    "ERROR_RENAMING_FILE_TITLE"         : "Erro ao renomear arquivo",
    "ERROR_RENAMING_FILE"               : "Ocorreu um erro ao tentar renomear o arquivo {0}. {1}",
    "INVALID_FILENAME_TITLE"            : "Nome de arquivo inválido",
    "INVALID_FILENAME_MESSAGE"          : "Nomes de arquivos não podem conter os seguintes caracteres: /?*:;{}<>\\|",
    "FILE_ALREADY_EXISTS"               : "O arquivo {0} já existe.",
    "ERROR_CREATING_FILE_TITLE"         : "Erro ao criar arquivo",
    "ERROR_CREATING_FILE"               : "Ocorreu um erro ao tentar criar o arquivo {0}. {1}",

    // Application error strings
    "ERROR_IN_BROWSER_TITLE"            : "Opa! {APP_NAME} não funciona em navegadores ainda.",
    "ERROR_IN_BROWSER"                  : "{APP_NAME} é criado em HTML, mas agora mesmo ele é executado como um aplicativo de desktop para que você possa usá-lo para editar arquivos locais. Por favor, use o shell da aplicação no github.com/adobe/brackets-shell repo para executar {APP_NAME}.",

    // FileIndexManager error string
    "ERROR_MAX_FILES_TITLE"             : "Erro indexando Arquivos",
    "ERROR_MAX_FILES"                   : "O número máximo de arquivos foi indexado. Ações que procuram no índice de arquivos podem funcionar incorretamente.",

    // Live Development error strings
    "ERROR_LAUNCHING_BROWSER_TITLE"     : "Erro abrindo o navegador",
    "ERROR_CANT_FIND_CHROME"            : "O navegador Google Chrome não pôde ser encontrado. Por favor, verifique se ele está instalado.",
    "ERROR_LAUNCHING_BROWSER"           : "Ocorreu um erro ao iniciar o navegador. (error {0})",

    "LIVE_DEVELOPMENT_ERROR_TITLE"      : "Erro de Live Preview",
    "LIVE_DEVELOPMENT_RELAUNCH_TITLE"   : "Conectando ao navegador",
    "LIVE_DEVELOPMENT_ERROR_MESSAGE"    : "Para que a Live Preview conecte, Chrome deve ser aberto com a opção de depuração remota ativada.

Gostaria de reabrir Chrome e permitir a depuração remota?",
    "LIVE_DEV_NEED_HTML_MESSAGE"        : "Abra um arquivo HTML a fim de lançar Live Preview.",
    "LIVE_DEVELOPMENT_INFO_TITLE"       : "Bem-vindo à Live Preview!",
    "LIVE_DEVELOPMENT_INFO_MESSAGE"     : "Live Preview se conecta {APP_NAME} ao seu navegador. Ele lança uma prévia do seu arquivo HTML no navegador, em seguida atualiza a visualização de imediato, quando você editar o código.

Nesta versão inicial de {APP_NAME}, Live Preview só funciona para as edições de arquivos CSS e apenas com Google Chrome. Nós iremos implementá-lo para HTML e JavaScript em breve!

(Você só vai ver esta mensagem uma vez.)",

    "LIVE_DEV_STATUS_TIP_NOT_CONNECTED" : "Live Preview",
    "LIVE_DEV_STATUS_TIP_PROGRESS1"     : "Live Preview: Connectando\u2026",
    "LIVE_DEV_STATUS_TIP_PROGRESS2"     : "Live Preview: Inicializando\u2026",
    "LIVE_DEV_STATUS_TIP_CONNECTED"     : "Desconectando do Live Preview",
    "LIVE_DEV_STATUS_TIP_OUT_OF_SYNC"   : "Live Preview: Clique para desconectar (Salve o arquivo para atualizar)",

    "SAVE_CLOSE_TITLE"                  : "Salvar alterações",
    "SAVE_CLOSE_MESSAGE"                : "Você quer salvar as alterações feitas no documento {0}?",
    "SAVE_CLOSE_MULTI_MESSAGE"          : "Você quer salvar as alterações para os seguintes arquivos?",
    "EXT_MODIFIED_TITLE"                : "Mudanças externas",
    "EXT_MODIFIED_MESSAGE"              : "{0} foi modificado no disco, mas também tem alterações não salvadas em {APP_NAME}.

Qual versão você quer manter?",
    "EXT_DELETED_MESSAGE"               : "{0} foi deletado no disco, mas tem alterações não salvadas em {APP_NAME}.

Você quer manter as suas alterações?",

    // Find, Replace, Find in Files
    "SEARCH_REGEXP_INFO"                : "Use /re/ sintaxe para usar regexp na pesquisa",
    "WITH"                              : "Com",
    "BUTTON_YES"                        : "Sim",
    "BUTTON_NO"                         : "Não",
    "BUTTON_STOP"                       : "Pare",

    "OPEN_FILE"                         : "Abrir Arquivo",
    "CHOOSE_FOLDER"                     : "Escolha uma pasta",

    "RELEASE_NOTES"                     : "Notas de Lançamento",
    "NO_UPDATE_TITLE"                   : "Você está atualizado!",
    "NO_UPDATE_MESSAGE"                 : "Você está executando a versão mais recente de {APP_NAME}.",

    "FIND_IN_FILES_TITLE"               : "- {0} {1} em {2} {3}",
    "FIND_IN_FILES_FILE"                : "arquivo",
    "FIND_IN_FILES_FILES"               : "arquivos",
    "FIND_IN_FILES_MATCH"               : "resultado",
    "FIND_IN_FILES_MATCHES"             : "resultados",
    "FIND_IN_FILES_MAX"                 : " (Mostrando os primeiros {0} resultados)",
    "FIND_IN_FILES_FILE_PATH"           : "Arquivo: {0}",
    "FIND_IN_FILES_LINE"                : "linha: {0}",

    "ERROR_FETCHING_UPDATE_INFO_TITLE"  : "Erro recebendo informações atualizadas",
    "ERROR_FETCHING_UPDATE_INFO_MSG"    : "Houve um problema obtendo a informação de atualização mais recente a partir do servidor. Por favor, verifique se você está conectado à Internet e tente novamente.",

    /**
     * ProjectManager
     */

    "UNTITLED" : "Sem título",

    /**
     * Keyboard modifier names
     */

    "KEYBOARD_CTRL"   : "Ctrl",
    "KEYBOARD_SHIFT"  : "Shift",
    "KEYBOARD_SPACE"  : "Space",

    /**
     * StatusBar strings
     */
    "STATUSBAR_CURSOR_POSITION"             : "Linha {0}, Coluna {1}",
    "STATUSBAR_INDENT_TOOLTIP_SPACES"       : "Clique para alterar a indentação para espaços",
    "STATUSBAR_INDENT_TOOLTIP_TABS"         : "Clique para alterar a indentação para tabulação",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES"  : "Clique para alterar o número de espaços usados ao indentar",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_TABS"    : "Clique para alterar a largura do caractere de tabulação",
    "STATUSBAR_SPACES"                      : "Espaços",
    "STATUSBAR_TAB_SIZE"                    : "Tamanho da Tabulação",
    "STATUSBAR_LINE_COUNT"                  : "{0} Linhas",

    /**
     * Command Name Constants
     */

    // File menu commands
    "FILE_MENU"                           : "Arquivo",
    "CMD_FILE_NEW"                        : "Novo",
    "CMD_FILE_NEW_FOLDER"                 : "Nova Pasta",
    "CMD_FILE_OPEN"                       : "Abrir\u2026",
    "CMD_ADD_TO_WORKING_SET"              : "Adicionar Para Conjunto de Trabalho",
    "CMD_OPEN_FOLDER"                     : "Abrir pasta\u2026",
    "CMD_FILE_CLOSE"                      : "Fechar",
    "CMD_FILE_CLOSE_ALL"                  : "Fechar Tudo",
    "CMD_FILE_SAVE"                       : "Salvar",
    "CMD_FILE_SAVE_ALL"                   : "Salvar Tudo",
    "CMD_LIVE_FILE_PREVIEW"               : "Live Preview",
    "CMD_FILE_RENAME"                     : "Renomear",
    "CMD_QUIT"                            : "Sair",

    // Edit menu commands
    "EDIT_MENU"                           : "Editar",
    "CMD_SELECT_ALL"                      : "Selecionar Tudo",
    "CMD_FIND"                            : "Encontrar",
    "CMD_FIND_IN_FILES"                   : "Encontrar em Arquivos",
    "CMD_FIND_NEXT"                       : "Encontrar Próximo",
    "CMD_FIND_PREVIOUS"                   : "Encontrar Anterior",
    "CMD_REPLACE"                         : "Substituir",
    "CMD_INDENT"                          : "Recuar",
    "CMD_UNINDENT"                        : "Desfazer Recuo",
    "CMD_DUPLICATE"                       : "Duplicar",
    "CMD_DELETE_LINES"                    : "Deletar Linha(s) Selecionadas",
    "CMD_COMMENT"                         : "Comentar/Descomentar Linhas",
    "CMD_LINE_UP"                         : "Mover Linha(s) para Cima",
    "CMD_LINE_DOWN"                       : "Mover Linha(s) para Baixo",

    // View menu commands
    "VIEW_MENU"                           : "Ver",
    "CMD_HIDE_SIDEBAR"                    : "Esconder Barra Lateral",
    "CMD_SHOW_SIDEBAR"                    : "Mostrar Barra Lateral",
    "CMD_INCREASE_FONT_SIZE"              : "Aumentar Tamanho da Fonte",
    "CMD_DECREASE_FONT_SIZE"              : "Diminuir Tamanho da Fonte",
    "CMD_RESTORE_FONT_SIZE"               : "Restaurar Tamanho da Fonte",

    // Navigate menu Commands
    "NAVIGATE_MENU"                       : "Navegar",
    "CMD_QUICK_OPEN"                      : "Abertura Rápida",
    "CMD_GOTO_LINE"                       : "Ir para a Linha",
    "CMD_GOTO_DEFINITION"                 : "Ir para Definição",
    "CMD_TOGGLE_QUICK_EDIT"               : "Edição Rápida",
    "CMD_QUICK_EDIT_PREV_MATCH"           : "Resultado Anterior",
    "CMD_QUICK_EDIT_NEXT_MATCH"           : "Resultado Seguinte",
    "CMD_NEXT_DOC"                        : "Documento Seguinte",
    "CMD_PREV_DOC"                        : "Documento Anterior",

    // Help menu commands
    "HELP_MENU"                           : "Ajuda",
    "CMD_CHECK_FOR_UPDATE"                : "Verificar Atualizações",
    "CMD_SHOW_EXTENSIONS_FOLDER"          : "Mostrar Pasta de Extensões",
    "CMD_FORUM"                           : "{APP_NAME} Forum",
    "CMD_ABOUT"                           : "Sobre",

    // Special commands invoked by the native shell
    "CMD_CLOSE_WINDOW"                    : "Fechar Janela",
    "CMD_ABORT_QUIT"                      : "Abortar Saída",

    // Strings for main-view.html
    "EXPERIMENTAL_BUILD"                   : "Versão Experimental",
    "SEARCH_RESULTS"                       : "Resultados da Pesquisa",
    "OK"                                   : "OK",
    "DONT_SAVE"                            : "Não Salvar",
    "SAVE"                                 : "Salvar",
    "CANCEL"                               : "Cancelar",
    "RELOAD_FROM_DISK"                     : "Atualizar a Partir do Disco",
    "KEEP_CHANGES_IN_EDITOR"               : "Manter Mudanças no Editor",
    "CLOSE_DONT_SAVE"                      : "Fechar (Não Salvar)",
    "RELAUNCH_CHROME"                      : "Relançar Chrome",
    "ABOUT"                                : "Sobre",
    "CLOSE"                                : "Fechar",
    "ABOUT_TEXT_LINE1"                     : "sprint {VERSION_MINOR} versão experimental {VERSION}",
    "ABOUT_TEXT_LINE3"                     : "Avisos, termos e condições de softwares de terceiros estão localizados em http://www.adobe.com/go/thirdparty/ e aqui incorporados por referência.",
    "ABOUT_TEXT_LINE4"                     : "Documentação e fontes https://github.com/adobe/brackets/",
    "UPDATE_NOTIFICATION_TOOLTIP"          : "Há uma nova versão de {APP_NAME} disponível! Clique aqui para mais detalhes.",
    "UPDATE_AVAILABLE_TITLE"               : "Atualização Disponível",
    "UPDATE_MESSAGE"                       : "Opa, há uma nova versão de {APP_NAME} disponível. Aqui estão alguns dos novos recursos:",
    "GET_IT_NOW"                           : "Obtenha agora!",

    // extensions/default/DebugCommands
    "DEBUG_MENU"                           : "Depurar",
    "CMD_SHOW_DEV_TOOLS"                   : "Mostrar Ferramentas de Desenvolvimento",
    "CMD_REFRESH_WINDOW"                   : "Recarregar {APP_NAME}",
    "CMD_NEW_BRACKETS_WINDOW"              : "Nova Janela {APP_NAME}",
    "CMD_SWITCH_LANGUAGE"                  : "Trocar Linguagem",
    "CMD_RUN_UNIT_TESTS"                   : "Executar Testes",
    "CMD_SHOW_PERF_DATA"                   : "Mostrar Dados de Desempenho",

    "LANGUAGE_TITLE"                       : "Comutar Idioma",
    "LANGUAGE_MESSAGE"                     : "Por favor, selecione o idioma desejado na lista abaixo:",
    "LANGUAGE_SUBMIT"                      : "Recarregar {APP_NAME}",
    "LANGUAGE_CANCEL"                      : "Cancelar",

    // extensions/default/JSLint
    "CMD_JSLINT"                           : "Habilitar JSLint",
    "JSLINT_ERRORS"                        : "Erros JSLint",
    "JSLINT_ERROR_INFORMATION"             : "1 Erro JSLint",
    "JSLINT_ERRORS_INFORMATION"            : "{0} Erros JSLint",
    "JSLINT_NO_ERRORS"                     : "Sem erros JSLint - bom trabalho!",
    "JSLINT_DISABLED"                      : "JSLint desabilitado ou não funcionando para o arquivo atual"
});


// Update sprint number in package.json and rewrite src/config.json

'use strict';

module.exports = Until;
var PullStream = require('pullstream');
var inherits = require("util").inherits;
var over = require('over');
var Buffers = require('buffers');

function Until(opts) {
  this._bufs = Buffers();
  PullStream.call(this, opts);
}
inherits(Until, PullStream);

//todo rework prepend call. Unlikely to remain in API
Until.prototype.prepend = function (chunk) {
  this._bufs.push(chunk);
};

Until.prototype.pull = over([
  [over.numberOptionalWithDefault(null), over.func, function (len, callback) {
    if (!this._bufs.length || len === 0) {
      return PullStream.prototype.pull.call(this, len, callback);
    }

    var self = this;
    pullServiceRequest();

    function pullServiceRequest() {
      self._serviceRequests = null;
      if (self._flushed) {
        return callback(new Error('End of Stream'));
      }

      var data;
      if (self._bufs.length >= len) {
        data = self._bufs.slice(0, len);
        var slice = self._bufs = self._bufs.slice(len, self._bufs.length);
        self._bufs = Buffers();
        self._bufs.push(slice);
      } else {
        var streamData = self.read(len - self._bufs.length);
        if (streamData) {
          data = Buffer.concat([self._bufs.toBuffer(), streamData])
          self._bufs = Buffers();
        }
      }

      if (data) {
        process.nextTick(callback.bind(null, null, data));
      } else {
        self._serviceRequests = pullServiceRequest;
      }
    }
  }]
]);

//todo pullUpTo(null) should return internal buffer plus any bytes within the stream
Until.prototype.pullUpTo = function (len) {
  if (this._bufs.length >= len) {
    var data = this._bufs.slice(0, len);
    var slice = this._bufs.slice(len, this._bufs.length);
    this._bufs = Buffers();
    this._bufs.push(slice);
    return data;
  }

  var data = PullStream.prototype.pullUpTo.call(this, len - this._bufs.length);
  if (data) {
    data = Buffer.concat([this._bufs.toBuffer(), data]);
  } else {
    data = this._bufs.toBuffer();
  }
  this._bufs = Buffers();
  return data;
}

//todo prepend _bufs in front of pipe


keep keep keep add keep keep keep keep keep

/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */
/*global module, require*/
module.exports = function (grunt) {
    'use strict';

    var common = require("./tasks/lib/common")(grunt);

    // Project configuration.
    grunt.initConfig({
        pkg  : grunt.file.readJSON("package.json"),
        meta : {
            src   : [
                'src/**/*.js',
                '!src/thirdparty/**',
                '!src/widgets/bootstrap-*.js',
                '!src/extensions/**/unittest-files/**/*.js',
                '!src/extensions/**/thirdparty/**/*.js',
                '!src/extensions/dev/**',
                '!src/extensions/disabled/**',
                '!**/node_modules/**/*.js',
                '!src/**/*-min.js',
                '!src/**/*.min.js'
            ],
            test : [
                'test/**/*.js',
                '!test/perf/*-files/**/*.js',
                '!test/spec/*-files/**/*.js',
                '!test/smokes/**',
                '!test/temp/**',
                '!test/thirdparty/**',
                '!test/**/node_modules/**/*.js'
            ],
            grunt: [
                'Gruntfile.js',
                'tasks/**/*.js'
            ],
            /* specs that can run in phantom.js */
            specs : [
                'test/spec/CommandManager-test.js',
                //'test/spec/LanguageManager-test.js',
                //'test/spec/PreferencesManager-test.js',
                'test/spec/ViewUtils-test.js'
            ]
        },
        watch: {
            all : {
                files: ['**/*', '!**/node_modules/**'],
                tasks: ['jshint']
            },
            grunt : {
                files: ['<%= meta.grunt %>', 'tasks/**/*'],
                tasks: ['jshint:grunt']
            },
            src : {
                files: ['<%= meta.src %>', 'src/**/*'],
                tasks: ['jshint:src']
            },
            test : {
                files: ['<%= meta.test %>', 'test/**/*'],
                tasks: ['jshint:test']
            }
        },
        /* FIXME (jasonsanjose): how to handle extension tests */
        jasmine : {
            src : 'undefined.js', /* trick the default runner to run without importing src files */
            options : {
                junit : {
                    path: 'test/results',
                    consolidate: true
                },
                specs : '<%= meta.specs %>',
                /* Keep in sync with test/SpecRunner.html dependencies */
                vendor : [
                    'src/thirdparty/jquery-1.7.js',
                    'src/thirdparty/CodeMirror2/lib/codemirror.js',
                    'src/thirdparty/CodeMirror2/lib/util/dialog.js',
                    'src/thirdparty/CodeMirror2/lib/util/searchcursor.js',
                    'src/thirdparty/CodeMirror2/addon/edit/closetag.js',
                    'src/thirdparty/CodeMirror2/addon/selection/active-line.js',
                    'src/thirdparty/mustache/mustache.js',
                    'src/thirdparty/path-utils/path-utils.min',
                    'src/thirdparty/less-1.3.0.min.js'
                ],
                helpers : [
                    'test/spec/PhantomHelper.js'
                ],
                template : require('grunt-template-jasmine-requirejs'),
                templateOptions: {
                    requireConfig : {
                        baseUrl: 'src',
                        paths: {
                            'test' : '../test',
                            'perf' : '../test/perf',
                            'spec' : '../test/spec',
                            'text' : 'thirdparty/text/text',
                            'i18n' : 'thirdparty/i18n/i18n'
                        }
                    }
                }
            }
        },
        jshint: {
            all: [
                '<%= meta.grunt %>',
                '<%= meta.src %>',
                '<%= meta.test %>'
            ],
            grunt:  '<%= meta.grunt %>',
            src:    '<%= meta.src %>',
            test:   '<%= meta.test %>',
            /* use strict options to mimic JSLINT until we migrate to JSHINT in Brackets */
            options: {
                jshintrc: '.jshintrc'
            }
        },
        shell: {
            repo: grunt.option("shell-repo") || "../brackets-shell",
            mac: "<%= shell.repo %>/installer/mac/staging/<%= pkg.name %>.app",
            win: "<%= shell.repo %>/installer/win/staging/<%= pkg.name %>.exe"
        }
    });

    // load dependencies
    grunt.loadTasks('tasks');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-jshint');
    grunt.loadNpmTasks('grunt-contrib-watch');

    // task: install
    grunt.registerTask('install', ['write-config']);

    // task: test
    //grunt.registerTask('test', ['jshint', 'jasmine']);
    grunt.registerTask('test', ['jshint']);

    // task: set-sprint
    // Update sprint number in package.json and rewrite src/config.json
    grunt.registerTask('set-sprint', ['update-sprint-number', 'write-config']);

    // Default task.
    grunt.registerTask('default', ['test']);
};


/*
 * draggable_events.js
 */
(function($) {

module("draggable: events");

test("callbacks occurrence count", function() {

	expect(3);

	var start = 0, stop = 0, dragc = 0;
	el = $("#draggable2").draggable({
		start: function() { start++; },
		drag: function() { dragc++; },
		stop: function() { stop++; }
	});

	drag(el, 10, 10);

	equal(start, 1, "start callback should happen exactly once");
	equal(dragc, 3, "drag callback should happen exactly once per mousemove");
	equal(stop, 1, "stop callback should happen exactly once");

});

test("stopping the start callback", function() {

	expect(3);

	var start = 0, stop = 0, dragc = 0;
	el = $("#draggable2").draggable({
		start: function() { start++; return false; },
		drag: function() { dragc++; },
		stop: function() { stop++; }
	});

	drag(el, 10, 10);

	equal(start, 1, "start callback should happen exactly once");
	equal(dragc, 0, "drag callback should not happen at all");
	equal(stop, 0, "stop callback should not happen if there wasnt even a start");

});

test("stopping the drag callback", function() {

	expect(3);

	var start = 0, stop = 0, dragc = 0;
	el = $("#draggable2").draggable({
		start: function() { start++;},
		drag: function() { dragc++; return false;  },
		stop: function() { stop++; }
	});

	drag(el, 10, 10);

	equal(start, 1, "start callback should happen exactly once");
	equal(dragc, 1, "drag callback should happen exactly once");
	equal(stop, 1, "stop callback should happen, as we need to actively stop the drag");

});

test("stopping the stop callback", function() {

	expect(1);

	el = $("#draggable2").draggable({
		helper: 'clone',
		stop: function() { return false; }
	});

	drag(el, 10, 10);

	ok($("#draggable2").data('draggable').helper, "the clone should not be deleted if the stop callback is stopped");

});

})(jQuery);


var assert = require('assert');
var Chainsaw = require('../index');

exports.getset = function () {
    var to = setTimeout(function () {
        assert.fail('builder never fired');
    }, 1000);

    var ch = Chainsaw(function (saw) {
        clearTimeout(to);
        var num = 0;

        this.get = function (cb) {
            cb(num);
            saw.next();
        };

        this.set = function (n) {
            num = n;
            saw.next();
        };

        var ti = setTimeout(function () {
            assert.fail('end event not emitted');
        }, 50);

        saw.on('end', function () {
            clearTimeout(ti);
            assert.equal(times, 3);
        });
    });

    var times = 0;
    ch
        .get(function (x) {
            assert.equal(x, 0);
            times ++;
        })
        .set(10)
        .get(function (x) {
            assert.equal(x, 10);
            times ++;
        })
        .set(20)
        .get(function (x) {
            assert.equal(x, 20);
            times ++;
        })
    ;
};

exports.nest = function () {
    var ch = (function () {
        var vars = {};
        return Chainsaw(function (saw) {
            this.do = function (cb) {
                saw.nest(cb, vars);
            };
        });
    })();

    var order = [];
    var to = setTimeout(function () {
        assert.fail("Didn't get to the end");
    }, 50);

    ch
        .do(function (vars) {
            vars.x = 'y';
            order.push(1);

            this
                .do(function (vs) {
                    order.push(2);
                    vs.x = 'x';
                })
                .do(function (vs) {
                    order.push(3);
                    vs.z = 'z';
                })
            ;
        })
        .do(function (vars) {
            vars.y = 'y';
            order.push(4);
        })
        .do(function (vars) {
            assert.eql(order, [1,2,3,4]);
            assert.eql(vars, { x : 'x', y : 'y', z : 'z' });
            clearTimeout(to);
        })
    ;
};

exports.nestWait = function () {
    var ch = (function () {
        var vars = {};
        return Chainsaw(function (saw) {
            this.do = function (cb) {
                saw.nest(cb, vars);
            };

            this.wait = function (n) {
                setTimeout(function () {
                    saw.next();
                }, n);
            };
        });
    })();

    var order = [];
    var to = setTimeout(function () {
        assert.fail("Didn't get to the end");
    }, 1000);

    var times = {};

    ch
        .do(function (vars) {
            vars.x = 'y';
            order.push(1);

            this
                .do(function (vs) {
                    order.push(2);
                    vs.x = 'x';
                    times.x = Date.now();
                })
                .wait(50)
                .do(function (vs) {
                    order.push(3);
                    vs.z = 'z';

                    times.z = Date.now();
                    var dt = times.z - times.x;
                    assert.ok(dt >= 50 && dt < 75);
                })
            ;
        })
        .do(function (vars) {
            vars.y = 'y';
            order.push(4);

            times.y = Date.now();
        })
        .wait(100)
        .do(function (vars) {
            assert.eql(order, [1,2,3,4]);
            assert.eql(vars, { x : 'x', y : 'y', z : 'z' });
            clearTimeout(to);

            times.end = Date.now();
            var dt = times.end - times.y;
            assert.ok(dt >= 100 && dt < 125)
        })
    ;
};

exports.nestNext = function () {
    var ch = (function () {
        var vars = {};
        return Chainsaw(function (saw) {
            this.do = function (cb) {
                saw.nest(false, function () {
                    var args = [].slice.call(arguments);
                    args.push(saw.next);
                    cb.apply(this, args);
                }, vars);
            };
        });
    })();

    var order = [];
    var to = setTimeout(function () {
        assert.fail("Didn't get to the end");
    }, 500);

    var times = [];

    ch
        .do(function (vars, next_) {
            vars.x = 'y';
            order.push(1);

            this
                .do(function (vs, next) {
                    order.push(2);
                    vs.x = 'x';
                    setTimeout(next, 30);
                })
                .do(function (vs, next) {
                    order.push(3);
                    vs.z = 'z';
                    setTimeout(next, 10);
                })
                .do(function () {
                    setTimeout(next_, 20);
                })
            ;
        })
        .do(function (vars, next) {
            vars.y = 'y';
            order.push(4);
            setTimeout(next, 5);
        })
        .do(function (vars) {
            assert.eql(order, [1,2,3,4]);
            assert.eql(vars, { x : 'x', y : 'y', z : 'z' });

            clearTimeout(to);
        })
    ;
};

exports.builder = function () {
    var cx = Chainsaw(function (saw) {
        this.x = function () {};
    });
    assert.ok(cx.x);

    var cy = Chainsaw(function (saw) {
        return { y : function () {} };
    });
    assert.ok(cy.y);

    var cz = Chainsaw(function (saw) {
        return { z : function (cb) { saw.nest(cb) } };
    });
    assert.ok(cz.z);

    var to = setTimeout(function () {
        assert.fail("Nested z didn't run");
    }, 50);

    cz.z(function () {
        clearTimeout(to);
        assert.ok(this.z);
    });
};

this.attr = function () {
    var to = setTimeout(function () {
        assert.fail("attr chain didn't finish");
    }, 50);

    var xy = [];
    var ch = Chainsaw(function (saw) {
        this.h = {
            x : function () {
                xy.push('x');
                saw.next();
            },
            y : function () {
                xy.push('y');
                saw.next();
                assert.eql(xy, ['x','y']);
                clearTimeout(to);
            }
        };
    });
    assert.ok(ch.h);
    assert.ok(ch.h.x);
    assert.ok(ch.h.y);

    ch.h.x().h.y();
};

exports.down = function () {
    var error = null;
    var s;
    var ch = Chainsaw(function (saw) {
        s = saw;
        this.raise = function (err) {
            error = err;
            saw.down('catch');
        };

        this.do = function (cb) {
            cb.call(this);
        };

        this.catch = function (cb) {
            if (error) {
                saw.nest(cb, error);
                error = null;
            }
            else saw.next();
        };
    });

    var to = setTimeout(function () {
        assert.fail(".do() after .catch() didn't fire");
    }, 50);

    ch
        .do(function () {
            this.raise('pow');
        })
        .do(function () {
            assert.fail("raise didn't skip over this do block");
        })
        .catch(function (err) {
            assert.equal(err, 'pow');
        })
        .do(function () {
            clearTimeout(to);
        })
    ;
};

exports.trap = function () {
    var error = null;
    var ch = Chainsaw(function (saw) {
        var pars = 0;
        var stack = [];
        var i = 0;

        this.par = function (cb) {
            pars ++;
            var j = i ++;
            cb.call(function () {
                pars --;
                stack[j] = [].slice.call(arguments);
                saw.down('result');
            });
            saw.next();
        };

        this.join = function (cb) {
            saw.trap('result', function () {
                if (pars == 0) {
                    cb.apply(this, stack);
                    saw.next();
                }
            });
        };

        this.raise = function (err) {
            error = err;
            saw.down('catch');
        };

        this.do = function (cb) {
            cb.call(this);
        };

        this.catch = function (cb) {
            if (error) {
                saw.nest(cb, error);
                error = null;
            }
            else saw.next();
        };
    });

    var to = setTimeout(function () {
        assert.fail(".do() after .join() didn't fire");
    }, 100);
    var tj = setTimeout(function () {
        assert.fail('.join() never fired');
    }, 100);

    var joined = false;
    ch
        .par(function () {
            setTimeout(this.bind(null, 1), 50);
        })
        .par(function () {
            setTimeout(this.bind(null, 2), 25);
        })
        .join(function (x, y) {
            assert.equal(x[0], 1);
            assert.equal(y[0], 2);
            clearTimeout(tj);
            joined = true;
        })
        .do(function () {
            clearTimeout(to);
            assert.ok(joined);
        })
    ;
};

exports.jump = function () {
    var to = setTimeout(function () {
        assert.fail('builder never fired');
    }, 50);

    var xs = [ 4, 5, 6, -4, 8, 9, -1, 8 ];
    var xs_ = [];

    var ch = Chainsaw(function (saw) {
        this.x = function (i) {
            xs_.push(i);
            saw.next();
        };

        this.y = function (step) {
            var x = xs.shift();
            if (x > 0) saw.jump(step);
            else saw.next();
        };

        saw.on('end', function () {
            clearTimeout(to);
            assert.eql(xs, [ 8 ]);
            assert.eql(xs_, [ 1, 1, 1, 1, 2, 3, 2, 3, 2, 3 ]);
        });
    });

    ch
        .x(1)
        .y(0)
        .x(2)
        .x(3)
        .y(2)
    ;
};


/* Euskarako oinarria 'UI date picker' jquery-ko extentsioarentzat */
/* Karrikas-ek itzulia (karrikas@karrikas.com) */
jQuery(function($){
	$.datepicker.regional['eu'] = {
		closeText: 'Egina',
		prevText: '<Aur',
		nextText: 'Hur>',
		currentText: 'Gaur',
		monthNames: ['urtarrila','otsaila','martxoa','apirila','maiatza','ekaina',
			'uztaila','abuztua','iraila','urria','azaroa','abendua'],
		monthNamesShort: ['urt.','ots.','mar.','api.','mai.','eka.',
			'uzt.','abu.','ira.','urr.','aza.','abe.'],
		dayNames: ['igandea','astelehena','asteartea','asteazkena','osteguna','ostirala','larunbata'],
		dayNamesShort: ['ig.','al.','ar.','az.','og.','ol.','lr.'],
		dayNamesMin: ['ig','al','ar','az','og','ol','lr'],
		weekHeader: 'As',
		dateFormat: 'yy-mm-dd',
		firstDay: 1,
		isRTL: false,
		showMonthAfterYear: false,
		yearSuffix: ''};
	$.datepicker.setDefaults($.datepicker.regional['eu']);
});


/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, node: true, nomen: true,
indent: 4, maxerr: 50, regexp: true */

"use strict";

var unzip   = require("unzip"),
    semver  = require("semver"),
    path    = require("path"),
    http    = require("http"),
    request = require("request"),
    os      = require("os"),
    fs      = require("fs-extra");

var Errors = {
    NOT_FOUND_ERR: "NOT_FOUND_ERR",                       // {0} is path where ZIP file was expected
    INVALID_ZIP_FILE: "INVALID_ZIP_FILE",                 // {0} is path to ZIP file
    INVALID_PACKAGE_JSON: "INVALID_PACKAGE_JSON",         // {0} is JSON parse error, {1} is path to ZIP file
    MISSING_PACKAGE_NAME: "MISSING_PACKAGE_NAME",         // {0} is path to ZIP file
    BAD_PACKAGE_NAME: "BAD_PACKAGE_NAME",                 // {0} is the name
    MISSING_PACKAGE_VERSION: "MISSING_PACKAGE_VERSION",   // {0} is path to ZIP file
    INVALID_VERSION_NUMBER: "INVALID_VERSION_NUMBER",     // {0} is version string in JSON, {1} is path to ZIP file
    MISSING_MAIN: "MISSING_MAIN",                         // {0} is path to ZIP file
    MISSING_PACKAGE_JSON: "MISSING_PACKAGE_JSON",         // {0} is path to ZIP file
    INVALID_BRACKETS_VERSION: "INVALID_BRACKETS_VERSION", // {0} is the version string in JSON, {1} is the path to the zip file,
    DISALLOWED_WORDS: "DISALLOWED_WORDS"                  // {0} is the field with the word, {1} is a string list of words that were in violation, {2} is the path to the zip file
};

/*
 * Directories to ignore when computing the common prefix among the entries of
 * a zip file.
 */
var ignoredPrefixes = {
    "__MACOSX": true
};

/**
 * Returns true if the name presented is acceptable as a package name. This enforces the
 * requirement as presented in the CommonJS spec: http://wiki.commonjs.org/wiki/Packages/1.0
 * which states:
 *
 * "This must be a unique, lowercase alpha-numeric name without spaces. It may include "." or "_" or "-" characters."
 *
 * We add the additional requirement that the first character must be a letter or number
 * (there's a security implication to allowing a name like "..", because the name is
 * used in directory names).
 *
 * @param {string} name to test
 * @return {boolean} true if the name is valid
 */
function validateName(name) {
    if (/^[a-z0-9][a-z0-9._\-]*$/.exec(name)) {
        return true;
    }
    return false;
}

// Parses strings of the form "name <email> (url)" where email and url are optional
var _personRegex = /^([^<\(]+)(?:\s+<([^>]+)>)?(?:\s+\(([^\)]+)\))?$/;

/**
 * Normalizes person fields from package.json.
 *
 * These fields can be an object with name, email and url properties or a
 * string of the form "name <email> <url>". This does a tolerant parsing of
 * the data to try to return an object with name and optional email and url.
 * If the string does not match the format, the string is returned as the
 * name on the resulting object.
 *
 * If an object other than a string is passed in, it's returned as is.
 *
 * @param <String|Object> obj to normalize
 * @return {Object} person object with name and optional email and url
 */
function parsePersonString(obj) {
    if (typeof (obj) === "string") {
        var parts = _personRegex.exec(obj);

        // No regex match, so we just synthesize an object with an opaque name string
        if (!parts) {
            return {
                name: obj
            };
        } else {
            var result = {
                name: parts[1]
            };
            if (parts[2]) {
                result.email = parts[2];
            }
            if (parts[3]) {
                result.url = parts[3];
            }
            return result;
        }
    } else {
        // obj is not a string, so return as is
        return obj;
    }
}

/**
 * Determines if any of the words in wordlist appear in str.
 *
 * @param {String[]} wordlist list of words to check
 * @param {String} str to check for words
 * @return {String[]} words that matched
 */
function containsWords(wordlist, str) {
    var i;
    var matches = [];
    for (i = 0; i < wordlist.length; i++) {
        var re = new RegExp("\\b" + wordlist[i] + "\\b", "i");
        if (re.exec(str)) {
            matches.push(wordlist[i]);
        }
    }
    return matches;
}

/**
 * Implements the "validate" command in the "extensions" domain.
 * Validates the zipped package at path.
 *
 * The "err" parameter of the callback is only set if there was an
 * unexpected error. Otherwise, errors are reported in the result.
 *
 * The result object has an "errors" property. It is an array of
 * arrays of strings. Each array in the array is a set of parameters
 * that can be passed to StringUtils.format for internationalization.
 * The array will be empty if there are no errors.
 *
 * The result will have a "metadata" property if the metadata was
 * read successfully from package.json in the zip file.
 *
 * @param {string} path Absolute path to the package zip file
 * @param {{requirePackageJSON: ?boolean, disallowedWords: ?Array.<string>}} options for validation
 * @param {function} callback (err, result)
 */
function validate(path, options, callback) {
    options = options || {};
    fs.exists(path, function (doesExist) {
        if (!doesExist) {
            callback(null, {
                errors: [[Errors.NOT_FOUND_ERR, path]]
            });
            return;
        }
        var callbackCalled = false;
        var metadata;
        var foundMainIn = null;
        var errors = [];
        var commonPrefix = null;

        var readStream = fs.createReadStream(path);

        readStream
            .pipe(unzip.Parse())
            .on("error", function (exception) {
                // General error to report for problems reading the file
                errors.push([Errors.INVALID_ZIP_FILE, path]);
                callback(null, {
                    errors: errors
                });
                callbackCalled = true;
                readStream.destroy();
            })
            .on("entry", function (entry) {
                // look for the metadata
                var fileName = entry.path;

                var slash = fileName.indexOf("/");
                if (slash > -1) {
                    var prefix = fileName.substring(0, slash);
                    if (!ignoredPrefixes.hasOwnProperty(prefix)) {
                        if (commonPrefix === null) {
                            commonPrefix = prefix;
                        } else if (prefix !== commonPrefix) {
                            commonPrefix = "";
                        }
                        if (commonPrefix) {
                            fileName = fileName.substring(commonPrefix.length + 1);
                        }
                    }
                } else {
                    commonPrefix = "";
                }

                if (fileName === "package.json") {
                    // This handles an edge case where we found a package.json in a
                    // nested directory that we thought was a commonPrefix but
                    // actually wasn't. We reset as if we never read that first
                    // package.json
                    if (metadata) {
                        metadata = undefined;
                        errors = [];
                    }

                    var packageJSON = "";
                    entry
                        .on("data", function (data) {
                            // We're assuming utf8 encoding here, which is pretty safe
                            // Note that I found that .setEncoding on the stream
                            // would fail, so I convert the buffer to a string here.
                            packageJSON += data.toString("utf8");
                        })
                        .on("error", function (exception) {
                            // general exception handler. It is unknown what kinds of
                            // errors we can get here.
                            callback(exception, null);
                            callbackCalled = true;
                            readStream.destroy();
                        })
                        .on("end", function () {
                            // attempt to parse the metadata
                            try {
                                metadata = JSON.parse(packageJSON);
                            } catch (e) {
                                errors.push([Errors.INVALID_PACKAGE_JSON, e.toString(), path]);
                                return;
                            }

                            // confirm required fields in the metadata
                            if (!metadata.name) {
                                errors.push([Errors.MISSING_PACKAGE_NAME, path]);
                            } else if (!validateName(metadata.name)) {
                                errors.push([Errors.BAD_PACKAGE_NAME, metadata.name]);
                            }
                            if (!metadata.version) {
                                errors.push([Errors.MISSING_PACKAGE_VERSION, path]);
                            } else if (!semver.valid(metadata.version)) {
                                errors.push([Errors.INVALID_VERSION_NUMBER, metadata.version, path]);
                            }

                            // normalize the author
                            if (metadata.author) {
                                metadata.author = parsePersonString(metadata.author);
                            }

                            // contributors should be an array of people.
                            // normalize each entry.
                            if (metadata.contributors) {
                                if (metadata.contributors.map) {
                                    metadata.contributors = metadata.contributors.map(function (person) {
                                        return parsePersonString(person);
                                    });
                                } else {
                                    metadata.contributors = [
                                        parsePersonString(metadata.contributors)
                                    ];
                                }
                            }

                            if (metadata.engines && metadata.engines.brackets) {
                                var range = metadata.engines.brackets;
                                if (!semver.validRange(range)) {
                                    errors.push([Errors.INVALID_BRACKETS_VERSION, range, path]);
                                }
                            }

                            if (options.disallowedWords) {
                                ["title", "description", "name"].forEach(function (field) {
                                    var words = containsWords(options.disallowedWords, metadata[field]);
                                    if (words.length > 0) {
                                        errors.push([Errors.DISALLOWED_WORDS, field, words.toString(), path]);
                                    }
                                });
                            }
                        });
                } else if (fileName === "main.js") {
                    foundMainIn = commonPrefix;
                }
            })
            .on("end", function () {
                // Reached the end of the zipfile
                // Report results

                // generally, if we hit an exception, we've already called the callback
                if (callbackCalled) {
                    return;
                }

                if (foundMainIn === null || foundMainIn !== commonPrefix) {
                    errors.push([Errors.MISSING_MAIN, path]);
                }

                // No errors and no metadata means that we never found the metadata
                if (errors.length === 0 && !metadata) {
                    metadata = null;
                }

                if (metadata === null && options.requirePackageJSON) {
                    errors.push([Errors.MISSING_PACKAGE_JSON, path]);
                }

                callback(null, {
                    errors: errors,
                    metadata: metadata,
                    commonPrefix: commonPrefix
                });
            });
    });
}

// exported for unit testing
exports._parsePersonString = parsePersonString;

exports.errors = Errors;
exports.validate = validate;


# mkdirp

Like `mkdir -p`, but in node.js!

[![build status](https://secure.travis-ci.org/substack/node-mkdirp.png)](http://travis-ci.org/substack/node-mkdirp)

# example

## pow.js

```js
var mkdirp = require('mkdirp');

mkdirp('/tmp/foo/bar/baz', function (err) {
    if (err) console.error(err)
    else console.log('pow!')
});
```

Output

```
pow!
```

And now /tmp/foo/bar/baz exists, huzzah!

# methods

```js
var mkdirp = require('mkdirp');
```

## mkdirp(dir, mode, cb)

Create a new directory and any necessary subdirectories at `dir` with octal
permission string `mode`.

If `mode` isn't specified, it defaults to `0777 & (~process.umask())`.

`cb(err, made)` fires with the error or the first directory `made`
that had to be created, if any.

## mkdirp.sync(dir, mode)

Synchronously create a new directory and any necessary subdirectories at `dir`
with octal permission string `mode`.

If `mode` isn't specified, it defaults to `0777 & (~process.umask())`.

Returns the first directory that had to be created, if any.

# install

With [npm](http://npmjs.org) do:

```
npm install mkdirp
```

# license

MIT


<!doctype html>

                You're not supposed to put colors in your HTML, but we'll let you do it anyway.


export function Component(props, context) {

// if (this.state==null) this.state = {};

// Other properties that Component will have set later,

// shown here as commented out for quick reference

// this._ancestorComponent = null; // Always set right after instantiation

keep keep keep keep keep replace replace replace keep keep keep keep keep

this.state = {}; // TODO: Consider removing

// this._renderCallbacks = []; // Only class components

import { EMPTY_OBJ, EMPTY_ARR } from '../constants';
// import { assign } from '../util';
import { Component, enqueueRender } from '../component';
import { coerceToVNode, Fragment } from '../create-element';
import { diffChildren /*, create */ } from './children';
import { diffProps } from './props';
import { assign } from '../util';
// import { processQueue } from '../component';

// let hash = [
// 	['type', ['number']],
// 	['tag', [null,'string','function']],
// 	['props', ['object']],
// 	['children', ['object']],
// 	['text', ['string', null]],
// 	['key', [null,'string']],
// 	['index', [null,'number']],
// 	['_children', ['object']],
// 	['_el', ['object']],
// 	['_component', ['object']]
// ];

// function assertIsPureVNode(obj) {
// 	let index = 0;
// 	for (let i in obj) {
// 		let p = hash[index];
// 		if (i!==p[0]) return console.error('VNode property error ('+i+' !== '+p[0]+')');
// 		let isMatch = false;
// 		for (let j=0; j<p[1].length; j++) {
// 			if (obj[i]==p[1][j] || typeof obj[i]===p[1][j]) {
// 				isMatch = true;
// 				break;
// 			}
// 		}
// 		if (!isMatch) return console.error('VNode type error ('+i+' !~ '+p[1]+'): ', obj[i]);
// 		index++;
// 	}
// }

// const IDLE_OPTIONS = { timeout: 100 };
// let idleQueue = [];
// // let queuePopulated = false;
// // let queueIndex = 0;
// function processIdleQueue() {
// 	let p;
// 	// let processed = 0;
// 	let time = Date.now();
// 	while (Date.now()-time<30 && idleQueue.length!==0) {
// 		if ((p = idleQueue.pop())) {
// 			// eslint-disable-next-line prefer-spread
// 			diff.apply(null, p);
// 			// processed++;
// 		}
// 	}

// 	// if (processed) console.log(processed);

// 	if (idleQueue.length) {
// 		requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	}

// 	// if (queueIndex>=idleQueue.length-1) {
// 	// 	queuePopulated = false;
// 	// 	queueIndex = idleQueue.length = 0;
// 	// }
// 	// else {
// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	// }

// 	// // let len = idleQueue.length;
// 	// // console.log('emptying queue ('+queueIndex+' - '+idleQueue.length+')');
// 	// for ( ; queueIndex<idleQueue.length && !c.didTimeout; queueIndex++) {
// 	// 	// eslint-disable-next-line prefer-spread
// 	// 	diff.apply(null, idleQueue[queueIndex]);
// 	// }
// 	// if (queueIndex>=idleQueue.length-1) {
// 	// 	queuePopulated = false;
// 	// 	queueIndex = idleQueue.length = 0;
// 	// }
// 	// else {
// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
// 	// }
// }

/**
 * Diff two virtual nodes and apply proper changes to the DOM
 * @param {import('../internal').PreactElement} dom The DOM element representing
 * the virtual nodes under diff
 * @param {import('../internal').PreactElement} parent The parent of the DOM element
 * @param {import('../internal').VNode | null} newTree The new virtual node
 * @param {import('../internal').VNode | null} oldTree The old virtual node
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this element is an SVG node
 * @param {boolean} append Whether or not to immediately append the new DOM
 * element after diffing
 * @param {Array<import('../internal').PreactElement>} excessChildren
 * @param {Array<import('../internal').Component>} mounts A list of newly
 * mounted components
 * @param {import('../internal').Component | null} ancestorComponent The direct
 * parent component
 * @param {import('../internal').VNode} parentVNode Used to set `_lastSibling`
 * pointer to keep track of our current position
 */
export function diff(dom, parent, newTree, oldTree, context, isSvg, append, excessChildren, mounts, ancestorComponent, parentVNode) {
	// if (dom!=null && now!==true) {
	// 	// console.log('queueing');

	// 	for (let i=0; i<idleQueue.length; i++) {
	// 		if (idleQueue[i]!=null && idleQueue[i][0]===dom) {
	// 			idleQueue[i] = null;
	// 			break;
	// 		}
	// 	}
	// 	if (idleQueue.push([dom, parent, newTree, oldTree, context, isSvg, true])===1) {
	// 		requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
	// 	}

	// 	// let entry = [dom, parent, newTree, oldTree, context, isSvg, true];
	// 	// for (let i=queueIndex; i<idleQueue.length; i++) {
	// 	// 	if (idleQueue[i][0]===dom) {
	// 	// 		console.log('found earlier entry at '+i+' instead of '+idleQueue.length, dom);
	// 	// 		idleQueue[i] = entry;
	// 	// 		entry = null;
	// 	// 		break;
	// 	// 	}
	// 	// }
	// 	// if (entry!=null) idleQueue.push(entry);
	// 	// if (queuePopulated===false) {
	// 	// 	queuePopulated = true;
	// 	// 	requestIdleCallback(processIdleQueue, IDLE_OPTIONS);
	// 	// }

	// 	// setTimeout(processIdleQueue);
	// 	// requestIdleCallback( () => {
	// 	// 	let out = diff(dom, parent, newTree, oldTree, context, isSvg, true);
	// 	// 	if (out!==dom) {
	// 	// 		console.log('replacing ', dom, ' with ', out);
	// 	// 	}
	// 	// 	// if (out==null) dom.remove();
	// 	// 	// else dom.parentNode.replaceChild(out, dom);
	// 	// }, IDLE_OPTIONS);
	// 	return newTree._el = dom;
	// 	// return newTree._el = dom;
	// }

	// if (newTree==null) {
	// 	newTree = createVNode(3, null, null, null, '', null);
	// 	// return;
	// 	// let c = document.createComment('empty');
	// 	// if (parent!=null) {
	// 	// 	if (dom!=null) parent.replaceChild(c, dom);
	// 	// 	else parent.appendChild(c);
	// 	// }
	// 	// if (oldTree!=null) unmount(oldTree);
	// 	// return c;
	// }

	// assertIsPureVNode(newTree);
	// console.log(oldTree, newTree);

	// If the previous tag doesn't match the new tag we drop the whole subtree
	if (oldTree==null || newTree==null || oldTree.tag!==newTree.tag) {
		if (oldTree!=null) unmount(oldTree, ancestorComponent);
		if (newTree==null) return null;
		dom = null;
		oldTree = EMPTY_OBJ;
	}

	let c, p, isNew = false, oldProps, oldState, oldContext,
		newTag = newTree.tag, lastSibling;

	/** @type {import('../internal').Component | null} */
	let clearProcessingException;

	// @TODO unmounting here removes the dom pointer
	// if (newTree.tag!==oldTag) {
	// 	// console.log('mismatched tags: ', oldTag, newTree.tag);
	// 	if (typeof oldTag==='function') {
	// 		unmount(oldTree);
	// 	}
	// 	if (typeof newTree.tag==='function') {
	// 		mount(newTree);
	// 	}
	// }

	try {
		let isOldTreeFragment;
		outer: if ((isOldTreeFragment = oldTree.tag === Fragment) || newTag === Fragment) {
			oldTree = oldTree==null ? EMPTY_ARR : !isOldTreeFragment ? [oldTree] : getVNodeChildren(oldTree);
			diffChildren(parent, getVNodeChildren(newTree), oldTree, context, isSvg, excessChildren, mounts, c, newTree);

			// The new dom element for fragments is the first child of the new tree
			// When the first child of a Fragment is passed through `diff()`, it sets its dom
			// element to the parentVNode._el property (that assignment is near the bottom of
			// this function), which is read here.
			dom = newTree._el;
			lastSibling = newTree._lastSibling;
		}
		else if (typeof newTag==='function') {

			// Get component and set it to `c`
			if (oldTree._component) {
				c = newTree._component = oldTree._component;
				clearProcessingException = c._processingException;
			}
			else {
				isNew = true;
				// c = newTree._component = new newTree.tag(newTree.props, context);
				// c = newTree._component = createComponent(newTag, newTree.props, context, ancestorComponent);

				// Instantiate the new component
				if (newTag.prototype && newTag.prototype.render) {
					newTree._component = c = new newTag(newTree.props, context); // eslint-disable-line new-cap
					// @TODO this really shouldn't be necessary and people shouldn't rely on it!
					// Component.call(c, newTree.props, context);
				}
				else {
					newTree._component = c = new Component(newTree.props, context);
					c._constructor = newTag;
					c.render = doRender;
				}
				c._ancestorComponent = ancestorComponent;

				c.props = newTree.props;
				if (!c.state) c.state = {};
				c.context = context;
			}

			c._vnode = newTree;

			// Invoke getDerivedStateFromProps
			let s = c._nextState || c.state;
			if (newTag.getDerivedStateFromProps!=null) {
				oldState = assign({}, c.state);
				if (s===c.state) s = assign({}, s);
				assign(s, newTag.getDerivedStateFromProps(newTree.props, s));
			}

			// Invoke pre-render lifecycle methods
			if (isNew) {
				if (newTag.getDerivedStateFromProps==null && c.componentWillMount!=null) c.componentWillMount();
				mounts.push(c);

				// if (dom==null && newTree.tag.recycle===true && newTree.tag.__cache!=null) {
				// 	console.log('using recycled DOM');
				// 	c._previousVTree = newTree.tag.__cache.pop();
				// 	c.base = c._previousVTree._el;
				// }

				// if (dom == null && newTree.tag.$cache!=null) {
				// 	dom = newTree.tag.$cache.pop();
				// }
				// if (dom==null && newTree.tag.$precache!=null) {
				// 	if (newTree.tag.$cache==null) {
				// 		let t = document.createElement('template');
				// 		newTree.tag.$cache = newTree.tag.$precache.cloneNode(true);
				// 		t.appendChild(newTree.tag.$cache);
				// 	}
				// 	dom = newTree.tag.$cache.cloneNode(true);
				// }
			}
			else {
				// let nextState = c.state;
				// if (c.prevState!=null) {
				// 	c.state = c.prevState;
				// 	c.prevState = null;
				// }

				// console.log('updating component in-place', c._nextState);
				// if (c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newTree.props, c.state)===false) {
				// 	c.state = nextState;
				if (!c._force && c.shouldComponentUpdate!=null && c.shouldComponentUpdate(newTree.props, s, context)===false) {
					// dom = c.base;
					break outer;
					// return newTree._el = c.base;
				}
				if (newTag.getDerivedStateFromProps==null && c.componentWillReceiveProps!=null) {
					c.componentWillReceiveProps(newTree.props, context);
				}

				if (c.componentWillUpdate!=null) {
					c.componentWillUpdate(newTree.props, s, context);
				}
				// c.state = nextState;
			}

			oldProps = c.props;
			if (!oldState) oldState = c.state;

			oldContext = c.context = context;
			c.props = newTree.props;
			c.state = s;

			let prev = c._previousVTree;
			let vnode = c._previousVTree = coerceToVNode(c.render(c.props, c.state, c.context));
			c._dirty = false;

			if (c.getChildContext!=null) {
				context = assign(assign({}, context), c.getChildContext());
			}

			if (!isNew && c.getSnapshotBeforeUpdate!=null) {
				oldContext = c.getSnapshotBeforeUpdate(oldProps, oldState);
			}

			c.base = dom = diff(dom, parent, vnode, prev, context, isSvg, append, excessChildren, mounts, c, newTree);

			if (vnode!=null) {
				lastSibling = vnode._lastSibling;
			}

			c._parent = parent;
			c._parentVNode = parentVNode;

			if (newTree.ref) applyRef(newTree.ref, c, ancestorComponent);
			// context = assign({}, context);
			// context.__depth = (context.__depth || 0) + 1;
			// context = assign({
			// 	__depth: (context.__depth || 0) + 1
			// }, context);
			// if (c.getChildContext!=null) {
			// 	assign(context, c.getChildContext());
			// }
			// if (c.id==20) {
			// 	// console.trace('diffing '+c.id);
			// 	console.log('diffing '+c.id, vnode, prev);
			// }
			// newTree.tag.$precache = c.base;

			// if (dom!=null && c.base!=null && c.base!==dom) {
			// 	parent.replaceChild(c.base, dom);
			// }

			// if (dom!=null && (c.base!==dom || !dom.parentNode)) {
			// 	if (c.base==null) unmount(prev);
			// 	else if (dom.parentNode!==parent) parent.appendChild(c.base);
			// 	else parent.replaceChild(c.base, dom);
			// }
			// if (isNew) {
			// 	mounts.push(c);
			// 	// if (c.componentDidMount!=null) c.componentDidMount();
			// }
			// else if (c.componentDidUpdate!=null) {
			// 	c.componentDidUpdate(oldProps, oldState, oldContext);
			// }
			// }
		}
		else {
			dom = lastSibling = newTree._lastSibling = diffElementNodes(dom, parent, newTree, oldTree, context, isSvg, excessChildren, mounts, ancestorComponent);

			if (newTree.ref && (oldTree.ref !== newTree.ref)) {
				applyRef(newTree.ref, dom, ancestorComponent);
			}
		}

		// Update sibling pointers
		if (parentVNode._el==null) {
			parentVNode._el = dom;
		}

		parentVNode._lastSibling = lastSibling;

		if (parent && append!==false && dom!=null && dom.parentNode!==parent) {
			parent.appendChild(dom);
		}

		newTree._el = dom;

		if (c!=null) {
			while (p=c._renderCallbacks.pop()) p();

			if (!isNew && c.componentDidUpdate!=null) {
				c.componentDidUpdate(oldProps, oldState, oldContext);
			}
		}

		if (clearProcessingException) {
			c._processingException = null;
		}
	}
	catch (e) {
		catchErrorInComponent(e, ancestorComponent);
	}

	// console.log(isRootDiff);

	// if (oldTree!=null && oldTree!==newTree) unmount(oldTree, true, oldTree._el!==dom);

	// if (originalOldTree && originalOldTree._el && originalOldTree._el!==dom) {
	// 	unmount(originalOldTree);
	// }

	return dom;
}

export function flushMounts(mounts) {
	let c;
	while ((c = mounts.pop())) {
		if (c.componentDidMount!=null) {
			try {
				c.componentDidMount();
			}
			catch (e) {
				catchErrorInComponent(e, c._ancestorComponent);
			}
		}
	}
}

/**
 * Diff two virtual nodes representing DOM element
 * @param {import('../internal').PreactElement} dom The DOM element representing
 * the virtual nodes being diffed
 * @param {import('../internal').PreactElement} parent The parent DOM element
 * @param {import('../internal').VNode} vnode The new virtual node
 * @param {import('../internal').VNode} oldVNode The old virtual node
 * @param {object} context The current context object
 * @param {boolean} isSvg Whether or not this DOM node is an SVG node
 * @param {*} excessChildren
 * @param {Array<import('../internal').Component>} mounts An array of newly
 * mounted components
 * @param {import('../internal').Component} ancestorComponent The parent
 * component to the ones being diffed
 * @returns {import('../internal').PreactElement}
 */
function diffElementNodes(dom, parent, vnode, oldVNode, context, isSvg, excessChildren, mounts, ancestorComponent) {
	// if (vnode==null) {
	// 	let c = document.createComment('empty');
	// 	if (parent!=null) {
	// 		if (dom!=null) parent.replaceChild(c, dom);
	// 		else parent.appendChild(c);
	// 	}
	// 	if (oldVNode!=null) unmount(oldVNode);
	// 	return c;
	// }

	let d = dom;

	// Tracks entering and exiting SVG namespace when descending through the tree.
	isSvg = isSvg ? vnode.tag !== 'foreignObject' : vnode.tag === 'svg';

	// if (oldVNode!=null) {
	// 	if (vnode.tag!==oldVNode.tag) console.log('vnode tag mismatch: ', oldVNode.tag, vnode.tag);
	// }
	// if (dom==null && excessChildren!=null) {
	// 	console.log('hydrating from '+excessChildren.length+' excess children', excessChildren.map( c => c && `${c.nodeType} @ ${c.localName}`), vnode.tag);
	// 	for (let j=0; j<excessChildren.length; j++) {
	// 		let c = excessChildren[j];
	// 		if (c!=null && c.localName==vnode.tag) {
	// 			// oldVNode = toVNode(dom = d = c);
	// 			// console.log(vnode, oldVNode, dom);
	// 			excessChildren[j] = null;
	// 			// console.log('found hydration match for '+vnode.tag, oldVNode);
	// 			// console.log(oldVNode, vnode);
	// 			// dom = toVNode(c);
	// 			break;
	// 		}
	// 	}
	// }

	if (dom==null) {
	// if (dom==null || vnode.tag!==(oldVNode==null?null:oldVNode.tag)) {
		// return create(dom, parent, vnode, context, isSvg);
		//dom = create(dom, parent, vnode, context, isSvg);

		// if (vnode.tag===null) {
		// 	dom = document.createTextNode(vnode.text);
		// }
		// else if (isSvg) {
		// 	dom = document.createElementNS('http://www.w3.org/2000/svg', vnode.tag);
		// }
		// else {
		// 	dom = document.createElement(vnode.tag);
		// }
		// // else {
		// // 	dom = isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);
		// // }

		// vnode._el = dom;

		// vnode._el = dom = typeof vnode==='string' || typeof vnode==='number' ? document.createTextNode(vnode) : isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);
		vnode._el = dom = vnode.tag===null ? document.createTextNode(vnode.text) : isSvg ? document.createElementNS('http://www.w3.org/2000/svg', vnode.tag) : document.createElement(vnode.tag);

		// dom = vnode.tag===null ? document.createTextNode(vnode.text) : document.createElement(vnode.tag);
		// if (d) {
		// 	parent.replaceChild(dom, d);
		// 	while (d.firstChild) dom.appendChild(d.firstChild);
		// }
	}

	// if (typeof vnode==='string' || typeof vnode==='number') {
	if (vnode.tag===null) {
		if (dom===d && vnode.text!==oldVNode.text) {
			dom.data = vnode.text;
		}
	}
	else {
		if (excessChildren!=null && dom.childNodes!=null) {
			excessChildren = EMPTY_ARR.slice.call(dom.childNodes);
		}
		// console.log('diffChildren(', getVNodeChildren(vnode).map( p => Object.assign({}, p) ), getVNodeChildren(oldVNode).map( p => Object.assign({}, p) ), ')');
		// let newChildren = getVNodeChildren(vnode);
		// diffChildren(dom, newChildren, vnode===oldVNode ? newChildren : oldVNode==null ? [] : getVNodeChildren(oldVNode), context, isSvg, excessChildren);
		if (vnode!==oldVNode) {
			diffProps(dom, vnode.props, oldVNode==EMPTY_OBJ ? EMPTY_OBJ : oldVNode.props, isSvg);
		}

		diffChildren(dom, getVNodeChildren(vnode), oldVNode==EMPTY_OBJ ? EMPTY_ARR : getVNodeChildren(oldVNode), context, isSvg, excessChildren, mounts, ancestorComponent, vnode);
	}
	// if (oldVNode!=null && dom!==d) unmount(oldVNode);

	return dom;
}

/**
 * Invoke or update a ref, depending on whether it is a function or object ref.
 * @param {object|function} [ref=null]
 * @param {any} [value]
 */
export function applyRef(ref, value, ancestorComponent) {
	try {
		if (typeof ref=='function') ref(value);
		else ref.current = value;
	}
	catch (e) {
		catchErrorInComponent(e, ancestorComponent);
	}
}

/**
 * Unmount a virtual node from the tree and apply DOM changes
 * @param {import('../internal').VNode} vnode The virtual node to unmount
 * @param {import('../internal').Component} ancestorComponent The parent
 * component to this virtual node
 */
export function unmount(vnode, ancestorComponent) {
	let r;
	if (r = vnode.ref) {
		applyRef(r, null, ancestorComponent);
	}

	if ((r = vnode._el)!=null) r.remove();

	vnode._el = vnode._lastSibling = null;

	if ((r = vnode._component)!=null) {
		if (r.componentWillUnmount) {
			try {
				r.componentWillUnmount();
			}
			catch (e) {
				catchErrorInComponent(e, ancestorComponent);
			}
		}
		// let ctor = r.constructor;
		// let cache = ctor.$cache || (ctor.$cache = []);
		// cache.push(r.base);

		// let ctor = r.constructor;
		// if (r.base!=null && ctor.recycle===true) {
		// 	(ctor.__cache || (ctor.__cache=[])).push(r._previousVTree);
		// 	return;
		// }

		r.base = r._parent = null;
		if (r = r._previousVTree) unmount(r, ancestorComponent);
	}
	else if (r = vnode._children) {
		for (let i = 0; i < r.length; i++) {
			unmount(r[i], ancestorComponent);
		}
	}

	// if (recursive) {
	// 	let children = getVNodeChildren(vnode);
	// 	for (let i=children.length; i--; ) {
	// 		unmount(children[i], true, false);
	// 	}
	// }
}

// export function mount(vnode) {
// 	let r;
// 	if (vnode.props!=null && (r = vnode.props.ref)) r(vnode._component!=null ? vnode._component : vnode._el);
// 	if ((r = vnode._component)) {
// 		if (r.componentWillMount) r.componentWillMount();
// 		r.base = vnode._el;
// 	}
// }

/*
function getVNodeChildren(vnode) {
	if (vnode._children) return vnode._children;
	let list = [],
		stack = vnode.children.slice(),
		key = 0,
		child,
		type;
	while (stack.length) {
		child = stack.pop();
		if (child && child.pop!==undefined) {
			while (child.length) stack.push(child.pop());
		}
		else {
			type = typeof vnode;
			if (type!=='object' && type!=='function') {
				child = createVNode(TEXT_NODE, null, null, null, child, key);
			}
			else if (!child.key) {
				child.key = key;
			}
			list.push(child);
			key++;
		}
	}
	return vnode._children = list;
}
*/

// const ARR = [];

/**
 * Get the children of a virtual node as a flat array
 * @param {import('../internal').VNode} vnode The virtual node to get the
 * children of
 * @returns {Array<import('../internal').VNode>} The virtual node's children
 */
export function getVNodeChildren(vnode) {
	if (vnode._children==null) {
		// flattenChildren(vnode.children, vnode._children=[], '', 0);
		// flattenChildren(vnode.props.children, vnode._children=[], '', 0);
		flattenChildren(vnode.props.children, vnode._children=[]);
	}
	return vnode._children;

	// ARR[0] = vnode.children;
	// flattenChildren(ARR, children, '', 0);
	// flattenChildren(Array.isArray(vnode.children) ? vnode.children : [vnode.children], children, '', 0);
	// if (vnode._children!=null) {
	// 	return vnode._children;
	// }
	// // ARR[0] = vnode.children;
	// // flattenChildren(ARR, children, '', 0);
	// // flattenChildren(Array.isArray(vnode.children) ? vnode.children : [vnode.children], children, '', 0);
	// let children = vnode._children = [];
	// flattenChildren(vnode.children, children, '', 0);
	// return children;
}

// function flattenChildren(children, flattened, path, index) {
// function flattenChildren(children, flattened, key, index) {
/**
 * Flatten a virtual nodes children to a single dimensional array
 * @param {import('../index').ComponentChildren} children The unflattened
 * children of a virtual node
 * @param {Array<import('../index').ComponentChild>} flattened An flat array of children to modify
 */
function flattenChildren(children, flattened) {
	// let i = 0;
	//	key = path+'.'+index,	//key = path.length===0 ? index : `${path}.${index}`,
	//	isObject = children===Object(children);
	//	child,
	//	type = typeof children;

	// if (key) key += '.';

	// key += '.';
	// key += index;

	// if (type==='object' && typeof Symbol!=='undefined' && Symbol.iterator in children) {
	// 	children = Array.from(children);
	// }
	// if (children==null) {}
	if (children==null || typeof children === 'boolean') {}
	// else if (isObject && 'pop' in children) {
	else if (Array.isArray(children)) {
	// else if (type==='object' && 'pop' in children) {
		for (let i=0; i < children.length; i++) {
			// let child = children[i];
			// let key = path + '.' + i;
			// if ('pop' in child) {
			// 	flattenChildren(child, flattened, key, i);
			// }
			// else {
			// 	pushChild(child, flattened, key, i, typeof child);
			// }
			flattenChildren(children[i], flattened);
			// flattenChildren(children[i], flattened, key, i);
		}
	}
	// else if (type==='object' && Symbol.iterator in children) {
	// 	flattenChildren(Array.from(children), flattened, key, index);
	// }
	// else if (type==='object' && Symbol.iterator in children) {
	// 	let child;
	// 	while ((child = children.next()).done !== true) {
	// 		flattenChildren(child.value, flattened, key, i++);
	// 	}
	// }
	else {
		// pushChild(children, flattened, path, index, type);
		// if (!isObject) {

		// if (typeof children!=='object' && typeof children!=='function') {
		// 	children = createVNode(TEXT_NODE, null, null, null, children, null);
		// 	// children = createVNode(TEXT_NODE, null, null, null, children, key);
		// }
		children = coerceToVNode(children);

		// else if (children.key == null) {
		// 	children.key = key;
		// }
		// if (flattened.indexOf(children)!==-1) {
		// 	children = assign({}, children);
		// }
		flattened.push(children);
	}
}

// function pushChild(child, flattened, path, index, key, type) {
// 	if (type!=='object' && type!=='function') {
// 		child = createVNode(TEXT_NODE, null, null, null, child, key);
// 	}
// 	else if (child.key == null) {
// 		child.key = key;
// 	}
// 	if (flattened.indexOf(child)!==-1) {
// 		child = assign({}, child);
// 	}
// 	flattened.push(child);
// }

// function flattenChildren(children) {
// 	let stack = [children],
// 		result = [],
// 		child;

// 	while (stack.length) {
// 		let item = stack.pop();
// 		if (item && 'pop' in item) {
// 			for (let i=0; i<item.length; i++) stack.push(item[i]);
// 		}
// 		else if (item && Symbol.iterator in item) {
// 			while ((child = item.next()).done!==true) {
// 				stack.push(child.value);
// 			}
// 		}
// 		else {
// 			result.push(item);
// 		}
// 	}

// 	return result.reverse();
// }

/** The `.render()` method for a PFC backing instance. */
function doRender(props, state, context) {
	return this._constructor(props, context);
}

/**
 * Find the closest error boundary to a thrown error and call it
 * @param {object} error The thrown value
 * @param {import('../internal').Component} component The first ancestor
 * component check for error boundary behaviors
 */
function catchErrorInComponent(error, component) {
	for (; component; component = component._ancestorComponent) {
		if (component.componentDidCatch && !component._processingException) {
			try {
				component.componentDidCatch(error);
				return enqueueRender(component._processingException = component);
			}
			catch (e) {
				error = e;
			}
		}
	}
	throw error;
}


/*global coverage, ENABLE_PERFORMANCE, NODE_ENV*/
/*eslint no-console:0*/
/** @jsx h */
import { setupScratch, teardown } from '../_util/helpers';
let { createElement: h, Component, render } = require(NODE_ENV==='production' ? '../../dist/ceviche.min.js' : '../../dist/ceviche');

const MULTIPLIER = ENABLE_PERFORMANCE ? (coverage ? 5 : 1) : 999999;

let now = typeof performance!=='undefined' && performance.now ? () => performance.now() : () => +new Date();

function loop(iter, time) {
	let start = now(),
		count = 0;
	while ( now()-start < time ) {
		count++;
		iter();
	}
	return count;
}

function benchmark(iter, callback) {
	let a = 0; // eslint-disable-line no-unused-vars
	function noop() {
		try { a++; }
		finally { a += Math.random(); }
	}

	// warm
	for (let i=100; i--; ) noop(), iter();

	let count = 2,
		time = 500,
		passes = 0,
		noops = loop(noop, time),
		iterations = 0;

	function next() {
		iterations += loop(iter, time);
		setTimeout(++passes===count ? done : next, 10);
	}

	function done() {
		let ticks = Math.round(noops / iterations * count),
			hz = iterations / count / time * 1000,
			message = `${hz|0}/s (${ticks} ticks)`;
		callback({ iterations, noops, count, time, ticks, hz, message });
	}

	next();
}

describe('performance', function() {
	let scratch;

	this.timeout(10000);

	before(function () {
		if (!ENABLE_PERFORMANCE) this.skip();
		if (coverage) {
			console.warn('WARNING: Code coverage is enabled, which dramatically reduces performance. Do not pay attention to these numbers.');
		}
	});

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	it('should rerender without changes fast', done => {
		let jsx = (

a {'b'} c {0} d

						Foo
						Bar

					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<button-bar>
							<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
							<button style="top:0 ; right: 20">Poor CSS</button>
							<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
							<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
						</button-bar>
					</form>

		);

		let root;
		benchmark( () => {
			root = render(jsx, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: empty diff: ${message}`);
			expect(ticks).to.be.below(150 * MULTIPLIER);
			done();
		});
	});

	it('should rerender repeated trees fast', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		class ButtonBar extends Component {
			render() {
				return (
					<button-bar>
						<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
						<Button style="top:0 ; right: 20">Poor CSS</Button>
						<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
						<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
					</button-bar>
				);
			}
		}
		class Button extends Component {
			render(props) {
				return <button {...props} />;
			}
		}
		class Main extends Component {
			render() {
				return <Form />;
			}
		}
		class Root extends Component {
			render() {
				return (

				);
			}
		}
		class Empty extends Component {
			render() {
				return
;
			}
		}
		class Parent extends Component {
			render({ child: C }) {
				return <C />;
			}
		}

		let root;
		benchmark( () => {
			root = render(<Parent child={Root} />, scratch, root);
			root = render(<Parent child={Empty} />, scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: repeat diff: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should construct large VDOM trees fast', done => {
		const FIELDS = [];
		for (let i=100; i--; ) FIELDS.push((i*999).toString(36));

		let out = [];
		function digest(vnode) {
			out.push(vnode);
			out.length = 0;
		}
		benchmark( () => {
			digest(

a {'b'} c {0} d

							Foo
							Bar

						<form onSubmit={() => {}}>
							<input type="checkbox" checked />
							<input type="checkbox" />
							<fieldset>
								{ FIELDS.map( field => (

										{field}:
										<input placeholder={field} />

								)) }
							</fieldset>
							<button-bar>
								<button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</button>
								<button style="top:0 ; right: 20">Poor CSS</button>
								<button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</button>
								<button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</button>
							</button-bar>
						</form>

			);
		}, ({ ticks, message }) => {
			console.log(`PERF: large VTree: ${message}`);
			expect(ticks).to.be.below(2000 * MULTIPLIER);
			done();
		});
	});

	it('should mutate styles/properties quickly', done => {
		let counter = 0;
		const keyLooper = n => c => c % n ? `${c}px` : c;
		const get = (obj, i) => obj[i%obj.length];
		const CLASSES = ['foo', 'foo bar', '', 'baz-bat', null];
		const STYLES = [];
		const MULTIVALUE = ['0 1px', '0 0 1px 0', '0', '1px', '20px 10px', '7em 5px', '1px 0 5em 2px'];
		const STYLEKEYS = [
			['left', keyLooper(3)],
			['top', keyLooper(2)],
			['margin', c => get(MULTIVALUE, c).replace('1px', c+'px')],
			['padding', c => get(MULTIVALUE, c)],
			['position', c => c%5 ? c%2 ? 'absolute' : 'relative' : null],
			['display', c => c%10 ? c%2 ? 'block' : 'inline' : 'none'],
			['color', c => `rgba(${c%255}, ${255 - c%255}, ${50+c%150}, ${c%50/50})`],
			['border', c => c%5 ? `${c%10}px ${c%2?'solid':'dotted'} ${STYLEKEYS[6][1](c)}` : '']
		];
		for (let i=0; i<1000; i++) {
			let style = {};
			for (let j=0; j (

				<input type="checkbox" checked={index%3 == 0} />
				<input value={`test ${index/4|0}`} disabled={index%10 ? null : true} />

p1

p2

p3

p4

		);

		let root, count=0;
		benchmark( () => {
			root = render(app(++count), scratch, root);
		}, ({ ticks, message }) => {
			console.log(`PERF: style/prop mutation: ${message}`);
			expect(ticks).to.be.below(350 * MULTIPLIER);
			done();
		});
	});

	it('should hydrate from SSR quickly', done => {
		class Header extends Component {
			render() {
				return (

a {'b'} c {0} d

							Foo
							Bar

				);
			}
		}
		class Form extends Component {
			render() {
				return (
					<form onSubmit={() => {}}>
						<input type="checkbox" checked />
						<input type="checkbox" checked={false} />
						<fieldset>
							<input type="radio" checked />
							<input type="radio" />
						</fieldset>
						<ButtonBar />
					</form>
				);
			}
		}
		const ButtonBar = () => (
			<button-bar>
				<Button style="width:10px; height:10px; border:1px solid #FFF;">Normal CSS</Button>
				<Button style="top:0 ; right: 20">Poor CSS</Button>
				<Button style="invalid-prop:1;padding:1px;font:12px/1.1 arial,sans-serif;" icon>Poorer CSS</Button>
				<Button style={{ margin: 0, padding: '10px', overflow: 'visible' }}>Object CSS</Button>
			</button-bar>
		);
		class Button extends Component {
			handleClick() {}
			render(props) {
				return <button onClick={this.handleClick} {...props} />;
			}
		}
		const Main = () => <Form />;
		class App extends Component {
			render() {
				return (

				);
			}
		}

		render(<App />, scratch, scratch.firstChild);
		let html = scratch.innerHTML;

		benchmark( () => {
			scratch.innerHTML = html;
			render(<App />, scratch, scratch.firstChild);
		}, ({ ticks, message }) => {
			console.log(`PERF: SSR Hydrate: ${message}`);
			expect(ticks).to.be.below(3000 * MULTIPLIER);
			done();
		});
	});
});


import { createElement, cloneElement, Component as CevicheComponent, render } from 'ceviche';
const ATTRS_DESCRIPTOR = {
	configurable: true,
	enumerable: true,
	get() {
		return this.props;
	}
};
export function h(...args) {
	let vnode = createElement(...args);
	Object.defineProperty(vnode, 'attributes', ATTRS_DESCRIPTOR);
	return vnode;
}

function asArray(arr) {
	return Array.isArray(arr) ? arr : [arr];
}

function normalize(obj) {
	if (Array.isArray(obj)) {
		return obj.map(normalize);
	}
	if ('tag' in obj && !('attributes' in obj)) {
		Object.defineProperty(obj, 'attributes', ATTRS_DESCRIPTOR);
	}
	return obj;
}

export function Component (props, context) {
	CevicheComponent.call(this, props, context);
	const render = this.render;
	this.render = function(props, state, context) {
		if (props.children) props.children = asArray(normalize(props.children));
		return render.call(this, props, state, context);
	};
}
Component.prototype = new CevicheComponent();

export { createElement, cloneElement, render };


import { assign } from './util';
import { EMPTY_ARR } from './constants';
// import { createElement, createVNode } from './create-element';

/**
 * Clones the given VNode, optionally adding attributes/props and replacing its children.
 * @param {import('./internal').VNode} vnode The virtual DOM element to clone
 * @param {object} props Attributes/props to add when cloning
 * @param {Array<import('./index').ComponentChildren>} rest Any additional arguments will be used as replacement children.
 */
export function cloneElement(vnode, props) {
	// let children = [], i = 2;
	// for ( ; i<arguments.length; i++) children.push(arguments[i]);
	let out = assign({}, vnode);
	out.props = assign(assign({}, out.props), props);
	// if (i>2) out.children = children;
	// if (arguments.length>2) out.props.children = EMPTY_ARR.slice.call(arguments, 2);
	if (arguments.length>2) out.props.children = EMPTY_ARR.slice.call(arguments, 2);
	return out;
	// return createVNode(vnode.tag, assign(assign({}, vnode.props), props), i>2 ? children : null, vnode.text, vnode.key);

	// return createElement(
	// 	vnode.tag,
	// 	assign(assign({}, vnode.props), props),
	// 	arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children
	// );
}

// export function cloneElement(vnode, props) {
// 	return createElement(
// 		vnode.tag,
// 		assign(assign({}, vnode.props), props),
// 		arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children
// 	);
// }

// export function cloneElement(tag, props) {
// 	let vnode = tag;
// 	arguments[0] = vnode.tag;
// 	let el = createElement.apply(null, arguments);
// 	for (let i in vnode.props) if (el.props[i]===undefined) el.props[i] = vnode.props[i];
// 	if (arguments.length<3) el.children = vnode.children;
// 	return el;
// }


c.props = newTree.props;
if (!c.state) c.state = {};
c.context = context;
}
c._vnode = newTree;
// Invoke getDerivedStateFromProps

c._dirty = true;
c._renderCallbacks = [];


import { EMPTY_OBJ, EMPTY_ARR } from './constants';
import { diff, flushMounts } from './diff/index';
import { diffChildren } from './diff/children';
import { coerceToVNode } from './create-element';

/**
 * Render a Preact virtual node into a DOM element
 * @param {import('./index').ComponentChild} vnode The virtual node to render
 * @param {import('./internal').PreactElement} parent The DOM element to
 * render into
 */
export function render(vnode, parent) {
	let oldTree = parent._previousVTree;
	if (oldTree) {
		let mounts = [];
		diff(oldTree._el, parent, parent._previousVTree = coerceToVNode(vnode), oldTree, EMPTY_OBJ, parent.ownerSVGElement!==undefined, true, null, mounts, null, {});
		flushMounts(mounts);
	}
	else hydrate(vnode, parent);
}

/**
 * Update an existing DOM element with data from a Preact virtual node
 * @param {import('./index').ComponentChild} vnode The virtual node to render
 * @param {import('./internal').PreactElement} parent The DOM element to
 * update
 */
export function hydrate(vnode, parent) {
	parent._previousVTree = vnode = coerceToVNode(vnode);
	let mounts = [];
	diffChildren(parent, [vnode], EMPTY_ARR, EMPTY_OBJ, parent.ownerSVGElement!==undefined, EMPTY_ARR.slice.call(parent.childNodes), mounts, null, {});
	flushMounts(mounts);
}


// function createRoot(title) {
// 	let div = document.createElement('div');
// 	let h2 = document.createElement('h2');
// 	h2.textContent = title;
// 	div.appendChild(h2);
// 	document.body.appendChild(div);
// 	return div;
// }

/*
function logCall(obj, method, name) {
	let old = obj[method];
	obj[method] = function(...args) {
		console.log(`<${this.localName}>.`+(name || `${method}(${args})`));
		return old.apply(this, args);
	};
}

logCall(HTMLElement.prototype, 'appendChild');
logCall(HTMLElement.prototype, 'removeChild');
logCall(HTMLElement.prototype, 'insertBefore');
logCall(HTMLElement.prototype, 'replaceChild');
logCall(HTMLElement.prototype, 'setAttribute');
logCall(HTMLElement.prototype, 'removeAttribute');
let d = Object.getOwnPropertyDescriptor(Node.prototype, 'nodeValue');
Object.defineProperty(Text.prototype, 'nodeValue', {
	get() {
		let value = d.get.call(this);
		console.log('get Text#nodeValue: ', value);
		return value;
	},
	set(v) {
		console.log('set Text#nodeValue', v);
		return d.set.call(this, v);
	}
});

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

class Foo extends Component {
	componentDidMount() {
		console.log('mounted');
		this.timer = setInterval( () => {
			this.setState({ time: Date.now() });
		}, 5000);
	}
	componentWillUnmount() {
		clearInterval(this.timer);
	}
	render(props, state, context) {
		// console.log('rendering', props, state, context);
		return <time>test: {state.time}</time>
	}
}

render((

This is a test.

		<Foo />
		<time>...</time>

), createRoot('Stateful component update demo:'));

let items = [];
let count = 0;
let three = createRoot('Top-level render demo:');

setInterval( () => {
	if (count++ %20 < 10 ) {
		items.push(
item #{items.length}
);
	}
	else {
		items.shift();
	}

	render((

This is a test.

			<time>{Date.now()}</time>

{items}

	), three);
}, 5000);

// Mount the top-level component to the DOM:
render(, document.body);
*/


import { createElement as h, render, Component } from '../../src/index';
import { setupScratch, teardown, setupRerender } from '../_util/helpers';

/** @jsx h */

// const EMPTY_CHILDREN = [];

describe('Component spec', () => {
	let scratch, rerender;

	beforeEach(() => {
		scratch = setupScratch();
		rerender = setupRerender();
	});

	afterEach(() => {
		teardown(scratch);
	});

	describe('defaultProps', () => {
		it('should apply default props on initial render', () => {
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					expect(props).to.be.deep.equal({
						fieldA: 1, fieldB: 2,
						fieldC: 1, fieldD: 2
					});
				}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
			render(<WithDefaultProps fieldA={1} fieldB={2} fieldD={2} />, scratch);
		});

		it('should apply default props on rerender', () => {
			let doRender;
			class Outer extends Component {
				constructor() {
					super();
					this.state = { i: 1 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: 2 });
				}
				render(props, { i }) {
					return <WithDefaultProps fieldA={1} fieldB={i} fieldD={i} />;
				}
			}
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					this.ctor(props, context);
				}
				ctor(){}
				componentWillReceiveProps() {}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };

			let proto = WithDefaultProps.prototype;
			sinon.spy(proto, 'ctor');
			sinon.spy(proto, 'componentWillReceiveProps');
			sinon.spy(proto, 'render');

			render(<Outer />, scratch);
			doRender();

			const PROPS1 = {
				fieldA: 1, fieldB: 1,
				fieldC: 1, fieldD: 1
			};

			const PROPS2 = {
				fieldA: 1, fieldB: 2,
				fieldC: 1, fieldD: 2
			};

			expect(proto.ctor).to.have.been.calledWithMatch(PROPS1);
			expect(proto.render).to.have.been.calledWithMatch(PROPS1);

			rerender();

			// expect(proto.ctor).to.have.been.calledWith(PROPS2);
			expect(proto.componentWillReceiveProps).to.have.been.calledWithMatch(PROPS2);
			expect(proto.render).to.have.been.calledWithMatch(PROPS2);
		});

		// @TODO: migrate this to preact-compat
		xit('should cache default props', () => {
			class WithDefaultProps extends Component {
				constructor(props, context) {
					super(props, context);
					expect(props).to.be.deep.equal({
						fieldA: 1, fieldB: 2,
						fieldC: 1, fieldD: 2,
						fieldX: 10
					});
				}
				getDefaultProps() {
					return { fieldA: 1, fieldB: 1 };
				}
				render() {
					return <div />;
				}
			}
			WithDefaultProps.defaultProps = { fieldC: 1, fieldD: 1 };
			sinon.spy(WithDefaultProps.prototype, 'getDefaultProps');
			render((
				<div>
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
					<WithDefaultProps fieldB={2} fieldD={2} fieldX={10} />
				</div>
			), scratch);
			expect(WithDefaultProps.prototype.getDefaultProps).to.be.calledOnce;
		});
	});

	describe('forceUpdate', () => {
		it('should force a rerender', () => {
			let forceUpdate;
			class ForceUpdateComponent extends Component {
				componentWillUpdate() {}
				componentDidMount() {
					forceUpdate = () => this.forceUpdate();
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(ForceUpdateComponent.prototype, 'componentWillUpdate');
			sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
			render(<ForceUpdateComponent />, scratch);
			expect(ForceUpdateComponent.prototype.componentWillUpdate).not.to.have.been.called;

			forceUpdate();

			expect(ForceUpdateComponent.prototype.componentWillUpdate).to.have.been.called;
			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
		});

		it('should add callback to renderCallbacks', () => {
			let forceUpdate;
			let callback = sinon.spy();
			class ForceUpdateComponent extends Component {
				componentDidMount() {
					forceUpdate = () => this.forceUpdate(callback);
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(ForceUpdateComponent.prototype, 'forceUpdate');
			render(<ForceUpdateComponent />, scratch);

			forceUpdate();

			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.called;
			expect(ForceUpdateComponent.prototype.forceUpdate).to.have.been.calledWith(callback);
			expect(callback).to.have.been.called;
		});
	});
});