describe("knockout-jqAutocomplete", function(){
    var instance, engine, input, $input,
        sandbox = document.createElement("div");

    //helper engine that can use a template from a string
    var createStringTemplateEngine = function() {
        var templates = { data: {} };

        var stringTemplateSource = function(template) {
            this.text = function(value) {
                if (arguments.length === 0) {
                    return templates[template];
                }
                templates[template] = value;
            };
        };

        var templateEngine = new ko.nativeTemplateEngine();
        templateEngine.makeTemplateSource = function(template) {
            return new stringTemplateSource(template);
        };

        templateEngine.addTemplate = function(key, value) {
            templates[key] = value;
        };

        return templateEngine;
    };

    it("should create the jqAuto binding", function() {
        expect(ko.bindingHandlers.jqAuto).toBeDefined();
    });

    beforeEach(function() {
        instance = new ko.bindingHandlers.jqAuto.constructor();
        engine = createStringTemplateEngine();

        ko.setTemplateEngine(engine);

        document.body.appendChild(sandbox);
        input = document.createElement("div");
        $input = $(input);
        sandbox.appendChild(input);

        $("ul.ui-autocomplete").remove();
    });

    afterEach(function() {
        ko.removeNode(input);
    });

    describe("JqAuto constructor", function() {
        it("should be able to create a new instance", function() {
            expect(typeof instance).toEqual("object");
        });
    });

    describe("init", function() {
        it("should initialize autocomplete on the element", function() {
            instance.init(input, function() {
                return {};
            });

            expect($input.data("ui-autocomplete")).toBeDefined();
        });

        it("should apply additional options passed in the binding", function() {
            instance.init(input, function() {
                return {
                    options: {
                        delay: 1000
                    }
                };
            });

            expect($input.autocomplete("option", "delay")).toEqual(1000);
        });

        it("should apply global options", function() {
            instance.options = {
                delay: 2000
            };

            instance.init(input, function() {
                return {};
            });

            expect($input.autocomplete("option", "delay")).toEqual(2000);
        });

        it("should override global options with local options", function() {
            instance.options = {
                delay: 2000
            };

            instance.init(input, function() {
                return {
                    options: {
                        delay: 1000
                    }
                };
            });

            expect($input.autocomplete("option", "delay")).toEqual(1000);
        });

        it("should destroy the widget when the node is removed by KO", function() {
            var widget, called;

            instance.init(input, function() {
                return {};
            });

            widget = $input.data("ui-autocomplete");

            widget.destroy = function() {
                called = true;
            };

            ko.removeNode(input);

            expect(called).toBeTruthy();
        });

        it("should not cause an error if widget is already destroyed when the node is removed by KO", function() {
            instance.init(input, function() {
                return {};
            });

            $input.autocomplete("destroy");

            ko.removeNode(input);
        });

        it("should not cause an error if widget is already gone when the node is removed by KO", function() {
            var widget;

            instance.init(input, function() {
                return {};
            });

            widget = $input.data("ui-autocomplete");

            widget.destroy = null;

            ko.removeNode(input);
        });
    });

    describe("defaultFilter", function() {
        it("should not match when the item is null", function() {
            var result = instance.defaultFilter(null, "search");

            expect(result).toEqual(false);
        });

        it("should be able to match an item that is simply 0", function() {
            var result = instance.defaultFilter(0, "0");

            expect(result).toEqual(true);
        });

        it("should match within property values", function() {
            var result = instance.defaultFilter({
                test: "myValue"
            }, "my");

            expect(result).toEqual(true);
        });

        it("should match within property names", function() {
            var result = instance.defaultFilter({
                test: "myValue"
            }, "test");

            expect(result).toEqual(true);
        });

        it("should not match when term does not match any property/value", function() {
            var result = instance.defaultFilter({
                test: "myValue"
            }, "bad");

            expect(result).toEqual(false);
        });
    });

    describe("processOptions", function() {
        it("should call the response function with the results", function() {
            var responseData,
                response = jasmine.createSpy(),
                valueAccessor = function() {
                    return {};
                },
                data = [
                    "one",
                    "two",
                    "three"
                ],
                request = { term: "" };

            instance.processOptions(valueAccessor, null, data, request, response);

            responseData = response.calls.mostRecent().args[0];

            expect(responseData.length).toEqual(3);
            expect(JSON.stringify(responseData[0])).toEqual('{"label":"one","value":"one","actual":"one","data":"one"}');
            expect(JSON.stringify(responseData[1])).toEqual('{"label":"two","value":"two","actual":"two","data":"two"}');
            expect(JSON.stringify(responseData[2])).toEqual('{"label":"three","value":"three","actual":"three","data":"three"}');
        });

        it("should apply the filter to the results", function() {
            var responseData,
                response = jasmine.createSpy(),
                valueAccessor = function() {
                    return {};
                },
                data = [
                    "one",
                    "two",
                    "three"
                ],
                request = { term: "t" };

            instance.processOptions(valueAccessor, instance.defaultFilter, data, request, response);

            responseData = response.calls.mostRecent().args[0];

            expect(responseData.length).toEqual(2);
            expect(JSON.stringify(responseData[0])).toEqual('{"label":"two","value":"two","actual":"two","data":"two"}');
            expect(JSON.stringify(responseData[1])).toEqual('{"label":"three","value":"three","actual":"three","data":"three"}');
        });

        it("should apply the default filter in a case-insensitve manner to the results", function() {
            var responseData,
                response = jasmine.createSpy(),
                valueAccessor = function() {
                    return {};
                },
                data = [
                    "one",
                    "two",
                    "three"
                ],
                request = { term: "T" };

            instance.processOptions(valueAccessor, instance.defaultFilter, data, request, response);

            responseData = response.calls.mostRecent().args[0];

            expect(responseData.length).toEqual(2);
            expect(JSON.stringify(responseData[0])).toEqual('{"label":"two","value":"two","actual":"two","data":"two"}');
            expect(JSON.stringify(responseData[1])).toEqual('{"label":"three","value":"three","actual":"three","data":"three"}');
        });

        it("should not error when data is empty", function() {
            var responseData,
                response = jasmine.createSpy(),
                valueAccessor = function() {
                    return {};
                },
                data = null,
                request = { term: "t" };

            instance.processOptions(valueAccessor, instance.defaultFilter, data, request, response);

            responseData = response.calls.mostRecent().args[0];

            expect(responseData.length).toEqual(0);
        });

        it("should use the appropriate values for the mapped properties", function() {
            var responseData,
                response = jasmine.createSpy(),
                valueAccessor = function() {
                    return {
                        inputProp: "one",
                        labelProp: "two",
                        valueProp: "three"
                    };
                },
                data = [
                    {
                        one: "1-one",
                        two: "1-two",
                        three: "1-three"
                    },
                    {
                        one: "2-one",
                        two: "2-two",
                        three: "2-three"
                    },
                    {
                        one: "3-one",
                        two: "3-two",
                        three: "3-three"
                    }
                ],
                request = {};

            instance.processOptions(valueAccessor, null, data, request, response);

            responseData = response.calls.mostRecent().args[0];

            expect(responseData.length).toEqual(3);
            expect(responseData[0].label).toEqual("1-two");
            expect(responseData[0].value).toEqual("1-one");
            expect(responseData[0].actual).toEqual("1-three");

            expect(responseData[1].label).toEqual("2-two");
            expect(responseData[1].value).toEqual("2-one");
            expect(responseData[1].actual).toEqual("2-three");

            expect(responseData[2].label).toEqual("3-two");
            expect(responseData[2].value).toEqual("3-one");
            expect(responseData[2].actual).toEqual("3-three");
        });
    });

    describe("renderItem", function() {
        it("should use appropriate template", function() {
            var context, $ul;

            engine.addTemplate("test", "test");

            //get a context
            ko.applyBindings({ name: "test" }, input);
            context = ko.contextFor(input);

            $ul = $("
").appendTo($input);

            instance.renderItem("test", context, $ul, {});

            expect($ul.html()).toEqual("
test
");
        });

        it("should use the appropriate context for binding", function() {
            var context, $ul;

            engine.addTemplate("test", "");

            //get a context
            ko.applyBindings({ name: "test" }, input);
            context = ko.contextFor(input);

            $ul = $("
").appendTo($input);

            instance.renderItem("test", context, $ul, {
                data: {
                    id: 1
                }
            });

            expect($ul.html()).toEqual("
1
");
        });

        it("should call cleanNode when a templated li is removed", function() {
            var context, $ul, li;

            engine.addTemplate("test", "");

            //get a context
            ko.applyBindings({ name: "test" }, input);
            context = ko.contextFor(input);

            $ul = $("
").appendTo($input);

            instance.renderItem("test", context, $ul, {
                data: {
                    id: 1
                }
            });

            li = $ul.children()[0];

            ko.utils.domData.set(li, "mykey", "myvalue");

            //trigger remove event which should trigger cleanNode which will clear the data
            $(li).trigger("remove");

            expect(ko.utils.domData.get(li, "mykey")).toBeUndefined();
        });

        it("should return the newly createad li", function() {
            var context, $ul, li;

            engine.addTemplate("test", "");

            //get a context
            ko.applyBindings({ name: "test" }, input);
            context = ko.contextFor(input);

            $ul = $("
").appendTo($input);

            li = instance.renderItem("test", context, $ul, {
                data: {
                    id: 1
                }
            });

            expect(li[0]).toEqual($ul.children()[0]);
        });
    });

    describe("getPropertyNames", function() {
        it("should return the correct names when all options are supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    inputProp: "one",
                    labelProp: "two",
                    valueProp: "three"
                };
            });

            expect(results.input).toEqual("one");
            expect(results.label).toEqual("two");
            expect(results.value).toEqual("three");
        });

        it("should return the correct names when inputProp and valueProp are supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    inputProp: "one",
                    valueProp: "three"
                };
            });

            expect(results.input).toEqual("one");
            expect(results.label).toEqual("three");
            expect(results.value).toEqual("three");
        });

        it("should return the correct names when labelProp and valueProp are supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    labelProp: "two",
                    valueProp: "three"
                };
            });

            expect(results.input).toEqual("two");
            expect(results.label).toEqual("two");
            expect(results.value).toEqual("three");
        });

        it("should return the correct names when labelProp and inputProp are supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    inputProp: "one",
                    labelProp: "two"
                };
            });

            expect(results.input).toEqual("one");
            expect(results.label).toEqual("two");
            expect(results.value).toBeUndefined();
        });

        it("should return the correct names when only valueProp is supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    valueProp: "three"
                };
            });

            expect(results.input).toEqual("three");
            expect(results.label).toEqual("three");
            expect(results.value).toEqual("three");
        });

        it("should return the correct names when only labelProp is supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    labelProp: "two"
                };
            });

            expect(results.input).toEqual("two");
            expect(results.label).toEqual("two");
            expect(results.value).toBeUndefined();
        });

        it("should return the correct names when only inputProp is supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {
                    inputProp: "one"
                };
            });

            expect(results.input).toEqual("one");
            expect(results.label).toBeUndefined();
            expect(results.value).toBeUndefined();
        });

        it("does not error when no properties are supplied", function() {
            var results = instance.getPropertyNames(function() {
                return {};
            });

            expect(results.input).toBeUndefined();
            expect(results.label).toBeUndefined();
            expect(results.value).toBeUndefined();
        });
    });

    describe("update function", function() {
        it("should set the input's value", function() {
            instance.update(input, function() {
                return {
                    value: ko.observable("test")
                };
            });

            expect(input.value).toEqual("test");
        });

        it("should clear the input's value, when value is null", function() {
            instance.update(input, function() {
                return {
                    value: ko.observable(null)
                };
            });

            expect(input.value).toEqual("");
        });

        it("should clear the input's value, when value is undefined", function() {
            instance.update(input, function() {
                return {
                    value: ko.observable()
                };
            });

            expect(input.value).toEqual("");
        });

        it("should clear the input's value, when value is an empty string", function() {
            instance.update(input, function() {
                return {
                    value: ko.observable()
                };
            });

            expect(input.value).toEqual("");
        });

        it("should set the input's value to labelProp based on valueProp", function () {
            var items = [{ id: "1", name: "One" }],
                value = ko.observable("1");

            instance.update(input, function() {
                return {
                    value: value,
                    source: items,
                    labelProp: "name",
                    valueProp: "id"
                };
            });

            expect(input.value).toEqual("One");
        });

        it("should set the input's value to inputProp based on valueProp", function () {
            var items = [{ id: "1", name: "One" }],
                value = ko.observable("1");

            instance.update(input, function () {
                return {
                    value: value,
                    source: items,
                    inputProp: "name",
                    valueProp: "id"
                };
            });

            expect(input.value).toEqual("One");
        });

        it("should set the input's value to valueProp based on valueProp", function () {
            var items = [{ id: "1", name: "One" }],
                value = ko.observable("1");

            instance.update(input, function () {
                return {
                    value: value,
                    source: items,
                    valueProp: "id"
                };
            });

            expect(input.value).toEqual("1");
        });
    });

    describe("using the binding", function() {
        it("should allow binding against strings", function() {
            var $listItems,
                items = ["one", "two", "three"],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            expect($listItems.length).toEqual(2);
            expect($listItems.first().text()).toEqual("two");
            expect($listItems.last().text()).toEqual("three");
        });

        it("should respect the labelProp option", function() {
            var $listItems,
                items = [
                    {
                        name: "one",
                        description: "one description"
                    },
                    {
                        name: "two",
                        description: "two description"
                    },
                    {
                        name: "three",
                        description: "three description"
                    }
                ],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    labelProp: "description",
                    source: items
                }
            });

            $input.autocomplete("search", "one");

            $listItems = $("ul.ui-autocomplete li");

            expect($listItems.length).toEqual(1);
            expect($listItems.first().text()).toEqual("one description");
        });

        it("should allow using a template", function() {
            var $listItems,
                items = ["one", "two", "three"],
                value = ko.observable();

            engine.addTemplate("test", "!!");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    template: "test"
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            expect($listItems.length).toEqual(2);
            expect($listItems.first().text()).toEqual("!two!");
            expect($listItems.last().text()).toEqual("!three!");
        });

        it("context bound to template should be data item", function() {
            var $listItems,
                items = [
                    { name: "one" },
                    { name: "two" },
                    { name: "three" }
                ],
                value = ko.observable();

            engine.addTemplate("test", "!!");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    template: "test",
                    valueProp: "name"
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            expect($listItems.length).toEqual(2);
            expect($listItems.first().text()).toEqual("!two!");
            expect($listItems.last().text()).toEqual("!three!");
        });

        it("should respect a custom filter", function() {
            var $listItems,
                filterCallCount = 0,
                items = ["one", "two", "three"],
                value = ko.observable(),
                customFilter = function(item, term) {
                    filterCallCount++;
                    return term === "t"
                };

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    filter: customFilter
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            expect(filterCallCount).toEqual(3);
            expect($listItems.length).toEqual(3);
        });

        it("should allow using a function to retrieve the data", function() {
            var $listItems, calledTerm,
                items = ["one", "two", "three"],
                value = ko.observable(),
                getItems = function(term, response) {
                    calledTerm = term;

                    response(items);
                };

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: getItems
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            expect(calledTerm).toEqual("t");
            expect($listItems.length).toEqual(3);
        });

        it("should populate the value on selection", function() {
            var $listItems,
                items = ["one", "two", "three"],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            $listItems.first("a").click();

            expect(value()).toEqual("two");
        });

        it("should populate the value when not selecting from list", function() {
            var items = ["one", "two", "three"],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items
                }
            });

            $input.val("test").blur();
            $input.trigger("autocompletechange");

            expect(value()).toEqual("test");
        });

        it("should clear the dataValue when not selecting from list", function() {
            var items = ["one", "two", "three"],
                value = ko.observable(),
                dataValue = ko.observable("test");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    dataValue: dataValue,
                    source: items
                }
            });

            $input.val("test").blur();
            $input.trigger("autocompletechange");

            expect(value()).toEqual("test");
            expect(dataValue()).toEqual(null);
        });

        it("should respect the valueProp option on selection", function() {
            var $listItems,
                items = [
                    {
                        name: "one",
                        description: "one description"
                    },
                    {
                        name: "two",
                        description: "two description"
                    },
                    {
                        name: "three",
                        description: "three description"
                    }
                ],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    valueProp: "name"
                }
            });

            $input.autocomplete("search", "one");

            $listItems = $("ul.ui-autocomplete li");

            $listItems.first("a").click();

            expect(value()).toEqual("one");
        });

        it("should respect the inputProp option on selection", function() {
            var $listItems,
                items = [
                    {
                        name: "one",
                        description: "one description"
                    },
                    {
                        name: "two",
                        description: "two description"
                    },
                    {
                        name: "three",
                        description: "three description"
                    }
                ],
                value = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    inputProp: "description"
                }
            });

            $input.autocomplete("search", "one");

            $listItems = $("ul.ui-autocomplete li");

            $listItems.first("a").click();

            expect($input.text()).toEqual("one description");
        });

        it("should respect the dataValue option on selection", function() {
            var $listItems,
                items = [
                    {
                        name: "one",
                        description: "one description"
                    },
                    {
                        name: "two",
                        description: "two description"
                    },
                    {
                        name: "three",
                        description: "three description"
                    }
                ],
                value = ko.observable(),
                dataValue = ko.observable();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    dataValue: dataValue,
                    source: items,
                    valueProp: "name"
                }
            });

            $input.autocomplete("search", "one");

            $listItems = $("ul.ui-autocomplete li");

            $listItems.first("a").click();

            expect(value()).toEqual("one");
            expect(dataValue()).toEqual(items[0]);
        });

        it("should initially set the input's value", function() {
            var items = [],
                value = ko.observable("testing");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items
                }
            });

            expect(input.value).toEqual("testing");
        });

        it("should initially set the input's value to inputProp based on valueProp", function () {
            var items = [{id:"1", name: "One"}],
                value = ko.observable("1");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    inputProp: "name",
                    valueProp: "id"
                }
            });

            expect(input.value).toEqual("One");
        });

        it("should initially set the input's value to labelProp based on valueProp", function () {
            var items = [{ id: "1", name: "One" }],
                value = ko.observable("1");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    labelProp: "name",
                    valueProp: "id"
                }
            });

            expect(input.value).toEqual("One");
        });

        it("should initially set the input's value to valueProp based on valueProp", function () {
            var items = [{ id: "1", name: "One" }],
                value = ko.observable("1");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    valueProp: "id"
                }
            });

            expect(input.value).toEqual("1");
        });

        it("should update the input's value when the observable changes", function() {
            var items = [],
                value = ko.observable("testing");

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items
                }
            });

            expect(input.value).toEqual("testing");

            value("updated");

            expect(input.value).toEqual("updated");
        });

        it("should call a passed in \"select\" function", function() {
            var $listItems,
                items = ["one", "two", "three"],
                value = ko.observable(),
                handler = jasmine.createSpy();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    options: {
                        select: handler
                    }
                }
            });

            $input.autocomplete("search", "t");

            $listItems = $("ul.ui-autocomplete li");

            $listItems.first("a").click();

            expect(handler.calls.count()).toEqual(1);
            expect(handler.calls.argsFor(0)[0].type).toEqual("autocompleteselect");
            expect("item" in handler.calls.argsFor(0)[1]).toBeTruthy();
        });

        it("should call a passed in \"change\" function", function() {
            var items = [],
                value = ko.observable("testing"),
                handler = jasmine.createSpy();

            ko.applyBindingsToNode(input, {
                jqAuto: {
                    value: value,
                    source: items,
                    options: {
                        change: handler
                    }
                }
            });

            $input.val("test").blur();

            expect(handler.calls.count()).toEqual(1);
            expect(handler.calls.argsFor(0)[0].type).toEqual("autocompletechange");
            expect("item" in handler.calls.argsFor(0)[1]).toBeTruthy();
        });
    });
});


"use strict";
var parseOptions = require("./parseOptions");
var ReactDOM     = require("./ReactDOM");

var HandlebarsHtmlParser = require("handlebars-html-parser");
var postcss = require("postcss");
var postcssJs = require("postcss-js");

var eachNode = HandlebarsHtmlParser.each;
var NodeType = HandlebarsHtmlParser.type;

function compiler(options)
{
	this.options = options = parseOptions(options);

	this.parser = new HandlebarsHtmlParser(
	{
		normalizeWhitespace: options.normalizeWhitespace,
		processCSS:          options.processCSS,
		processJS:           options.processJS
	});
}

compiler.prototype.compile = function(str)
{
	var parserState;

	var compilerState =
	{
		// React.DOM… or React.createElement per element in stack
		// Stack indexed by parent tag depth -- first index is a "document" node (root/top-level nodes container)
		areDomMethods: [false]
	};

	var result = [];

	return this.parser.parse(str)
	.then( eachNode((node, state) =>
	{
		// Parent scope access
		parserState = state;

		switch (node.type)
		{
			case NodeType.HBS_EXPRESSION_END:
			{
				break;
			}
			case NodeType.HBS_EXPRESSION_START:
			{
				break;
			}

			case NodeType.HBS_HASH_END:
			{
				break;
			}
			case NodeType.HBS_HASH_START:
			{
				break;
			}

			case NodeType.HBS_HASH_KEY_END:
			{
				break;
			}
			case NodeType.HBS_HASH_KEY_START:
			{
				break;
			}

			case NodeType.HBS_HASH_VALUE_END:
			{
				break;
			}
			case NodeType.HBS_HASH_VALUE_START:
			{
				break;
			}

			case NodeType.HBS_PART_END:
			{
				break;
			}
			case NodeType.HBS_PART_START:
			{
				break;
			}

			case NodeType.HBS_PATH:
			{
				break;
			}

			case NodeType.HBS_TAG_END:
			{
				break;
			}
			case NodeType.HBS_TAG_START:
			{
				/*if (state.isTag === false)
				{
					if (node.closing !== true)
					{
						incrementTop(state.hbsCounts);
					}
				}*/

				break;
			}

			case NodeType.HTML_ATTR_END:
			{
				break;
			}
			case NodeType.HTML_ATTR_START:
			{
				if (numAttributes(parserState) <= 1)
				{
					if (isDomMethod(compilerState) === false)
					{
						// React.createElement("tag",
						result.push(",");
					}

					// React.createElement("tag", {
					// React.DOM.tag({
					result.push("{");
				}
				else
				{
					// React.createElement("tag", {attr:"value",
					// React.DOM.tag({attr:value,
					result.push(",");
				}

				break;
			}

			case NodeType.HTML_ATTR_NAME_END:
			{
				break;
			}
			case NodeType.HTML_ATTR_NAME_START:
			{
				break;
			}

			case NodeType.HTML_ATTR_VALUE_END:
			{
				break;
			}
			case NodeType.HTML_ATTR_VALUE_START:
			{
				result.push(":");
				break;
			}

			case NodeType.HTML_COMMENT_END:
			{
				break;
			}
			case NodeType.HTML_COMMENT_START:
			{
				break;
			}

			// …>
			case NodeType.HTML_TAG_END:
			{
				if (parserState.isClosingTag === true)
				{
					if (numAttributes(parserState)>0 && numChildren(parserState)<=0)
					{
						result.push("}");
					}

					result.push(")");

					compilerState.areDomMethods.pop();
				}

				break;
			}
			// <…
			case NodeType.HTML_TAG_START:
			{
				if (parserState.isClosingTag === false)
				{
					compilerState.areDomMethods.push(false);

					beforeChild(parserState, compilerState, result, true);

					result.push("React.createElement(");
				}

				break;
			}

			case NodeType.HTML_TAG_NAME_END:
			{
				break;
			}
			case NodeType.HTML_TAG_NAME_START:
			{
				break;
			}

			case NodeType.LITERAL:
			{
				if (parserState.isTag === true)
				{
					if (parserState.isTagName === true)
					{
						if (parserState.isClosingTag === false)
						{
							if (this.options.useDomMethods === true)
							{
								// If tag name has a `React.DOM` function
								if (ReactDOM[node.value] === true)
								{
									// Change stack's top value
									compilerState.areDomMethods[compilerState.areDomMethods.length-1] = true;

									// Change last/previous result index
									result[result.length-1] = "React.DOM." + node.value + "(";

									// Done -- no more code in this `case` will run
									break;
								}
							}

							// React.createElement("tag"
							result.push('"'+ node.value +'"');
						}
						// Else: closing tag name excluded from result
					}
					else if (parserState.isAttribute === true)
					{
						if (parserState.isAttributeName === true)
						{
							// React.createElement("tag", {"attr"
							// React.DOM.tag({"attr"
							result.push( transformAttributeName(node.value) );
						}
						else if (parserState.isAttributeValue === true)
						{
							// TODO :: support `href="javscript:code()"`
							/*if (parserState.isEventAttribute === true)
							{
								// React.createElement("tag", {"onsomething":"code()"
								// React.DOM.tag({"onsomething":"code()"
								result.push( transformScript(node.value, this.options) );
							}
							else*/ if (parserState.isStyleAttribute === true)
							{
								// React.createElement("tag", {"style":{…}
								// React.DOM.tag({"style":{…}
								result.push( transformInlineStyles(node.value, this.options) );
							}
							else
							{
								// React.createElement("tag", {"attr":"value"
								// React.DOM.tag({"attr":"value"
								result.push( safeString(node.value) );
							}
						}
					}
				}
				else
				{
					beforeChild(parserState, compilerState, result);

					if (parserState.isWithinScriptTag === true)
					{
						// React.createElement("script", …, "script()"
						// TODO :: only do so if mimetype is "text/javascript", "" or undefined
						result.push( transformScript(node.value, this.options) );
					}
					else if (parserState.isWithinStyleTag === true)
					{
						// React.createElement("style", …, "style:sheet"
						// TODO :: only do so if mimetype is "text/css", "" or undefined
						result.push( transformStylesheet(node.value, this.options) );
					}
					else
					{
						//if (typeof node.value==="string" || node.value instanceof String===true)
						//{
							// React.createElement("tag", …, "text"
							// React.DOM.tag(…, "text"
							result.push( safeString(node.value) );
						//}
						//else
						//{
							// Support for null, undefined, numbers
						//	result.push(node.value);
						//}
					}
				}

				break;
			}

			default:
			{
				// oops?
			}
		}
	}))
	.then(program =>
	{
		// If more than one top-level node
		if (numChildren(parserState) > 1)
		{
			if (this.options.multipleTopLevelNodes === true)
			{
				// Contain comma-separated list in an Array
				result.unshift("[");
				result.push("]");
			}
			else
			{
				throw new Error(numChildren(parserState) + " top-level nodes detected. Only 1 is currently supported by React.");
			}
		}

		//console.log(str);
		//console.log(program);
		//console.log(result);
		result = finalize(result, this.options);
		//console.log(result);

		return result;
	});
};

//::: PRIVATE FUNCTIONS

function beforeChild(parserState, compilerState, result, checkParent)
{
	var _isDomMethod   = checkParent!==true ? isDomMethod(compilerState) : isParentDomMethod(compilerState);
	var _numAttributes = checkParent!==true ? numAttributes(parserState) : numParentAttributes(parserState);
	var _numChildren   = checkParent!==true ? numChildren(parserState)   : numParentChildren(parserState);

	var _isTopLevelChild = checkParent!==true ? parserState.childCounts.length>1 : parserState.childCounts.length>2;

	if (_isTopLevelChild === true)
	{
		if (_numAttributes <= 0)
		{
			if (_numChildren <= 1)
			{
				if (_isDomMethod !== true)
				{
					// React.createElement("tag",
					result.push(",");
				}

				// React.createElement("tag", null,
				// React.DOM.tag(null,
				result.push("null");
				result.push(",");
			}
			else
			{
				// React.createElement("tag", {"attr":"value"}, sibling,
				// React.DOM.tag({"attr":"value"}, sibling,
				result.push(",");
			}
		}
		else
		{
			// React.createElement("tag", {"attr":"value"},
			// React.DOM.tag({"attr":"value"},
			result.push("}");
			result.push(",");
		}
	}
	// If top-level node with siblings
	else if (_numChildren > 1)
	{
		// React.createElement(…),
		// React.DOM.tag(…),
		// "text",
		result.push(",");
	}
}

function finalize(result, options)
{
	var js = options.prefix + result.join("") + options.suffix;

	// Check that the compiled code is valid
	try
	{
		Function("", js);
	}
	catch (error)
	{
		console.log(js);
		throw error;
	}

	if (options.beautify === true)
	{
		js = HandlebarsHtmlParser.beautifyJS(js);
	}

	return js;
}

function getLast(stack)
{
	return stack[stack.length - 1];
}

function getSecondLast(stack)
{
	return stack[stack.length - 2];
}

function isDomMethod(compilerState)
{
	return getLast(compilerState.areDomMethods);
}

function isParentDomMethod(compilerState)
{
	var result = getSecondLast(compilerState.areDomMethods);

	if (result === undefined) result = -1;

	return result;
}

function numAttributes(parserState)
{
	return getLast(parserState.attrCounts);
}

function numChildren(parserState)
{
	return getLast(parserState.childCounts);
}

function numParentAttributes(parserState)
{
	var result = getSecondLast(parserState.attrCounts);

	if (result === undefined) result = -1;

	return result;
}

function numParentChildren(parserState)
{
	var result = getSecondLast(parserState.childCounts);

	if (result === undefined) result = -1;

	return result;
}

function safeString(string)
{
	// Converts whitespace, unicode chars, adds/escapes quotes, etc
	return JSON.stringify(string);
}

function transformAttributeName(attrName)
{
	// TODO :: is this necessary?
	// TODO :: find a lib for this as there're more?
	switch (attrName)
	{
		case "class":
		{
			attrName = "className";
			break;
		}
		case "for":
		{
			attrName = "htmlFor";
			break;
		}
		default:
		{
			// TODO :: camel-case it?
		}
	}

	return '"'+ attrName +'"';
}

function transformInlineStyles(styles, options)
{
	return JSON.stringify( postcssJs.objectify( postcss.parse(styles) ) );
}

function transformScript(script, options)
{
	return safeString(script);
}

function transformStylesheet(stylesheet, options)
{
	return safeString(stylesheet);
}

module.exports = compiler;


var events = require('events');
var util = require('util');

module.exports = Job;

/**
* Job retry specification
* @typedef {Object} Job~Attempts
* @property {string} strategy - Name of {@link Worker~strategyCallback} to use on retry
* @property {number} count - total number of attempts so far
* @property {number} delay - a delay constant for use in determining a delay.  In default linear strategy, this will be the delay between attempts
*/

/**
* @constructor
* @param {string} collection - The collection to save the job to
* @param {Object} data - The Job data
*/
function Job(collection, data) {
    this.collection = collection;

    if (data) {
        // Convert plain object to JobData type
        data.__proto__ = JobData.prototype;
        this.data = data;
    } else {
        this.data = new JobData();
    }
}

util.inherits(Job, events.EventEmitter);

Job.QUEUED = 'queued';
Job.DEQUEUED = 'dequeued';
Job.COMPLETE = 'complete';
Job.FAILED = 'failed';
Job.CANCELLED = 'cancelled';

Job.prototype.save = function (callback) {
    var self = this;

    this.collection.save(this.data, function (err, doc) {
        if (err) return callback(err);

        if (doc && self.data._id === undefined) {
            self.data._id = doc._id;
        }

        callback && callback(null, self);
    });
};

Job.prototype.cancel = function (callback) {
    if (this.data.status !== Job.QUEUED) {
        return callback(new Error('Only queued jobs may be cancelled'));
    }

    this.data.status = Job.CANCELLED;
    this.data.ended = new Date();

    this.save(callback);
};

Job.prototype.complete = function (result, callback) {
    this.data.status = Job.COMPLETE;
    this.data.ended = new Date();
    this.data.result = result;

    this.save(callback);
};

Job.prototype.fail = function (err, callback) {
    this.data.status = Job.FAILED;
    this.data.ended = new Date();
    this.data.error = err.message;
    this.data.stack = err.stack;

    this.save(callback);
};

Job.prototype.enqueue = function (callback) {
    if (this.data.delay === undefined) {
        this.data.delay = new Date();
    }

    if (this.data.priority === undefined) {
        this.data.priority = 0;
    }

    this.data.status = Job.QUEUED;
    this.data.enqueued = new Date();

    this.save(callback);
};

Job.prototype.delay = function (delay, callback) {
    this.data.delay = new Date(new Date().getTime() + delay);

    this.enqueue(callback);
};

function JobData() {}

Object.defineProperty(JobData.prototype, 'id', {
    get: function () {
        return this._id && this._id.toString && this._id.toString();
    }
});


var events = require('events');
var util = require('util');
var Queue = require('./queue');

module.exports = Worker;

/**
* Options for a new worker
* @typedef {Object} Worker~Options
* @property {Number} interval - the polling interval for the worker. Note: The worker will process jobs, one at a time, as fast as possible while queues have waiting jobs
* @property {Worker~Strategies} strategies - {@link Worker~Strategies} for retrying jobs
* @property {Worker~Callbacks} callbacks - Map of {@link Worker~Callback} for processing jobs
* @property {Number} minPriority - The lowest job priority the worker will process
*/

/**
* @constructor
* @param {string[]} queues - an array of queue names that this worker will listen for
* @param {Worker~Options} options - {@link Worker~Options} Options object
*/
function Worker(queues, options) {
    options || (options = {});

    this.empty = 0;
    this.queues = queues || [];
    this.interval = options.interval || 5000;

    this.callbacks = options.callbacks || {};
    this.strategies = options.strategies || {};
    this.universal = options.universal || false;

    // Default retry strategies
    this.strategies.linear || (this.strategies.linear = linear);
    this.strategies.exponential || (this.strategies.exponential = exponential);

    // This worker will only process jobs of this priority or higher
    this.minPriority = options.minPriority;

}

util.inherits(Worker, events.EventEmitter);

/**
* Job handler functions should take this form
* @callback Worker~Callback
* @param {Job.params} params - {@link Job.params} object for the job to be processed
* @param {Function} callback - NodeJS style callback to be invoked when job processing is finished
*/

/**
* Sets handlers to be invoked for each queue that the worker is listening to
* @param {Object} callbacks - map of {@link Worker~Callback} objects. Keys are the name of the queue, values are the handlers for those queues
*/
Worker.prototype.register = function (callbacks) {
    for (var name in callbacks) {
        this.callbacks[name] = callbacks[name];
    }
};

/**
* A map of {@link Worker~StrategyCallback}s
* @typedef {Object} Worker~Strategies
*/

/**
* @callback Worker~StrategyCallback
* @param {Job~Attempts} attempts - {@link Job~Attempts} object
* @returns {Number} delay time
*/

Worker.prototype.strategies = function (strategies) {
    for (var name in strategies) {
        this.strategies[name] = strategies[name];
    }
};

/**
* Starts the worker.  If no queues have been specified yet, this will loop
*/
Worker.prototype.start = function () {
    if (this.queues.length === 0) {
        return setTimeout(this.start.bind(this), this.interval);
    }
    this.working = true;
    this.poll();
};

/**
* Stops the worker
*/
Worker.prototype.stop = function (callback) {
    var self = this;

    function done() {
        if (callback) callback();
    }

    if (!this.working) done();
    this.working = false;

    if (this.pollTimeout) {
      clearTimeout(this.pollTimeout);
      this.pollTimeout = null;
      return done();
    }

    this.once('stopped', done);
};

/**
* Adds a queue for the worker to listen on
* @param {string} queue - the name of the queue to add
*/
Worker.prototype.addQueue = function (queue) {
  if (!this.universal)
    this.queues.push(queue);
};

Worker.prototype.poll = function () {
    if (!this.working) {
        return this.emit('stopped');
    }

    var self = this;

    this.dequeue(function (err, job) {
        if (err) return self.emit('error', err);

        if (job) {
            self.empty = 0;
            self.emit('dequeued', job.data);
            self.work(job);
        } else {
            self.emit('empty');

            if (self.empty < self.queues.length) {
                self.empty++;
            }

            if (self.empty === self.queues.length) {
                // All queues are empty, wait a bit
                self.pollTimeout = setTimeout(function () {
                    self.pollTimeout = null;
                    self.poll();
                }, self.interval);
            } else {
                self.poll();
            }
        }
    });
};

Worker.prototype.dequeue = function (callback) {
    var queue = this.queues.shift();
    this.queues.push(queue);
    queue.dequeue({ minPriority: this.minPriority, callbacks: this.callbacks }, callback);
};

Worker.prototype.work = function (job) {
    var self = this;
    var finished = false;

    if (job.data.timeout) {
        var timer = setTimeout(function () {
            done(new Error('timeout'));
        }, job.data.timeout);
    }

    function done(err, result) {
        // It's possible that this could be called twice in the case that a job times out,
        // but the handler ends up finishing later on
        if (finished) {
            return;
        } else {
            finished = true;
        }

        clearTimeout(timer);
        self.emit('done', job.data);

        if (err) {
            self.error(job, err, function (err) {
                if (err) return self.emit('error', err);

                self.emit('failed', job.data);
                self.poll();
            });
        } else {
            job.complete(result, function (err) {
                if (err) return self.emit('error', err);

                self.emit('complete', job.data);
                self.poll();
            });
        }
    };

    this.process(job.data, done);
};

Worker.prototype.process = function (data, callback) {
    var func = this.callbacks[data.name];

    if (!func) {
        callback(new Error('No callback registered for `' + data.name + '`'));
    } else {
        func(data.params, callback);
    }
};

Worker.prototype.error = function (job, err, callback) {
    var attempts = job.data.attempts;
    var remaining = 0;

    if (attempts) {
        remaining = attempts.remaining = (attempts.remaining || attempts.count) - 1;
    }

    if (remaining > 0) {
        var strategy = this.strategies[attempts.strategy || 'linear'];
        if (!strategy) {
            strategy = linear;

            console.error('No such retry strategy: `' + attempts.strategy + '`');
            console.error('Using linear strategy');
        }

        if (attempts.delay !== undefined) {
            var wait = strategy(attempts);
        } else {
            var wait = 0;
        }

        job.delay(wait, callback)
    } else {
        job.fail(err, callback);
    }
};

// Strategies
// ---------------

function linear(attempts) {
    return attempts.delay;
}

function exponential(attempts) {
    return attempts.delay * (attempts.count - attempts.remaining);
}


/**
 * Module dependencies
 */

var util = require('util');
var EE3 = require('eventemitter3').EventEmitter;

var UploadError = require('./errors/upload-error');

module.exports = Uploader;

// uploader
/**
 *
 * @param options
 * @param options.el required. Can be a DOM reference or a CSS selector. Supported tags
 *        are input (classic file input) or a div (for drag & drop)
 * @param options.name Name of the FormData param used for upload. Default: file
 * @param options.url URL of your server-side upload API endpoint. If omitted, calling upload()
 *        will throw or emit and error
 * @param options.method Request method used during upload. Default: POST
 * @param options.headers object of headers to be set on the XHR object before sending
 * @constructor
 */
function Uploader(options) {

  if (typeof options !== 'object') {
    throw new TypeError('options must be an object');
  }

  this.$el = this._getElement(options.el);
  this._elType = this.$el.tagName.toLowerCase();

  //

  if (options.name && typeof options.name !== 'string') {
    throw new TypeError('options `name` must be a string');
  }

  if (options.url && typeof options.url !== 'string') {
    throw new TypeError('options `url` must be a string');
  }

  if (options.method && typeof options.method !== 'string') {
    throw new TypeError('options `method` must be a string');
  }

  if (options.headers && typeof options.headers !== 'object') {
    throw new TypeError('options `headers` must be an object');
  }

  //

  this.files = [];
  this.method = options.method || 'POST';
  this.name = options.name || 'file';
  this.url = options.url || null;
  this.headers = options.headers || {};

  // EE3

  EE3.call(this);

  // error listener

  this.on('error', this._onError.bind(this));

  // check support

  this._checkSupport();

  // attach to $el

  this._attach();

}
util.inherits(Uploader, EE3);

Uploader.prototype._checkSupport = function() {

  if (typeof FileReader == 'undefined') {
    return this.emit('error', new Error('FileReader API not available'));
  }

  if (!window.FormData) {
    return this.emit('error', new Error('FormData API not available'));
  }

  this.support = {
    progress: 'upload' in new XMLHttpRequest(),
    draggable: 'draggable' in document.createElement('span')
  };

};

Uploader.prototype._onError = function(err) {
  if (this.listeners('error').length === 1) throw err;
};

Uploader.prototype._getElement = function(el) {
  var $el = null;

  if (typeof el === 'object') {
    if (el.nodeName) {
      $el = el;
    } else {
      throw new TypeError('option `el` is not a DOM element');
    }
  }
  else if (typeof el === 'string') {
    $el = document.querySelector(el);
    if (!$el) throw new Error('options `el` must be an existent DOM element reference');
  }
  else throw new TypeError('option `el` is invalid');

  return $el;
};

Uploader.prototype._attach = function() {
  var self = this;

  // generic drag event handler

  function handleDragEvents(e) {
    e.stopPropagation();
    e.preventDefault();
    return this.emit(e.type, e);
  }

  // check element type and attach

  if (this._elType === 'input') {

    // input

    if (this.$el.type !== 'file') {
      return this.emit('error',
        new Error('you should attach Uploader to an <input type="file"> tag'));
    }

    this.$el.onclick = function() {
      this.value = '';
    };

    this.$el.onchange = function() {
      self._read(this.files);
    };

  } else {

    if (this.support.draggable) {

      // div (drag/drop)

      this.$el.ondragover = handleDragEvents.bind(this);
      this.$el.ondragleave = handleDragEvents.bind(this);
      this.$el.ondragstart = handleDragEvents.bind(this);
      this.$el.ondragend = handleDragEvents.bind(this);

      this.$el.ondrop = function(e) {
        e.stopPropagation();
        e.preventDefault();

        self.emit('drop', e);
        self._read(e.dataTransfer.files);
      };

    } else {

      // If drag/drop is not supported, we can't read files.
      // We need an <input> tag

      this.emit('error', new Error('drag and drop events are not supported. \
        You should attach Uploader to an <input> tag'));
    }

  }
};

Uploader.prototype._read = function(files) {
  if (!files) files = this.$el.files || this.files;
  if (!files || files.length === 0) return this.emit('error', new Error('No files to upload'));

  // clear previously selected files

  if (this.files.length > 0) this.files = [];

  // read files and build FormData

  this.formData = new FormData();

  var i;

  for (i = 0; i < files.length; i++) {
    this.files.push(files[i]);
    this.formData.append(this.name, this.files[i]);
  }

  this.emit('files:added', this.files);

  // preview

  for (i = 0; i < files.length; i++) {
    this._preview(files[i]);
  }

};

Uploader.prototype._preview = function(file) {
  var self = this;
  var reader = new FileReader();

  if (file.type.indexOf('image/') === 0) {

    // image

    reader.onload = function(event) {
      var img = new Image();

      img.onload = function() {
        self.emit('file:preview', file, img);
      };

      img.src = event.target.result;
    };

    reader.readAsDataURL(file);

  } else {

    // non-image

    this.emit('file:preview', file);
  }
};

Uploader.prototype._upload = function() {
  var self = this;

  // checks

  if (this.files.length === 0) {
    return this.emit('error', new UploadError('No files to upload'));
  }

  if (!this.url) {
    return this.emit('error', new UploadError('No `url` option specified'));
  }

  // upload

  var xhr = new XMLHttpRequest();
  xhr.open(this.method.toUpperCase(), this.url);

  // set custom headers if specified
  for(var key in this.headers) {
    xhr.setRequestHeader(key, this.headers[key] || "");
  }

  xhr.onerror = function() {
    var message = this.statusText || 'upload failed';
    self.emit('error', new UploadError(message, this.status));
  };

  xhr.onload = function() {

    if (this.status === 200) {
      self.emit('upload:done', this.response);
    } else {
      self.emit('error', new UploadError(this.response, this.status));
    }

  };

  // progress

  if (this.support.progress) {

    xhr.upload.onprogress = function(event) {

      if (event.lengthComputable) {
        var progress = event.loaded / event.total * 100;
        self.emit('upload:progress', progress);
      }

    };

  }

  // upload previously populated formData

  xhr.send(this.formData);
};

Uploader.prototype.upload = function() {
  this._upload();
  return true;
};

Uploader.prototype.getFiles = function() {
  return this.files;
};

Uploader.prototype.clearFiles = function() {
  this.files = [];
  return this.emit('files:cleared');
};


/*
 * xlsx2json utilities
 * Copyright (c) 2014-2016 daikiueda, @ue_di
 * Licensed under the MIT license.
 * https://github.com/daikiueda/xlsx2json
 */

'use strict';

const memoize = require('memoizee');

/**
 * 順序を表す文字列（アルファベット）を、整数に変換して返却する。
 * ex. 'A' -> 1, 'AA' -> 27
 * @private
 * @param {String|Number} ordinalAlphabet 順序を表す文字列または数値。ex. 'A', 'AA' | 1, '2'
 * @returns {Number} 順序を表す整数
 */
const toOrdinalNumber = memoize(function toOrdinalNumber(ordinalAlphabet) {

    const intFromArg = parseInt(ordinalAlphabet, 10);

    if (!isNaN(intFromArg) && intFromArg > 0 && intFromArg == ordinalAlphabet) {
        return intFromArg;
    }

    if (/^[a-zA-Z]+$/.test(ordinalAlphabet)) {
        return ordinalAlphabet.toUpperCase().split('').reverse()
            .map(function(value, digit) {
                return (value.charCodeAt(0) - 'A'.charCodeAt(0) + 1) * Math.pow(26, digit);
            })
            .reduce(function(previousValue, currentValue) {
                return previousValue + currentValue;
            });
    }

    throw new TypeError();
});

/**
 * 整数を、順序を表すアルファベットに変換して返却する。
 * ex. 1 -> 'A', 27 -> 'AA'
 * @private
 * @param {Number|String} ordinalNumber 変換対象の整数。
 * @returns {String} 順序を表すアルファベット
 */
const toOrdinalAlphabet = memoize(function toOrdinalAlphabet(ordinalNumber) {

    if (/^[a-zA-Z]+$/.test(ordinalNumber)) {
        return ordinalNumber.toUpperCase();
    }

    if (parseInt(ordinalNumber, 10) != ordinalNumber || ordinalNumber <= 0) {
        throw new TypeError();
    }

    const charOfEachDigit = [];

    let digit = 1,
        testValue = ordinalNumber;
    do {
        const digitValue = testValue % Math.pow(26, digit++) || 26;
        charOfEachDigit.unshift(String.fromCharCode('A'.charCodeAt(0) + digitValue - 1));
        testValue = (testValue - digitValue) / 26;
    } while (testValue);

    return charOfEachDigit.join('');
});

/**
 * 順序を表す数値やアルファベットを操作するためのクラス
 */
class Ordinal {
    /**
     * @param {Number|String} ordinalData
     * @returns {Ordinal}
     */
    constructor(ordinalData) {
        if (!ordinalData || !/^([0-9]+|[a-zA-z]+)$/.test(ordinalData) || ordinalData <= 0) {
            throw new TypeError(ordinalData);
        }

        /**
         * @private
         * @type {Number|String}
         */
        this.ordinalData = ordinalData;
    }

    /** @returns {Number} */
    toNumber() {return toOrdinalNumber(this.ordinalData);}

    /** @returns {String} */
    toAlphabet() {return toOrdinalAlphabet(this.ordinalData);}
}

/**
 * recodeの内容を、Excel上のカラム表示（A, B, ...）にあわせてオブジェクトに格納し返却する。
 * @param {Array} recode カラム順にレコードの内容を格納した配列
 * @returns {Object}
 */
function formatRecordByColumnLabel(recode) {
    const recodeBuffer = {};
    recode.forEach(function(value, index) {
        recodeBuffer[new Ordinal(index + 1).toAlphabet()] = value;
    });
    return recodeBuffer;
}

/**
 * recordの内容を、mappingで対応づけられたKey名にあわせてオブジェクトに格納し返却する。
 * @param {Array} recode カラム順にレコードの内容を格納した配列
 * @param {Object} mapping key名とカラム位置の対応を示すオブジェクト（{key名: カラム位置, ...}）
 * @returns {Object}
 */
function formatRecordByMapping(recode, mapping) {
    return Object.keys(mapping).reduce((formatted, keyName) => {
        if (mapping[keyName] !== 0) {
            formatted[keyName] = recode[new Ordinal(mapping[keyName]).toNumber() - 1];
        }
        return formatted;
    }, {});
}

module.exports = {
    Ordinal: Ordinal,
    formatRecordByColumnLabel: formatRecordByColumnLabel,
    formatRecordByMapping: formatRecordByMapping
};


/* @license
 *
 * BLE Abstraction Tool: core functionality - classic specification
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Rob Moran
 *
 * 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.
 */

// https://github.com/umdjs/umd
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['bluetooth.helpers'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS
        module.exports = factory(require('./bluetooth.helpers'));
    } else {
        // Browser globals with support for web workers (root is window)
        root.bleat = factory(root.bleatHelpers);
    }
}(this, function(helpers) {
    "use strict";

    var adapter = null;
    var adapters = {};

    // Helpers
    function raiseError(errorFn, msg) {
        return function(error) {
            if (errorFn) errorFn(msg + ": " + error);
        };
    }

    function executeFn(fn) {
        return function() {
            if (typeof fn === "function") {
                var args = [].slice.call(arguments);
                fn.apply(this, args);
            }
        };
    }

    function AsyncWait(finishFn, errorFn) {
        var count = 0;
        var callbackAdded = false;
        this.addCallback = function(fn) {
            count++;
            callbackAdded = true;
            return function() {
                if (fn) fn.apply(null, arguments);
                if (--count === 0 && finishFn) finishFn();
            };
        };
        this.error = function() {
            if (errorFn) errorFn.apply(null, arguments);
            if (--count === 0 && finishFn) finishFn();
        };
        this.finish = function() {
            if (!callbackAdded && finishFn) finishFn();
        };
    }

    // Device Object
    var Device = function(deviceInfo) {
        this._handle = deviceInfo._handle;

        this.address = deviceInfo._handle;
        this.name = deviceInfo.name;
        this.serviceUUIDs = deviceInfo.uuids;
        this.adData = deviceInfo.adData;

        this.connected = false;
        this.services = {};
    };
    Device.prototype.hasService = function(serviceUUID) {
        return this.serviceUUIDs.some(function(uuid) {
            return (uuid === serviceUUID);
        });
    };
    Device.prototype.connect = function(connectFn, disconnectFn, errorFn, suppressDiscovery) {
        adapter.connect(this._handle, function() {
            this.connected = true;
            if (typeof errorFn === "boolean") {
                suppressDiscovery = errorFn;
                errorFn = null;
            }
            if (suppressDiscovery) return executeFn(connectFn)();
            this.discoverAll(connectFn, errorFn);
        }.bind(this), function() {
            this.connected = false;
            this.services = {};
            executeFn(disconnectFn)();
        }.bind(this), raiseError(errorFn, "connect error"));
    };
    Device.prototype.disconnect = function(errorFn) {
        adapter.disconnect(this._handle, raiseError(errorFn, "disconnect error"));
    };
    Device.prototype.discoverServices = function(serviceUUIDs, completeFn, errorFn) {
        if (this.connected === false) return raiseError(errorFn, "discovery error")("device not connected");
        if (typeof serviceUUIDs === "function") {
            completeFn = serviceUUIDs;
            serviceUUIDs = [];
        } else if (typeof serviceUUIDs === "string") {
            serviceUUIDs = [serviceUUIDs];
        }
        adapter.discoverServices(this._handle, serviceUUIDs, function(services) {
            services.forEach(function(serviceInfo) {
                this.services[serviceInfo.uuid] = new Service(serviceInfo);
            }, this);

            if (completeFn) completeFn();
        }.bind(this), raiseError(errorFn, "service discovery error"));
    };
    Device.prototype.discoverAll = function(completeFn, errorFn) {
        if (this.connected === false) return raiseError(errorFn, "discovery error")("device not connected");
        var wait = new AsyncWait(completeFn, errorFn);

        this.discoverServices(wait.addCallback(function() {
            Object.keys(this.services).forEach(function(serviceUUID) {
                var service = this.services[serviceUUID];

                service.discoverIncludedServices(wait.addCallback(), wait.error);

                service.discoverCharacteristics(wait.addCallback(function() {
                    Object.keys(service.characteristics).forEach(function(characteristicUUID) {
                        var characteristic = service.characteristics[characteristicUUID];
                        characteristic.discoverDescriptors(wait.addCallback(), wait.error);
                    }, this);
                }.bind(this)), wait.error);

            }, this);
        }.bind(this)), wait.error);

        wait.finish();
    };

    // Service Object
    var Service = function(serviceInfo) {
        this._handle = serviceInfo._handle;

        this.uuid = serviceInfo.uuid;
        this.primary = serviceInfo.primary;

        this.includedServices = {};
        this.characteristics = {};
    };
    Service.prototype.discoverIncludedServices = function(serviceUUIDs, completeFn, errorFn) {
        if (typeof serviceUUIDs === "function") {
            completeFn = serviceUUIDs;
            serviceUUIDs = [];
        } else if (typeof serviceUUIDs === "string") {
            serviceUUIDs = [serviceUUIDs];
        }
        adapter.discoverIncludedServices(this._handle, serviceUUIDs, function(services) {
            services.forEach(function(serviceInfo) {
                this.includedServices[serviceInfo.uuid] = new Service(serviceInfo);
            }, this);

            if (completeFn) completeFn();
        }.bind(this), raiseError(errorFn, "included service discovery error"));
    };
    Service.prototype.discoverCharacteristics = function(characteristicUUIDs, completeFn, errorFn) {
        if (typeof characteristicUUIDs === "function") {
            completeFn = characteristicUUIDs;
            characteristicUUIDs = [];
        } else if (typeof characteristicUUIDs === "string") {
            characteristicUUIDs = [characteristicUUIDs];
        }
        adapter.discoverCharacteristics(this._handle, characteristicUUIDs, function(characteristics) {
            characteristics.forEach(function(characteristicInfo) {
                this.characteristics[characteristicInfo.uuid] = new Characteristic(characteristicInfo);
            }, this);

            if (completeFn) completeFn();
        }.bind(this), raiseError(errorFn, "characteristic discovery error"));
    };

    // Characteristic Object
    var Characteristic = function(characteristicInfo) {
        this._handle = characteristicInfo._handle;

        this.uuid = characteristicInfo.uuid;
        this.properties = characteristicInfo.properties;

        this.descriptors = {};
    };
    Characteristic.prototype.discoverDescriptors = function(descriptorUUIDs, completeFn, errorFn) {
        if (typeof descriptorUUIDs === "function") {
            completeFn = descriptorUUIDs;
            descriptorUUIDs = [];
        } else if (typeof descriptorUUIDs === "string") {
            descriptorUUIDs = [descriptorUUIDs];
        }
        adapter.discoverDescriptors(this._handle, descriptorUUIDs, function(descriptors) {
            descriptors.forEach(function(descriptorInfo) {
                this.descriptors[descriptorInfo.uuid] = new Descriptor(descriptorInfo);
            }, this);

            if (completeFn) completeFn();
        }.bind(this), raiseError(errorFn, "descriptor discovery error"));
    };
    Characteristic.prototype.read = function(completeFn, errorFn) {
        adapter.readCharacteristic(this._handle, executeFn(completeFn), raiseError(errorFn, "read characteristic error"));
    };
    Characteristic.prototype.write = function(dataView, completeFn, errorFn) {
        adapter.writeCharacteristic(this._handle, dataView, executeFn(completeFn), raiseError(errorFn, "write characteristic error"));
    };
    Characteristic.prototype.enableNotify = function(notifyFn, completeFn, errorFn) {
        adapter.enableNotify(this._handle, executeFn(notifyFn), executeFn(completeFn), raiseError(errorFn, "enable notify error"));
    };
    Characteristic.prototype.disableNotify = function(completeFn, errorFn) {
        adapter.disableNotify(this._handle, executeFn(completeFn), raiseError(errorFn, "disable notify error"));
    };

    // Descriptor Object
    var Descriptor = function(descriptorInfo) {
        this._handle = descriptorInfo._handle;

        this.uuid = descriptorInfo.uuid;
    };
    Descriptor.prototype.read = function(completeFn, errorFn) {
        adapter.readDescriptor(this._handle, executeFn(completeFn), raiseError(errorFn, "read descriptor error"));
    };
    Descriptor.prototype.write = function(dataView, completeFn, errorFn) {
        adapter.writeDescriptor(this._handle, dataView, executeFn(completeFn), raiseError(errorFn, "write descriptor error"));
    };

    // Main Module
    return {
        _addAdapter: function(adapterName, definition) {
            adapters[adapterName] = definition;
            adapter = definition;
        },
        startScan: function(serviceUUIDs, foundFn, completeFn, errorFn, allowDuplicates) {
            if (typeof serviceUUIDs === "function") {
                // Service UUIDs not present, shift args.
                allowDuplicates = errorFn;
                errorFn = completeFn;
                completeFn = foundFn;
                foundFn = serviceUUIDs;
                serviceUUIDs = [];
            } else if (typeof serviceUUIDs === "string") {
                serviceUUIDs = [serviceUUIDs];
            }
            adapter.stopScan(raiseError(errorFn, "stop scan error"));
            var devices = {};
            adapter.startScan(serviceUUIDs, function(deviceInfo) {
                var device = new Device(deviceInfo);
                if (devices[device.address] && !allowDuplicates) return;
                devices[device.address] = device;
                if (foundFn) foundFn(device);
            }.bind(this), completeFn, raiseError(errorFn, "scan error"));
        },
        stopScan: function(errorFn) {
            adapter.stopScan(raiseError(errorFn, "stop scan error"));
        }
    };
}));


'use strict';

var Stream = require('stream');
var Duplexer = require('plexer');

// Plugin function
function streamifyGulp(pluginStream) {

  var inputStream = new Stream.Transform({ objectMode: true });
  var outputStream = new Stream.Transform({ objectMode: true });
  var duplex = new Duplexer({ objectMode: true }, inputStream, outputStream);

  // Accepting functions returning streams
  if('function' === typeof pluginStream) {
    pluginStream = pluginStream();
  }

  // Listening for plugin errors and reemit
  pluginStream.on('error', function(error) {
    duplex.emit('error', error);
  });

  // Change files contents from stream to buffer and write to the plugin stream
  inputStream._transform = function(file, unused, cb) {
    // Buffering the file stream
    var originalStream;
    var buf;
    var bufstream;

    if(file.isNull() || file.isBuffer()) {
      inputStream.push(file);
      return cb();
    }
    file.wasStream = true;
    originalStream = file.contents;
    buf = new Buffer(0);
    bufstream = new Stream.Writable();

    // Buffer the stream
    bufstream._write = function(chunk, encoding, cb2) {
      buf = Buffer.concat([buf, chunk], buf.length + chunk.length);
      cb2();
    };

    // When buffered
    bufstream.once('finish', function() {
      // Send the buffer wrapped in a file
      file.contents = buf;
      inputStream.push(file);
      cb();
    });

    originalStream.pipe(bufstream);

  };

  // Change files contents from buffer to stream and write to the output stream
  outputStream._transform = function(file, unused, cb) {
    var buf;
    var newStream;

    if(file.isNull() || !file.wasStream) {
      outputStream.push(file);
      return cb();
    }
    delete file.wasStream;
    // Get the transformed buffer
    buf = file.contents;
    newStream = new Stream.Readable();
    // Write the buffer only when datas are needed
    newStream._read = function() {
      // Write the content back to the stream
      newStream.push(buf);
      newStream.push(null);
    };
    // Pass the file out
    file.contents = newStream;
    outputStream.push(file);
    cb();
  };
  outputStream._flush = function(cb) {
    setImmediate(function() {
      // Old streams WTF
      if(!pluginStream._readableState) {
        outputStream.emit('end');
        duplex.emit('end');
      }
    });
    cb();
  };

  inputStream
    .pipe(pluginStream)
    .pipe(outputStream);

  return duplex;

}

// Export the plugin main function
module.exports = streamifyGulp;


//! Copyright 2012 Eric Wendelin - MIT License

/**
 * es6-map-shim.js is a DESTRUCTIVE shim that follows the latest Map specification as closely as possible.
 * It is destructive in the sense that it overrides native implementations.
 *
 * This library assumes ES5 functionality: Object.create, Object.defineProperty, Array.indexOf, Function.bind
 */
(function(module) {
    function Map(iterable) {
        var _items = [];
        var _keys = [];
        var _values = [];

        // Object.is polyfill, courtesy of @WebReflection
        var is = Object.is || function(a, b) {
            return a === b ?
                a !== 0 || 1 / a == 1 / b :
                a != a && b != b;
        };

        // More reliable indexOf, courtesy of @WebReflection
        var betterIndexOf = function(value) {
            if(value != value || value === 0) {
                for(var i = this.length; i-- && !is(this[i], value);){}
            } else {
                i = [].indexOf.call(this, value);
            }
            return i;
        };

        var MapIterator = function MapIterator(map, kind) {
            var _index = 0;

            return Object.create({}, {
                next: {
                    value: function() {
                        // check if index is within bounds
                        if (_index < map.items().length) {
                            switch(kind) {
                                case 'keys': return map.keys()[_index++];
                                case 'values': return map.values()[_index++];
                                case 'keys+values': return [].slice.call(map.items()[_index++]);
                                default: throw new TypeError('Invalid iterator type');
                            }
                        }
                        // TODO: make sure I'm interpreting the spec correctly here
                        throw new Error('Stop Iteration');
                    }
                },
                iterator: {
                    value: function() {
                        return this;
                    }
                },
                toString: {
                    value: function() {
                        return '[object Map Iterator]';
                    }
                }
            });
        };

        var _set = function(key, value) {
            // check if key exists and overwrite
            var index = betterIndexOf.call(_keys, key);
            if (index > -1) {
                _items[index] = value;
                _values[index] = value;
            } else {
                _items.push([key, value]);
                _keys.push(key);
                _values.push(value);
            }
        };

        var setItem = function(item) {
            if (item.length !== 2) {
                throw new TypeError('Invalid iterable passed to Map constructor');
            }

            _set(item[0], item[1]);
        };

        // FIXME: accommodate any class that defines an @@iterator method that returns
        //      an iterator object that produces two element array-like objects
        if (Array.isArray(iterable)) {
            iterable.forEach(setItem);
        } else if (iterable !== undefined) {
            throw new TypeError('Invalid Map');
        }

        return Object.create(MapPrototype, {
            items:{
                value:function() {
                    return [].slice.call(_items);
                }
            },
            keys:{
                value:function() {
                    return [].slice.call(_keys);
                }
            },
            values:{
                value:function() {
                    return [].slice.call(_values);
                }
            },
            has:{
                value:function(key) {
                    // TODO: check how spec reads about null values
                    var index = betterIndexOf.call(_keys, key);
                    return index > -1;
                }
            },
            get:{
                value:function(key) {
                    var index = betterIndexOf.call(_keys, key);
                    return index > -1 ? _values[index] : undefined;
                }
            },
            set:{
                value: _set
            },
            size:{
                get:function() {
                    return _items.length;
                }
            },
            clear:{
                value:function() {
                    _keys.length = _values.length = _items.length = 0;
                }
            },
            'delete':{
                value:function(key) {
                    var index = betterIndexOf.call(_keys, key);
                    if (index > -1) {
                        _keys.splice(index, 1);
                        _values.splice(index, 1);
                        _items.splice(index, 1);
                        return true;
                    }
                    return false;
                }
            },
            forEach:{
                value:function(callbackfn /*, thisArg*/) {
                    if (typeof callbackfn != 'function') {
                        throw new TypeError('Invalid callback function given to forEach');
                    }

                    function tryNext() {
                        try {
                            return iter.next();
                        } catch(e) {
                            return undefined;
                        }
                    }

                    var iter = this.iterator();
                    var current = tryNext();
                    var next = tryNext();
                    while(current !== undefined) {
                        callbackfn.apply(arguments[1], [current[1], current[0], this]);
                        current = next;
                        next = tryNext();
                    }
                }
            },
            iterator:{
                value: function() {
                    return new MapIterator(this, 'keys+values');
                }
            },
            toString:{
                value: function() {
                    return '[Object Map]';
                }
            }
        });
    }

    var notInNode = module == 'undefined';
    var window = notInNode ? this : global;
    var module = notInNode ? {} : exports;
    var MapPrototype = Map.prototype;

    Map.prototype = MapPrototype = Map();

    window.Map = module.Map = window.Map || Map;
}.call(this, typeof exports));
//! Copyright 2012 Eric Wendelin - MIT License
(function() {
    'use strict';

    /**
     * Given a filtering function, return a new Map of items matching that
     * function.
     *
     * @param filterFn {Function} that takes up to 3 arguments and returns a Boolean.
     * @return {Map} a new Map that is the subset of this map's items
     * that match the given filter function.
     */
    Map.prototype.filter = function(filterFn) {
        if (typeof filterFn != 'function') {
            throw new TypeError('Expected a function argument');
        }
        var _map = new Map();
        this.forEach(function(value, key, map) {
            if(filterFn(key, value, map)) {
                _map.set(key, value);
            }
        });
        return _map;
    };

    /**
     * Return a new Map that is the union of this Map and the given other Map.
     * If there are conflicting keys, the item in the Map argument overrides.
     *
     * @param otherMap {Map}
     * @return {Map} with all non-conflicting items
     */
    Map.prototype.merge = function(otherMap) {
        if (!(otherMap instanceof Map)) {
            throw new TypeError('Cannot merge with objects that are not Maps');
        }

        function setAll(value, key) {
            _map.set(key, value);
        }
        var _map = new Map();

        this.forEach(setAll);
        otherMap.forEach(setAll);
        return _map;
    };

    /**
     * Get entry for given key, or if it doesn't exist the default value.
     *
     * @param key {Object} anything, including primitives
     * @param defaultValue {Object}
     * @return item at key or default
     */
    Map.prototype.fetch = function(key, defaultValue) {
        if (this.has(key)) {
            return this.get(key);
        }
        return defaultValue;
    };

    /**
     * Return a new Map whose keys are the values of this Map, and values are keys.
     *
     * @return a new Map, this map inverted
     */
    Map.prototype.invert = function() {
        var _map = new Map();
        this.forEach(_map.set);
        return _map;
    };

    /**
     * Remove items from this Map as designated by a filtering function.
     *
     * @param filterFn {Function} using key, value and/or index and returning a Boolean
     */
    Map.prototype.reject = function(filterFn) {
        if (typeof filterFn != 'function') {
            throw new TypeError('Expected a function argument');
        }
        this.forEach(function(value, key, map) {
            if(filterFn(key, value, map)) {
                map['delete'](key);
            }
        }.bind(this));
    };

    /**
     * @return true if there are no entries in this Map.
     */
    Map.prototype.isEmpty = function() {
        return this.keys().length === 0;
    };
})();


var spawn   = require('child_process').spawn;
var Promise = require('bluebird');

/**
 * A wrapper for the tor process containing its ChildProcess, port number
 * and data directory.
 *
 * @param {ChildProcess} child
 * @param {int}          port
 * @param {string}       dir
 */
function Tor(child, port, dir) {
  this.process = child;
  this.port    = port;
  this.dir     = dir;
}

/**
 * Given a port number and directory path, spawns a Tor process. Returns a
 * promise that resolves with an instance of the Tor class. Since it's using
 * a new DataDirectory with no cached microdescriptors or any other state,
 * it can take a bit for it to start up. An optional retryTimeout will have
 * the spawn function kill the child process after that number of milliseconds,
 * and retry. The Tor process is killed when node exits.
 *
 * @returns {Promise}
 */
Tor.spawn = function(port, dir, retryTimeout) {
  return new Promise(function(resolve, reject) {
    var child = spawn('tor', ['--SOCKSPort', port, '--DataDirectory', dir]);
    var active = false;

    if (retryTimeout) {
      var timeout = setTimeout(function() {
        if (active) return;
        child.removeAllListeners();
        child.kill('SIGINT');

        // Let tor exit gracefully, since we try to bind on
        // the same port
        setTimeout(function() {
          resolve(Tor.spawn(port, dir, retryTimeout));
        }, 1000);
      }, retryTimeout);
    }

    var listener = function(data) {
      if (data.toString('utf8').indexOf('Done') === -1) return;

      child.stdout.removeListener('data', listener);
      clearTimeout(timeout);
      active = true;

      process.on('exit', function() {
        child.kill('SIGINT');
      });

      resolve(new Tor(child, port, dir));
    };

    child.stdout.on('data', listener);

    child.on('close', function(code) {
      if (!active) reject();
    });
  });
};

/**
 * Rotates the IP address used by Tor by sending a SIGHUP.
 *
 * @returns {Promise}
 */
Tor.prototype.rotateAddress = function() {
  return this.signalWait('SIGHUP', 'Received reload signal');
};

/**
 * Sends a SIGINT to Tor, waiting for it to exit.
 *
 * @returns {Promise}
 */
Tor.prototype.destroy = function() {
  return this.signalWait('SIGINT', 'exiting cleanly').delay(1000);
};

/**
 * Sends the given signal to the child process, and returns a promise that
 * resolves once the given msg has been written to its stdout.
 *
 * @returns {Promise}
 */
Tor.prototype.signalWait = function(signal, msg) {
  var child = this.process;

  return new Promise(function(resolve) {
    var listener = function(data) {
      if (data.toString('utf8').indexOf(msg) === -1) return;
      child.stdout.removeListener('data', listener);
      resolve();
    };

    child.stdout.on('data', listener)
    child.kill(signal);
  });
}

module.exports = Tor;


'use strict';

var DEFAULT_PATHS = ['lib/', 'src/'],
    config = require('./config'),
    fs = require('fs'),
    Path = require('path');
var glob = require('glob');

/**
 * Returns an Array containing the excluded locations.
 */
function getExcluded() {
  var args = config.files().excluded || [];
  return _expandGlobs(args);
}

/**
 * Returns an Array containing the included locations.
 */
function getIncluded() {
  var args = config.files().included || getDefaultArgs() ||
              getPackageJsonArgs() || getBowerJsonArgs() || [];
  return _expandGlobs(args);
}

/**
 * Returns an Array of default paths.
 */
function getDefaultArgs() {
  var results = [];
  DEFAULT_PATHS.forEach(function(dir) {
    if( fs.existsSync(dir) ) results.push(dir);
  });
  return results.length == 0 ? null : results;
}

/**
 * Checks package.json for possible paths
 */
function getPackageJsonArgs() {
  var results = [];
  var config = _loadJson('package.json');
  if( config.main ) results = results.concat(getMainFieldAsArray(config.main));
  if( config.files ) results = results.concat(config.files);
  return results.length == 0 ? null : results.filter(_uniqfilter);
}

/**
 * Checks bower.json for possible paths
 */
function getBowerJsonArgs() {
  var results = [];
  var config = _loadJson('bower.json');
  if( config.main ) results = results.concat(getMainFieldAsArray(config.main));
  return results.length == 0 ? null : results.filter(_uniqfilter);
}

/**
 *  Returns an Array of the files mentioned in a 'main' field
 *  If the value is noted without '.js' at the end, it is appended.
 */
function getMainFieldAsArray(main) {
  if( main.constructor === Array ) {
    return main;
  } else {
    if( fs.existsSync(main) ) {
      return [main];
    } else if( fs.existsSync(main+'.js') ) {
      return [main+'.js'];
    } else {
      return [];
    }
  }
}

function _loadJson(_filename) {
  var result = {};
  var filename = Path.join(process.cwd(), _filename);
  if( fs.existsSync(filename) ) result = require(filename);
  return result;
}

function _uniqfilter(value, index, self) {
    return self.indexOf(value) === index;
}

function _expandGlobs(args) {
  var results = [];
  for(var i=0;i<args.length;i++) {
    results = results.concat(glob.sync(args[i]))
  }
  return results;
}

module.exports = {
  getExcluded: getExcluded,
  getIncluded: getIncluded
};