// eslint-disable-next-line max-params
(function main(encode, decode) {
    return module.exports = Object.freeze({
        parse_query_string,
        parse_query_params,
        compose_query_string,
        }); // eslint-disable-line indent

    // -----------

    function parse_query_string(query_string) {
        const query_object = {};
        const query_pairs = query_string.split(/&+/);
        for (const pair of query_pairs) {
            let separator_index = pair.indexOf('=');
            if (0 === separator_index) {
                separator_index = pair.indexOf('=', 1);
            }
            /* eslint-disable indent */
            const key = decode(pair.substring(
                0,
                -1 === separator_index ? undefined : separator_index,
                ));
            const value
                = -1 === separator_index ? true
                : pair.length === separator_index + 1 ? null
                : decode(pair.substring(separator_index + 1))
                ;
            /* eslint-enable indent */
            if (is_composite_key(key)) {
                add_composite_value(key, value);
            } else {
                query_object[ decode(key) ] = value;
            }
        }
        return query_object;

        // -----------

        function is_composite_key(key) {
            const open_brack_i = key.indexOf('[', 1);
            return open_brack_i > 0 && key.includes(']', open_brack_i);
        }
        function add_composite_value(key, value) {
            const path_keys = get_path_keys(key);
            let query_object_value = query_object;
            for (let i = 0, n = path_keys.length - 1; i <= n; i++) {
                const path_key = path_keys[i];
                if (i === n) {
                    if (null === path_key
                        && query_object_value instanceof Set
                        ) { // eslint-disable-line indent
                        query_object_value.add(value);
                    } else {
                        /* eslint-disable indent */
                        const real_path_key
                            = Array.isArray(query_object_value) ? path_key
                            : null === path_key ? ''
                            : String(path_key)
                            ;
                        /* eslint-enable indent */
                        query_object_value[real_path_key] = value;
                    }
                } else {
                    ensure_query_object_value(path_key, path_keys[i + 1]);
                }
            }
            return true;

            // -----------

            function ensure_query_object_value(path_key, next_path_key) {
                if (!query_object_value[path_key]) {
                    /* eslint-disable indent */
                    query_object_value[path_key]
                        = 'number' === typeof next_path_key ? []
                        : null === next_path_key ? new Set
                        : {}
                        ;
                    /* eslint-enable indent */
                } else if ('string' === typeof next_path_key
                    && Array.isArray(query_object_value[path_key])
                    ) { // eslint-disable-line indent
                    query_object_value[path_key] = convert_to_object(
                        query_object_value[path_key],
                        ); // eslint-disable-line indent
                } else if ('string' === typeof next_path_key
                    && query_object_value[path_key] instanceof Set
                    ) { // eslint-disable-line indent
                    query_object_value[path_key] = convert_to_object(
                        Array.from(query_object_value[path_key]),
                        ); // eslint-disable-line indent
                }
                query_object_value = query_object_value[path_key];
                return true;
            }
            function convert_to_object(arr) {
                const obj = {};
                const indices = Object.keys(arr);
                for (const index of indices) {
                    obj[String(index)] = arr[index];
                }
                return obj;
            }
        }
        function get_path_keys(path_string) {
            const path_keys = [];
            let unparsed_path = path_string;
            while (unparsed_path) {
                const open_brack_i = unparsed_path.indexOf('[');
                if (0 === open_brack_i) {
                    const close_brack_i
                        = unparsed_path.indexOf(']', open_brack_i)
                        ; // eslint-disable-line indent
                    path_keys.push(
                        unparsed_path.substring(1, close_brack_i),
                        ); // eslint-disable-line indent
                    unparsed_path = unparsed_path.substring(close_brack_i + 1);
                } else {
                    const path_key = -1 === open_brack_i
                        ? unparsed_path
                        : unparsed_path.substring(0, open_brack_i)
                        ; // eslint-disable-line indent
                    path_keys.push(path_key);
                    unparsed_path = -1 === open_brack_i
                        ? ''
                        : unparsed_path.substring(open_brack_i)
                        ; // eslint-disable-line indent
                }
            }
            return path_keys.map(coerce_numbers);

            // -----------

            function coerce_numbers(item) {
                return '' === item ? null
                    : !isNaN(item) && 'Infinity' !== item ? Number(item)
                    : item // eslint-disable-line indent
                    ; // eslint-disable-line indent
            }
        }
    }

    function parse_query_params(query_params) {
        if (query_params instanceof Set) {
            return new Set(parse_query_params(Array.from(query_params)));
        }
        const query_object = Array.isArray(query_params) ? [] : {};
        const keys = Object.keys(query_params).sort();
        for (const raw_key of keys) {
            const key = decode(raw_key);
            const value = query_params[raw_key];
            switch (true) {
                case undefined === value:
                    query_object[key] = null;
                    break;
                case null === value:
                case '' === value:
                case 'number' === typeof value:
                case 'boolean' === typeof value:
                case 'symbol' === typeof value:
                    query_object[key] = value;
                    break;
                case 'string' === typeof value:
                    query_object[key] = decode(value);
                    break;
                case 'object' === typeof value:
                    query_object[key] = parse_query_params(value);
                    break;
            }
        }
        return query_object;
    }

    function compose_query_string(query_params) {
        if (!query_params || 'object' !== typeof query_params) {
            return '';
        }
        let uri_query = '';
        const keys = Object.keys(query_params).sort();
        for (const key of keys) {
            const separator = key === keys[0] ? '?' : '&';
            const value = query_params[key];
            switch (true) {
                case undefined === value:
                case null === value:
                case 'string' === typeof value:
                case 'number' === typeof value:
                case 'boolean' === typeof value:
                case 'symbol' === typeof value:
                    uri_query += `${ separator }${ compose_pair(key, value) }`;
                    break;
                case 'object' === typeof value:
                    uri_query
                        += `${ separator }${ compose_subquery(key, value) }`
                        ; // eslint-disable-line indent
                    break;
                // Ignore functions
            }
        }
        return uri_query;

        // -----------

        function compose_pair(key, value) {
            return `${ encode(key) }${ get_primitive_pair_suffix(value) }`;
        }
        function get_primitive_pair_suffix(value) {
            /* eslint-disable indent */
            return true === value ? ''
                : undefined === value || null === value ? '='
                : `=${ encode(String(value)) }`
                ;
            /* eslint-enable indent */
        }

        function compose_subquery(key, value) {
            return _compose_subquery(encode(key), value);
        }
        function _compose_subquery(key_prefix, iterable_value) {
            const subquery = [];
            if (iterable_value instanceof Set) {
                for (const item of iterable_value) {
                    const key = `${ key_prefix }[]`;
                    const value = item;
                    const pair = get_to_subquery_pair(subquery, key, value);
                    pair && subquery.push(pair);
                }
                return subquery.join('&');
            }
            // Force predictable order of keys for testability
            const subquery_keys = Object.keys(iterable_value).sort();
            for (const raw_key of subquery_keys) {
                const key = `${ key_prefix }[${ encode(raw_key) }]`;
                const value = iterable_value[raw_key];
                const pair = get_to_subquery_pair(subquery, key, value);
                pair && subquery.push(pair);
            }
            return subquery.join('&');
        }
        function get_to_subquery_pair(subquery, key, value) {
            switch (true) {
                case undefined === value:
                case null === value:
                case 'string' === typeof value:
                case 'number' === typeof value:
                case 'boolean' === typeof value:
                case 'symbol' === typeof value:
                    return `${ key }${ get_primitive_pair_suffix(value) }`;
                case 'object' === typeof value:
                    return _compose_subquery(key, value);
            }
            return null;
        }
    }
}(
    encodeURIComponent,
    function decode_uri_component(raw_value) {
        const value = String(raw_value).replace(/\+/g, '%20');
        return decodeURIComponent(value);
    },
));


require.config({
    paths: {
        bootstrap: './vendor/bootstrap.min',
        diffMatchPatch: './vendor/diff_match_patch.min',
        handlebars: './vendor/handlebars.min',
        handlebarsExtended: './utils/handlebars_helper',
        jquery: './vendor/jquery.min',
        locales: './locales/locale',
        lodash: './vendor/lodash.min',
        pathToRegexp: './vendor/path-to-regexp/index',
        prettify: './vendor/prettify/prettify',
        utilsSampleRequest: './utils/send_sample_request',
    },
    shim: {
        bootstrap: {
            deps: ['jquery']
        },
        diffMatchPatch: {
            exports: 'diff_match_patch'
        },
        handlebars: {
            exports: 'Handlebars'
        },
        handlebarsExtended: {
            deps: ['jquery', 'handlebars'],
            exports: 'Handlebars'
        },
        prettify: {
            exports: 'prettyPrint'
        }
    },
    urlArgs: 'v=' + (new Date()).getTime(),
    waitSeconds: 15
});

require([
    'jquery',
    'lodash',
    'locales',
    'handlebarsExtended',
    './api_project.js',
    './api_data.js',
    'prettify',
    'utilsSampleRequest',
    'bootstrap',
    'pathToRegexp'
], function($, _, locale, Handlebars, apiProject, apiData, prettyPrint, sampleRequest) {

    // load google web fonts
    loadGoogleFontCss();

    var api = apiData.api;

    //
    // Templates
    //
    var templateHeader         = Handlebars.compile( $('#template-header').html() );
    var templateFooter         = Handlebars.compile( $('#template-footer').html() );
    var templateArticle        = Handlebars.compile( $('#template-article').html() );
    var templateCompareArticle = Handlebars.compile( $('#template-compare-article').html() );
    var templateGenerator      = Handlebars.compile( $('#template-generator').html() );
    var templateProject        = Handlebars.compile( $('#template-project').html() );
    var templateSections       = Handlebars.compile( $('#template-sections').html() );
    var templateSidenav        = Handlebars.compile( $('#template-sidenav').html() );

    //
    // apiProject defaults
    //
    if ( ! apiProject.template)
        apiProject.template = {};

    if (apiProject.template.withCompare == null)
        apiProject.template.withCompare = true;

    if (apiProject.template.withGenerator == null)
        apiProject.template.withGenerator = true;

    if (apiProject.template.forceLanguage)
        locale.setLanguage(apiProject.template.forceLanguage);

    // Setup jQuery Ajax
    $.ajaxSetup(apiProject.template.jQueryAjaxSetup);

    //
    // Data transform
    //
    // grouped by group
    var apiByGroup = _.groupBy(api, function(entry) {
        return entry.group;
    });

    // grouped by group and name
    var apiByGroupAndName = {};
    $.each(apiByGroup, function(index, entries) {
        apiByGroupAndName[index] = _.groupBy(entries, function(entry) {
            return entry.name;
        });
    });

    //
    // sort api within a group by title ASC and custom order
    //
    var newList = [];
    var umlauts = { 'ä': 'ae', 'ü': 'ue', 'ö': 'oe', 'ß': 'ss' }; // TODO: remove in version 1.0
    $.each (apiByGroupAndName, function(index, groupEntries) {
        // get titles from the first entry of group[].name[] (name has versioning)
        var titles = [];
        $.each (groupEntries, function(titleName, entries) {
            var title = entries[0].title;
            if(title !== undefined) {
                title.toLowerCase().replace(/[äöüß]/g, function($0) { return umlauts[$0]; });
                titles.push(title + '#~#' + titleName); // '#~#' keep reference to titleName after sorting
            }
        });
        // sort by name ASC
        titles.sort();

        // custom order
        if (apiProject.order)
            titles = sortByOrder(titles, apiProject.order, '#~#');

        // add single elements to the new list
        titles.forEach(function(name) {
            var values = name.split('#~#');
            var key = values[1];
            groupEntries[key].forEach(function(entry) {
                newList.push(entry);
            });
        });
    });
    // api overwrite with ordered list
    api = newList;

    //
    // Group- and Versionlists
    //
    var apiGroups = {};
    var apiGroupTitles = {};
    var apiVersions = {};
    apiVersions[apiProject.version] = 1;

    $.each(api, function(index, entry) {
        apiGroups[entry.group] = 1;
        apiGroupTitles[entry.group] = entry.groupTitle || entry.group;
        apiVersions[entry.version] = 1;
    });

    // sort groups
    apiGroups = Object.keys(apiGroups);
    apiGroups.sort();

    // custom order
    if (apiProject.order)
        apiGroups = sortByOrder(apiGroups, apiProject.order);

    // sort versions DESC
    apiVersions = Object.keys(apiVersions);
    apiVersions.sort();
    apiVersions.reverse();

    //
    // create Navigationlist
    //
    var nav = [];
    apiGroups.forEach(function(group) {
        // Mainmenu entry
        nav.push({
            group: group,
            isHeader: true,
            title: apiGroupTitles[group]
        });

        // Submenu
        var oldName = '';
        api.forEach(function(entry) {
            if (entry.group === group) {
                if (oldName !== entry.name) {
                    nav.push({
                        title: entry.title,
                        group: group,
                        name: entry.name,
                        type: entry.type,
                        version: entry.version
                    });
                } else {
                    nav.push({
                        title: entry.title,
                        group: group,
                        hidden: true,
                        name: entry.name,
                        type: entry.type,
                        version: entry.version
                    });
                }
                oldName = entry.name;
            }
        });
    });

    // Mainmenu Header entry
    if (apiProject.header) {
        nav.unshift({
            group: '_',
            isHeader: true,
            title: (apiProject.header.title == null) ? locale.__('General') : apiProject.header.title,
            isFixed: true
        });
    }

    // Mainmenu Footer entry
    if (apiProject.footer && apiProject.footer.title != null) {
        nav.push({
            group: '_footer',
            isHeader: true,
            title: apiProject.footer.title,
            isFixed: true
        });
    }

    // render pagetitle
    var title = apiProject.title ? apiProject.title : 'apiDoc: ' + apiProject.name + ' - ' + apiProject.version;
    $(document).attr('title', title);

    // remove loader
    $('#loader').remove();

    // render sidenav
    var fields = {
        nav: nav
    };
    $('#sidenav').append( templateSidenav(fields) );

    // render Generator
    $('#generator').append( templateGenerator(apiProject) );

    // render Project
    _.extend(apiProject, { versions: apiVersions});
    $('#project').append( templateProject(apiProject) );

    // render apiDoc, header/footer documentation
    if (apiProject.header)
        $('#header').append( templateHeader(apiProject.header) );

    if (apiProject.footer)
        $('#footer').append( templateFooter(apiProject.footer) );

    //
    // Render Sections and Articles
    //
    var articleVersions = {};
    var content = '';
    apiGroups.forEach(function(groupEntry) {
        var articles = [];
        var oldName = '';
        var fields = {};
        var title = groupEntry;
        var description = '';
        articleVersions[groupEntry] = {};

        // render all articles of a group
        api.forEach(function(entry) {
            if(groupEntry === entry.group) {
                if (oldName !== entry.name) {
                    // determine versions
                    api.forEach(function(versionEntry) {
                        if (groupEntry === versionEntry.group && entry.name === versionEntry.name) {
                            if ( ! articleVersions[entry.group][entry.name])
                                articleVersions[entry.group][entry.name] = [];

                            articleVersions[entry.group][entry.name].push(versionEntry.version);
                        }
                    });
                    fields = {
                        article: entry,
                        versions: articleVersions[entry.group][entry.name]
                    };
                } else {
                    fields = {
                        article: entry,
                        hidden: true,
                        versions: articleVersions[entry.group][entry.name]
                    };
                }

                // add prefix URL for endpoint
                if (apiProject.url)
                    fields.article.url = apiProject.url + fields.article.url;

                addArticleSettings(fields, entry);

                if (entry.groupTitle)
                    title = entry.groupTitle;

                // TODO: make groupDescription compareable with older versions (not important for the moment)
                if (entry.groupDescription)
                    description = entry.groupDescription;

                articles.push({
                    article: templateArticle(fields),
                    group: entry.group,
                    name: entry.name
                });
                oldName = entry.name;
            }
        });

        // render Section with Articles
        var fields = {
            group: groupEntry,
            title: title,
            description: description,
            articles: articles
        };
        content += templateSections(fields);
    });
    $('#sections').append( content );

    // Bootstrap Scrollspy
    var $scrollSpy = $(this).scrollspy({ target: '#scrollingNav', offset: 18 });
    $('[data-spy="scroll"]').each(function () {
        $scrollSpy('refresh');
    });

    // Content-Scroll on Navigation click.
    $('.sidenav').find('a').on('click', function(e) {
        e.preventDefault();
        var id = $(this).attr('href');
        if ($(id).length > 0)
            $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 400);
        window.location.hash = $(this).attr('href');
    });

    // Quickjump on Pageload to hash position.
    if(window.location.hash) {
        var id = window.location.hash;
        if ($(id).length > 0)
            $('html,body').animate({ scrollTop: parseInt($(id).offset().top) }, 0);
    }

    /**
     * Check if Parameter (sub) List has a type Field.
     * Example: @apiSuccess          varname1 No type.
     *          @apiSuccess {String} varname2 With type.
     *
     * @param {Object} fields
     */
    function _hasTypeInFields(fields) {
        var result = false;
        $.each(fields, function(name) {
            if (_.any(fields[name], function(item) { return item.type; }) )
                result = true;
        });
        return result;
    }

    /**
     * On Template changes, recall plugins.
     */
    function initDynamic() {
        // bootstrap popover
        $('a[data-toggle=popover]')
            .popover()
            .click(function(e) {
                e.preventDefault();
            })
        ;

        var version = $('#version strong').html();
        $('#sidenav li').removeClass('is-new');
        if (apiProject.template.withCompare) {
            $('#sidenav li[data-version=\'' + version + '\']').each(function(){
                var group = $(this).data('group');
                var name = $(this).data('name');
                var length = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').length;
                var index  = $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\']').index($(this));
                if (length === 1 || index === (length - 1))
                    $(this).addClass('is-new');
            });
        }

        // tabs
        $('.nav-tabs-examples a').click(function (e) {
            e.preventDefault();
            $(this).tab('show');
        });
        $('.nav-tabs-examples').find('a:first').tab('show');

        // sample request switch
        $('.sample-request-switch').click(function (e) {
            var name = '.' + $(this).attr('name') + '-fields';
            $(name).addClass('hide');
            $(this).parent().next(name).removeClass('hide');
        });

        // init modules
        sampleRequest.initDynamic();
    }
    initDynamic();

    // Pre- / Code-Format
    prettyPrint();

    //
    // HTML-Template specific jQuery-Functions
    //
    // Change Main Version
    $('#versions li.version a').on('click', function(e) {
        e.preventDefault();

        var selectedVersion = $(this).html();
        $('#version strong').html(selectedVersion);

        // hide all
        $('article').addClass('hide');
        $('#sidenav li:not(.nav-fixed)').addClass('hide');

        // show 1st equal or lower Version of each entry
        $('article[data-version]').each(function(index) {
            var group = $(this).data('group');
            var name = $(this).data('name');
            var version = $(this).data('version');

            if (version <= selectedVersion) {
                if($('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible').length === 0) {
                    // enable Article
                    $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
                    // enable Navigation
                    $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('hide');
                    $('#sidenav li.nav-header[data-group=\'' + group + '\']').removeClass('hide');
                }
            }
        });

        initDynamic();
        return;
    });

    // compare all article with their predecessor
    $('#compareAllWithPredecessor').on('click', changeAllVersionCompareTo);

    // change version of an article
    $('article .versions li.version a').on('click', changeVersionCompareTo);

    // compare url-parameter
    $.urlParam = function(name) {
        var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
        return (results && results[1]) ? results[1] : null;
    };

    if ($.urlParam('compare')) {
        // URL Paramter ?compare=1 is set
        $('#compareAllWithPredecessor').trigger('click');

        if (window.location.hash) {
            var id = window.location.hash;
            $('html,body').animate({ scrollTop: parseInt($(id).offset().top) - 18 }, 0);
        }
    }

    /**
     * Change version of an article to compare it to an other version.
     */
    function changeVersionCompareTo(e) {
        e.preventDefault();

        var $root = $(this).parents('article');
        var selectedVersion = $(this).html();
        var $button = $root.find('.version');
        var currentVersion = $button.find('strong').html();
        $button.find('strong').html(selectedVersion);

        var group = $root.data('group');
        var name = $root.data('name');
        var version = $root.data('version');

        var compareVersion = $root.data('compare-version');

        if (compareVersion === selectedVersion)
            return;

        if ( ! compareVersion && version == selectedVersion)
            return;

        if (compareVersion && articleVersions[group][name][0] === selectedVersion || version === selectedVersion) {
            // the version of the entry is set to the highest version (reset)
            resetArticle(group, name, version);
        } else {
            var $compareToArticle = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + selectedVersion + '\']');

            var sourceEntry = {};
            var compareEntry = {};
            $.each(apiByGroupAndName[group][name], function(index, entry) {
                if (entry.version === version)
                    sourceEntry = entry;
                if (entry.version === selectedVersion)
                    compareEntry = entry;
            });

            var fields = {
                article: sourceEntry,
                compare: compareEntry,
                versions: articleVersions[group][name]
            };

            // add unique id
            // TODO: replace all group-name-version in template with id.
            fields.article.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
            fields.article.id = fields.article.id.replace(/\./g, '_');

            fields.compare.id = fields.compare.group + '-' + fields.compare.name + '-' + fields.compare.version;
            fields.compare.id = fields.compare.id.replace(/\./g, '_');

            var entry = sourceEntry;
            if (entry.parameter && entry.parameter.fields)
                fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);

            if (entry.error && entry.error.fields)
                fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);

            if (entry.success && entry.success.fields)
                fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);

            if (entry.info && entry.info.fields)
                fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);

            var entry = compareEntry;
            if (fields._hasTypeInParameterFields !== true && entry.parameter && entry.parameter.fields)
                fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);

            if (fields._hasTypeInErrorFields !== true && entry.error && entry.error.fields)
                fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);

            if (fields._hasTypeInSuccessFields !== true && entry.success && entry.success.fields)
                fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);

            if (fields._hasTypeInInfoFields !== true && entry.info && entry.info.fields)
                fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);

            var content = templateCompareArticle(fields);
            $root.after(content);
            var $content = $root.next();

            // Event on.click re-assign
            $content.find('.versions li.version a').on('click', changeVersionCompareTo);

            // select navigation
            $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + currentVersion + '\']').addClass('has-modifications');

            $root.remove();
            // TODO: on change main version or select the highest version re-render
        }

        initDynamic();
    }

    /**
     * Compare all currently selected Versions with their predecessor.
     */
    function changeAllVersionCompareTo(e) {
        e.preventDefault();
        $('article:visible .versions').each(function(){
            var $root = $(this).parents('article');
            var currentVersion = $root.data('version');
            var $foundElement = null;
            $(this).find('li.version a').each(function() {
                var selectVersion = $(this).html();
                if (selectVersion < currentVersion && ! $foundElement)
                    $foundElement = $(this);
            });

            if($foundElement)
                $foundElement.trigger('click');
        });
        initDynamic();
    }

    /**
     * Add article settings.
     */
    function addArticleSettings(fields, entry) {
        // add unique id
        // TODO: replace all group-name-version in template with id.
        fields.id = fields.article.group + '-' + fields.article.name + '-' + fields.article.version;
        fields.id = fields.id.replace(/\./g, '_');

        if (entry.header && entry.header.fields)
            fields._hasTypeInHeaderFields = _hasTypeInFields(entry.header.fields);

        if (entry.parameter && entry.parameter.fields)
            fields._hasTypeInParameterFields = _hasTypeInFields(entry.parameter.fields);

        if (entry.error && entry.error.fields)
            fields._hasTypeInErrorFields = _hasTypeInFields(entry.error.fields);

        if (entry.success && entry.success.fields)
            fields._hasTypeInSuccessFields = _hasTypeInFields(entry.success.fields);

        if (entry.info && entry.info.fields)
            fields._hasTypeInInfoFields = _hasTypeInFields(entry.info.fields);

        // add template settings
        fields.template = apiProject.template;
    }

    /**
     * Render Article.
     */
    function renderArticle(group, name, version) {
        var entry = {};
        $.each(apiByGroupAndName[group][name], function(index, currentEntry) {
            if (currentEntry.version === version)
                entry = currentEntry;
        });
        var fields = {
            article: entry,
            versions: articleVersions[group][name]
        };

        addArticleSettings(fields, entry);

        return templateArticle(fields);
    }

    /**
     * Render original Article and remove the current visible Article.
     */
    function resetArticle(group, name, version) {
        var $root = $('article[data-group=\'' + group + '\'][data-name=\'' + name + '\']:visible');
        var content = renderArticle(group, name, version);

        $root.after(content);
        var $content = $root.next();

        // Event on.click muss neu zugewiesen werden (sollte eigentlich mit on automatisch funktionieren... sollte)
        $content.find('.versions li.version a').on('click', changeVersionCompareTo);

        $('#sidenav li[data-group=\'' + group + '\'][data-name=\'' + name + '\'][data-version=\'' + version + '\']').removeClass('has-modifications');

        $root.remove();
        return;
    }

    /**
     * Load google fonts.
     */
    function loadGoogleFontCss() {
        var host = document.location.hostname.toLowerCase();
        var protocol = document.location.protocol.toLowerCase();
        var googleCss = '//fonts.googleapis.com/css?family=Source+Code+Pro|Source+Sans+Pro:400,600,700';
        if (host == 'localhost' || !host.length || protocol === 'file:')
            googleCss = 'http:' + googleCss;

        $('<link/>', {
            rel: 'stylesheet',
            type: 'text/css',
            href: googleCss
        }).appendTo('head');
    }

    /**
     * Return ordered entries by custom order and append not defined entries to the end.
     * @param  {String[]} elements
     * @param  {String[]} order
     * @param  {String}   splitBy
     * @return {String[]} Custom ordered list.
     */
    function sortByOrder(elements, order, splitBy) {
        var results = [];
        order.forEach (function(name) {
            if (splitBy)
                elements.forEach (function(element) {
                    var parts = element.split(splitBy);
                    var key = parts[1]; // reference keep for sorting
                    if (key == name)
                        results.push(element);
                });
            else
                elements.forEach (function(key) {
                    if (key == name)
                        results.push(name);
                });
        });
        // Append all other entries that ar not defined in order
        elements.forEach(function(element) {
            if (results.indexOf(element) === -1)
                results.push(element);
        });
        return results;
    }

});


/**
 * _github.js - Provides github-tasks/functions used by multi tasks
 */

module.exports = function (grunt) {

    var common   = grunt.fcoo.common,
        options  = grunt.fcoo.options,
        paths    = grunt.fcoo.paths,
        _console = grunt.fcoo._console,
        semver   = require('semver');

    //**************************************************
    //writeGithubActionList: write all selected action
    //**************************************************
    function writeGithubActionList(){
        var newVersion,
            tagMessage;

        _console.writelnYellow('**************************************************');
        _console.writelnYellow('ACTIONS:');

        if (grunt.config('build'))
            _console.writelnYellow('- Build/compile the '+(options.isApplication ? 'application' : 'packages'));

        if (grunt.config('newVersion') != 'none') {
            newVersion = semver.inc(options.currentVersion, grunt.config('newVersion'));
            _console.writelnYellow('- Bump \'version: "'+newVersion+'"\' to bower.json and package.json');
        }

        if (grunt.config('commit') == 'commit')
            _console.writelnYellow('- Commit staged changes to a new snapshot. Message="'+grunt.config('commitMessage')+'"');
        else
            _console.writelnYellow('- Amend/combine staged changes with the previous commit');

        if (grunt.config('newVersion') != 'none'){
            tagMessage = grunt.config('tagMessage');
            _console.writelnYellow('- Create new tag="'+newVersion+(tagMessage ? ': '+tagMessage : '') + '"');
        }

        if (options.haveGhPages)
            _console.writelnYellow('- Merge "master" branch into "gh-pages" branch');
        else
            grunt.config.set('release.options.afterRelease', []); //Remove all git merge commands

        if (grunt.config('newVersion') == 'none')
            _console.writelnYellow('- Push all branches to GitHub');
        else
            _console.writelnYellow('- Push all branches and tags to GitHub');
        _console.writelnYellow('**************************************************');
    };

    //*******************************************************
    //runGithubTasks: Run all the needed github-commands
    //*******************************************************
    function runGithubTasks(){
        function writeHeader(header){
            grunt.log.writeln('');
            _console.writelnYellow('**************************************************');
            _console.writelnYellow(header.toUpperCase());
        }

        if (!grunt.config('continue'))
            return 0;

        //Get new version and commit ang tag messages
        var newVersion    = grunt.config('newVersion') == 'none' ? '' : semver.inc(options.currentVersion, grunt.config('newVersion')),
            commitMessage = grunt.config('commitMessage') || 'No message',
            tagMessage    = grunt.config('tagMessage') || '',
            userName;

        if (newVersion){
            //Create tagMessage
            userName   = grunt.config('gitinfo').userName || grunt.fcoo.bowerJson.authors; //grunt.fcoo.bowerJson.authors is fall-back
            tagMessage = ' -m "Version '  + newVersion + '"'  +
                         ' -m "Released '+ grunt.fcoo.todayStr +' by '+ userName +' (https://github.com/orgs/FCOO/people)"' +
                         (tagMessage ? ' -m "' + tagMessage + '"' : '');

            //Update grunt.fcoo.bowerJson
            grunt.fcoo.bowerJson.version = newVersion;
        }

        //Build application/packages
        if (grunt.config('build')){
            writeHeader('Build/compile the '+(options.isApplication ? 'application' : 'packages'));
            common.runCmd('grunt build', true);
        }

        //Bump bower.json and packages.json
        if (newVersion){
            writeHeader('Bump \'version: "'+newVersion+'"\' to bower.json and package.json');
            var files = ['bower.json', 'package.json'], file, json;
            for (var i=0; i<files.length; i++ ){
                file = files[i];
                json = grunt.file.readJSON(file);
                json.version = newVersion;
                grunt.file.write(file, JSON.stringify(json, null, '  ') + '\n');
                grunt.log.writeln(file+'-OK');
            }

            //Replace {VERSION] with newVersion in all js in dist
            if (options.isPackage){
                common.runCmd('grunt replace:Dist_js_version');
            }

        }

        //git add all
        common.runCmd('git add -A');

        //commit or amend
        if (grunt.config('commit') == 'commit'){
            //commit
            writeHeader('Commit staged changes to a new snapshot');
            common.runCmd('git commit  -m "' + commitMessage + '"');
        }
        else {
            writeHeader('Combine/amend staged changes with the previous commit');
            common.runCmd('git commit --amend --no-edit');
        }

        //git tag
        if (newVersion){
            writeHeader('Create new tag="'+newVersion+'"');
            common.runCmd('git tag ' + newVersion + tagMessage);
        }

        //git push (and push tag)
        writeHeader('Push all branches '+(newVersion ? 'and tags ' : '')+'to GitHub');

        common.runCmd('git push "origin" HEAD');

        if (newVersion)
            common.runCmd('git push "origin" ' + newVersion);

        //Merge "master" into "gh-pages"
        if (options.haveGhPages){
            writeHeader('Merge "master" branch into "gh-pages" branch');
            common.runCmd('git checkout -B "gh-pages"');
            common.runCmd('git merge master');
            common.runCmd('git checkout master');
            common.runCmd('git push "origin" gh-pages');
        }
    };

    return {
        writeGithubActionList: writeGithubActionList,
        runGithubTasks       : runGithubTasks
    };
}


'use strict'

/* native modules */
const assert = require('assert')

/* npm modules */
const _ = require('lodash')

/* exports */
module.exports = MergeArgs

// default configuration options
const defaultConfig = {
    // concat arrays instead of replacing
    concatArrays: true,
    // convert empty strings to undefined
    emptyStringUndefined: true,
}

var mergeConfig = MergeArgs()

/**
 * @function MergeArgs
 *
 * @param {object} args
 *
 * @returns {function}
 *
 * @throws {Error}
 */
function MergeArgs (args) {
    // clone local config from default
    var config = _.cloneDeep(defaultConfig)
    // if config passed then merge config
    if (args !== undefined) {
        mergeConfig(config, args)
    }

    // create merge function

    /**
     * @funtion mergeArgs
     *
     * merge args into target object using options specified by config
     *
     * @param {object} target
     * @param {object} args
     *
     * @throws {Error}
     */
    var mergeArgs = function mergeArgs (target, args) {
        // require target object
        assert.ok(target && typeof target === 'object', 'target must be object')
        // if args is undefined then nothing to do
        if (args === undefined) {
            return
        }
        // require args to be object
        assert.ok(args && typeof args === 'object', 'args must be object')
        // check it target is empty
        var targetIsEmpty = _.keys(target).length === 0 ? true : false
        // iterate over args properties
        _.each(args, (val, key) => {
            // only validate against target if target has properties
            if (!targetIsEmpty) {
                // require key to exist in target
                assert.ok(target.hasOwnProperty(key), 'invalid argument '+key)
                // if target property has type then type must match
                if (target[key] !== undefined) {
                    assert.ok(typeof target[key] === typeof args[key], 'invalid type '+typeof args[key]+' for '+key+' '+typeof target[key]+' required')
                }
            }
            // if property is an array then either concat or replace
            if (Array.isArray(target[key])) {
                // concat arrays
                if (config.concatArrays) {
                    target[key] = target[key].concat(args[key])
                }
                // otherwise replace
                else {
                    target[key] = args[key]
                }
            }
            // if property is an object then do recursive merge
            else if (typeof target[key] === 'object') {
                mergeArgs(target[key], args[key])
            }
            // all other values overwrite
            else {
                target[key] = args[key]
            }
        })
        // convert empty strings to undefined
        if (config.emptyStringUndefined) {
            _.each(target, (val, key) => {
                if (typeof val === 'string' && val.length === 0) {
                    target[key] = undefined
                }
            })
        }
    }

    // return function
    return mergeArgs
}


/**
 * create_logfiles.js - Create all log-files for task build
 */

module.exports = function (grunt) {

    var common   = grunt.fcoo.common,
        options  = grunt.fcoo.options,
        paths    = grunt.fcoo.paths,
        LogFile  = grunt.fcoo.LogFile,
        bower    = grunt.fcoo.bower,
        taskList = [];

    //********************************************************************************
    //0: Check APPLICATIONNAME_TIMPSTAMP.css against the caniuse.com DB, and...
    //********************************************************************************
    taskList.push( 'postcss:caniuse' );

    //********************************************************************************
    //1: write the result to dist/log/caniuse.md and log/caniuse.txt
    //********************************************************************************
    taskList.push( function(){
        var logFile = new LogFile();;

        logFile
            .write('Check of Stylesheet using caniuse.com', '#')
            .write('File: `' + paths.dist + paths.APPLICATIONNAME_TIMPSTAMP_css + '`', '####' )
            .write('Time: `' + grunt.fcoo.todayStr + '`', '####' );

        var issues = 0;
        for (var featureId in grunt.fcoo.caniuseFeatures)
            issues += grunt.fcoo.caniuseFeatures[featureId].length;
        logFile.write('Issues: `' + issues + '`', '####' );

        for (var featureId in grunt.fcoo.caniuseFeatures){
            var featureList = grunt.fcoo.caniuseFeatures[featureId],
                feature = featureList[0],
                featureData = feature.featureData,
                caniuseData = featureData.caniuseData;

            logFile
                .write('', '----------', true)
                .write(caniuseData.title+' (`'+featureId+'`)', '###')
                .write('Description','####')
                .write(caniuseData.description )
                .write(caniuseData.spec );

            if (featureData.missing)
                logFile
                    .write('Not supported by','####')
                    .write(featureData.missing );

            if (featureData.partial)
                logFile
                    .write('Partial supported by','####')
                    .write(featureData.partial );

            if (caniuseData.notes || caniuseData.notes_by_num){
                logFile.write('Notes','####');
                if (caniuseData.notes)
                    logFile.write(caniuseData.notes, '-');
                if (caniuseData.notes_by_num)
                    for (var note in caniuseData.notes_by_num)
                        logFile.write(caniuseData.notes_by_num[note], '-');
                logFile.write('');
            }

            logFile.write('Line / Column','####');
            for (var i=0; i<featureList.length; i++ ){
                feature = featureList[i];
                featureData = feature.featureData;
                logFile.write('`'+feature.usage.source.start.line+' / '+feature.usage.source.start.column+'`', '-');
            }

        }

        logFile.writeTxtFile( paths.dist_log + 'caniuse.txt' );
        logFile.writeMdFile( paths.dist_log + 'caniuse.md' );
    });

    //********************************************************************************
    //2: Run "bower list" and save output in dist/log/bower_list.txt
    //********************************************************************************
    taskList.push( function(){
        common.runCmd('bower list >'+paths.dist_log+'bower_list.txt', true)
    });

    //********************************************************************************
    //3:Create list of all packages used by the application in dist/log/packages.md and dist/log/packages.txt
    //********************************************************************************
    function _addPackage( pname, bowerJson, depOptions, firstLevel, dotBowerJson ){
        if (!firstLevel)
            depOptions.list.push({
                name    : bowerJson.name     || dotBowerJson.name     || pname,
                homepage: bowerJson.homepage || dotBowerJson.homepage || '',
                version : bowerJson.version  || dotBowerJson.version  || ''
            });
    };

    taskList.push( function(){
        var depOptions = {list:[]};
        bower.eachDependencies( _addPackage, depOptions);

        depOptions.list.sort(function(a, b){
            var aName = a.name.toLowerCase(),
                bName = b.name.toLowerCase();
            if (aName < bName) return -1;
            if (aName > bName) return 1;
            return 0;
        });

        var logFile = new LogFile();
        logFile
            .write('Packages used by application', '#')
            .write('Application: `' + grunt.fcoo.bowerJson.name  + '`', '####' )
            .write('Title: `' + options.title  + '`', '####' )
            .write('Version: `' + grunt.fcoo.bowerJson.version + '`', '####' )
            .write('Time: ' + grunt.fcoo.todayStr, '####' )
            .writeTxt('*** Packages ***')
            .writeMd('## Packages');

        for (var i=0; i<depOptions.list.length; i++ ){
            logFile
                .writeMd('#### ['+depOptions.list[i].name+']('+depOptions.list[i].homepage+') - Version ' + depOptions.list[i].version )
                .writeTxt(depOptions.list[i].name + ':' )
                .writeTxt('    Version : '+depOptions.list[i].version )
                .writeTxt('    Homepage: ' + depOptions.list[i].homepage )
                .writeTxt('');
        }
        logFile
            .writeTxt('*** Debugging ***')
            .writeMd('## Debugging')
            .write('To debug this exact version you need to:')
            .write('Clone the application from Github', '1.', true)
            .write('Copy the file dist/log/bower_debug.json to /bower.json' , '2.')
            .write('Build the application using `>grunt dev`', '3.')

            .writeTxtFile( paths.dist_log + 'packages.txt' )
            .writeMdFile( paths.dist_log + 'packages.md' );

        //Create bower_debug.json = a copy of bower.json with "dependencies" and "resolutions" set excatly as the current version
        grunt.fcoo.bowerDebugJson.dependencies = {};
        grunt.fcoo.bowerDebugJson.resolutions = {};

        for (var i=0; i<depOptions.list.length; i++ ){
            var id = depOptions.list[i].name, version = depOptions.list[i].version;
            grunt.fcoo.bowerDebugJson.dependencies[id] = version;
            grunt.fcoo.bowerDebugJson.resolutions[id] = version;
        }

        //Write debug-version of bower.json to dist/log/bower_debug.json
        common.writeJSONFile( paths.dist_log + 'bower_debug.json', grunt.fcoo.bowerDebugJson );

    });

    return taskList;
}


(function (document) {

  var NO_ENDING_TAG = ['br', 'col', 'link', 'hr', 'command',
        'embed', 'img', 'input', 'meta', 'param', 'source'];

  /** Main document constructor
   */
  function Document(document) {
    this.tree = document;

    this.elemCache = [];
    this.nodeCache = [];
  }

  // export constructor function
  var enterpoint = function (doc) { return new Document(doc); };
  enterpoint.Document = Document;
  enterpoint.Search = Search;
  enterpoint.Node = Node,
  enterpoint.NO_ENDING_TAG;

  if (typeof module === 'object' && module !== null && module.exports === exports) {
    module.exports = enterpoint;
  } else {
    this.domstream = enterpoint;
  }

  // bind with search constructor
  Document.prototype.find = function () {
    return new Search(this, this.tree);
  };

  Document.prototype.container = function () {
    return this;
  };

  /** Search constructor
   */
  function Search(document, tree) {
    this.document = document;
    this.tree = tree;

    this.onlyFlag = false;
    this.onlySearch = false;

    this.searchFilled = false;
    this.searchList = {
      'id': null,
      'tag': null,
      'attr': []
    };
    this.nodeList = [];
  }

  Search.prototype._prepearMatch = function () {
    // throw if more search requests are made
    if (this.onlySearch) {
      throw new Error('can not add search criterias after .toArray or .toValue was called with .only');
    }
  };

  Search.prototype.elem = function (tagname) {
    this._prepearMatch();
    this.searchFilled = true;

    var searchList = this.searchList;

    // set tagname
    if (searchList.tag === null) {
      searchList.tag = tagname;
    }

    // An element can only have one tagname
    // using diffrent tagnames should return nothing
    else if (searchList.tag !== tagname) {
      searchList.tag = false;
    }

    return this;
  };

  Search.prototype.attr = function (name, match) {
    this._prepearMatch();
    this.searchFilled = true;

    var searchList = this.searchList;
    var query = searchList.attr;

    if (typeof name !== 'string') {
      throw new Error('Could not understand arguments');
    } else if (match === undefined) {
      query.push(function (elem) {
        return elem.hasAttribute(name);
      });
    } else if (typeof match === 'string') {

      if (name !== 'id') {
        query.push(function (elem) {
          return elem.getAttribute(name) === match;
        });
      }

      // set id
      else if (searchList.id === null) {
        searchList.id = match;
      }

      // An element can only have one id
      // using diffrent ids should return nothing
      else if (searchList.id !== match) {
        searchList.id = false;
      }

    } else if (match instanceof RegExp) {
      query.push(function (elem) {
        return elem.hasAttribute(name) && match.test( elem.getAttribute(name) );
      });
    } else {
      throw new Error('Could not understand arguments');
    }

    return this;
  };

  Search.prototype.only = function () {
    this.onlyFlag = true;
    return this;
  };

  Search.prototype.toArray = function () {
    if (this.searchFilled) {
      this.nodeList = performSearch(this);
      this.filled = true;
      this.searchFilled = false;
      this.searchList = {
        'id': null,
        'tag': null,
        'attr': []
      };

      // throw if more search requests are made
      if (this.onlyFlag) {
        this.onlySearch = true;
      }
    }

    // convert result to real nodes
    var realNodes = this.onlyFlag ? this.nodeList.slice(0, 1) : this.nodeList.slice(0);

    var map = [];
    for (var i = 0, l = realNodes.length; i < l; i++) {
      map.push(Node.create(this.document, realNodes[i]));
    }

    return map;
  };

  Search.prototype.toValue = function () {
    var result = this.toArray();

    if (result.length === 0) {
      return false;
    } else if (this.onlyFlag) {
      return result.shift();
    } else {
      return result;
    }
  };

  function performSearch(search) {
    var searchList = search.searchList;
    var only = search.onlyFlag;
    var temp = null;

    if (searchList.id === false || searchList.tag === false) return [];

    if (search.filled) {
      return listSearch(search.nodeList, only, search.searchList);
    }

    // search by id
    if (searchList.id) {
      temp = search.tree.getElementById(searchList.id);
      if (temp === null) return [];

      return listSearch([temp], only, {
        id: null,
        tag: searchList.tag,
        attr: searchList.attr
      });
    }

    // search by tagname
    if (searchList.tag) {
      temp = search.tree.getElementsByTagName(searchList.tag);

      return listSearch(temp, only, {
        id: null,
        tag: null,
        attr: searchList.attr
      });
    }

    // search tree
    temp = [];
    searchChildrens(searchList.attr, search.tree, temp, search.onlyFlag);
    return temp;
  }

  function listSearch(list, only, search) {
    var query = search.attr.slice(0);

    // Add id search query
    if (search.id) {
      query.unshift(function (elem) {
        return elem.getAttribute('id', search.id);
      });
    }

    // Add tagname search query
    if (search.tag) {
      query.unshift(function (elem) {
        return elem.tagName.toLowerCase() === search.tag;
      });
    }

    // perform search
    var result = [];
    for (var i = 0, l = list.length; i < l; i++) {
      if (doPass(query, list[i])) {
        result.push(list[i]);
        if (only) return result;
      }
    }

    return result;
  }

  function searchChildrens(query, elem, result, only) {
    var next = elem.firstChild;
    while (next !== null) {
      // we only want elements
      if (next.nodeType === 1) {

        // add elements there match to result list
        if (doPass(query, next)) {
          result.push(next);
          if (only) return true;
        }

        // deep resolve children
        var stop = searchChildrens(query, next, result, only);
        if (stop) return true;

      }

      next = next.nextSibling;
    }

    return false;
  }

  function doPass(query, elem) {
    var i = 0, l = query.length;

    // search in normal order, since its likly that will be the optimised order
    // example: .elem('tag').attr('value', /hallo/) would be a shame to do backwards
    for (; i < l; i++) {
      if (query[i](elem) === false) {
        return false;
      }
    }

    return true;
  }

  /** Node constructor
   */

  function Node(document, elem) {
    this.document = document;
    this.elem = elem;

    // precalculate these, since they in any case will be used
    this._isRoot = elem.nodeType === 9;

    if (this._isRoot === false) {
      this._tagName = elem.tagName.toLowerCase();
      this._isSingleton = NO_ENDING_TAG.indexOf(this._tagName) !== -1;
    } else {
      this._tagName = '';
      this._isSingleton = false;
    }

    this.isChunked = false;
    this.isContainer = false;

    // allow node object to be reused
    document.elemCache.push(elem);
    document.nodeCache.push(this);
  }

  // this function will check the cache and
  // return a new Node object if it don't exist
  // in cache, or return the cached Node object.
  Node.create = function (document, elem) {
    // check cache
    var index = document.elemCache.indexOf(elem);

    // not cached, create new
    if (index === -1) {
      return new Node(document, elem);
    }

    // return cache
    return document.nodeCache[index];
  };

  Node.prototype.tagName = function () {
    if (this._isRoot) throw new Error('root has no tagname');

    return this._tagName;
  };

  Node.prototype.isSingleton = function () {
    return this._isSingleton;
  };

  Node.prototype.isRoot = function () {
    return this._isRoot;
  };

  Node.prototype.getParent = function () {
    if (this._isRoot) throw new Error('root has no parent');

    return Node.create(this.document, this.elem.parentNode);
  };

  Node.prototype.getChildren = function () {
    if (this._isSingleton) throw new Error('singleton element has no children');

    var next = this.elem.firstChild;
    var nodes = [];
    while (next !== null) {
      if (next.nodeType === 1) {
        nodes.push(Node.create(this.document, next));
      }

      next = next.nextSibling;
    }

    return nodes;
  };

  Node.prototype.isParentTo = function (node) {
    var parent = node.elem.parentNode;

    return !!parent && this.elem === parent;
  };

  Node.prototype.done = function () {
    return this;
  };

  Node.prototype.insert = function (where, content) {
    var elem = this.elem;

    switch (where) {
      case 'beforebegin':
      case 'afterend':
        if (this._isRoot) throw new Error('can not insert content at beforebegin on root element');
        break;

      case 'afterbegin':
      case 'beforeend':
        if (this._isSingleton) throw new Error('can not insert content intro singleton element');
        break;

      default:
        throw new Error('did not understand first argument');
    }

    if (elem.insertAdjacentHTML) {
      elem.insertAdjacentHTML(where, content);
      return this;
    }

    // use createContextualFragment as fallback
    var fragment = document.createRange().createContextualFragment(content);

    switch (where) {
      case "beforebegin":
        elem.parentNode.insertBefore(fragment, elem);
        break;
      case "afterbegin":
        elem.insertBefore(fragment, elem.firstChild);
        break;
      case "beforeend":
        elem.appendChild(fragment);
        break;
      case "afterend":
        elem.parentNode.insertBefore(fragment, elem.nextSibling);
        break;
    }

    return this;
  };

  Node.prototype.append = function (content) {
    this.insert('beforeend', content);

    return this;
  };

  Node.prototype.remove = function () {
    if (this._isRoot) throw new Error('can not remove root element element');

    this.elem.parentNode.removeChild(this.elem);

    return this;
  };

  Node.prototype.trim = function () {
    if (this._isSingleton) throw new Error('can not remove content from singleton element');

    var elem = this.elem;
    while(elem.firstChild) {
      elem.removeChild(elem.firstChild);
    }

    return this;
  };

  Node.prototype.getContent = function () {
    if (this._isSingleton) throw new Error('can not get content from singleton element');

    return this.elem.innerHTML;
  };

  Node.prototype.setContent = function (content) {
    this.trim();
    this.insert('afterbegin', content);
    return this;
  };

  Node.prototype.getAttr = function (name) {
    return this.elem.getAttribute(name);
  };

  Node.prototype.hasAttr = function (name) {
    return this.elem.hasAttribute(name);
  };

  Node.prototype.setAttr = function (name, value) {
    if (this._isRoot) throw new Error('can not set attribute on root element');

    this.elem.setAttribute(name, value);
    return this;
  };

  Node.prototype.removeAttr = function (name) {
    if (this._isRoot) throw new Error('can not remove attribute from root element');

    this.elem.removeAttribute(name);
    return this;
  };

  Node.prototype.find = function () {
    return new Search(this.document, this.elem);
  };

})(this.document);


// qb/index.js
// qb is a quarterback for your soul

// builtin
var util = require('util')

// vendor
var async = require('async')
  , uuid = require('uuid')
  , book = require('book')
  , defaults = require('defaults')
  , once = require('once')

// local
var MiddlewareProvider = require('./middleware_provider')
  , handleSigterm = require('./handle_sigterm')

var default_options = {
  aliases: {}
, catch_sigterm_end: true // Catch sigterm and call qb.end()
, end_timeout: 10000 // Timeout on an end() call
, name: '<unnamed>' // For logging and identification. Set to `qb.name`
}

// -- Exports --
// =============

module.exports = QB;

// -- Main Type and Public API --
/*
* QB - The primary type of the qb package
*
* @param options - See README.md for options documentation
*/
function QB (options) {
  if (!(this instanceof QB)) {
    return new QB(options);
  }

  // Super constructor
  MiddlewareProvider.call(this);

  var qb = this;

  // Init
  qb._options = defaults(options, default_options)
  qb._types = {}
  qb._aliases = {}
  qb.name = qb._options.name

  // TODO process this into .alias
  _processAliasOptions(qb, qb._options.aliases)

  qb.log = book.default();

  if (qb._options.catch_sigterm_end)
    handleSigterm(qb);

  _listen(qb)
}

util.inherits(QB, MiddlewareProvider);

/*
* Include a component in this qb system.
* This is used to provide backend queue functionality as well as push dialects
*
* @param component Component
*/
QB.prototype.component = function (component, config) {
  component(this, config)
  return this
}

/*
* Enable a type to process
*
* @param type string
* @param callback function(task, $done) - Task processor function
*/
QB.prototype.can = function can(type, callback) {
  var qb = this;

  if (qb._types[type]) {
    return
  }

  qb._types[type] = callback;

  qb.emit('process-type', type, function (err) {
    if (err) {
      qb.emit('error', err, errcallback(qb))
    } else {
      qb.emit('process-ready', type, errcallback(qb))
    }
  })

  return qb;
}

/*
* Alias a type for a location.
* This allows remote pushes via different protocols to be switched out using config
* Note that options.aliases is used as well
*
* @param alias string type or name that can be called in .push(alias, task)
* @param location actual protocol://location to push the task to
*/
QB.prototype.alias = function (alias, location) {
  var loclist = this._aliases[alias] = this._aliases[alias] || []
  loclist.push(location)
  return this
}

/*
* Start the processing pipeline for a task.
* This immediately starts processing the task. If you meant to push into a queue, see .push
*
* @param type string
* @param task object
* @param @optional callback function (err) - Avoid the thrown error by passing a callback
*   If callback doesn't exist, will throw if an error occurs.
*/
QB.prototype.process = function (type, task, callback) {
  var qb = this
  if (!qb._types[type])
    return errcallback(qb, callback)(new Error('no ability to process task of type ' + type))

  return qb.emit('process', type, task, errcallback(qb, callback))
}

/*
* Push a task of some type onto our own queue.
*
* @param type string of the type to push if its aliased or a protocol://location
* @param task object
* @param @optional callback function (err) - Avoid the thrown error by passing a callback
*   If callback doesn't exist, will throw if an error occurs.
*/
QB.prototype.push = function push(type, task, callback) {
  var qb = this
    , locations = qb._aliases[type] || [type]

  task._type = type

  async.each(locations, function (location, cb) {
    qb.emit('push', location, task, cb)
  }, errcallback(qb, callback))

  return qb
}

/*
* Gracefully end the QB System
*
* @param @optional callback function (err, caller) - .end(f) is equivalent to .on('end', f).end()
*/
QB.prototype.end = function end(callback) {
  var qb = this;
  if (qb._ended) {
    return callback && callback();
  }
  qb._ended = true;
  qb.log.info('Ending QB %s', qb.name);

  if (callback) {
    qb.once('end', function (next) { callback(); next() });
  }

  _end(qb, function (err) {
    if (err) {
      qb.emit('error', err);
    }
    qb.log.info('All tasks finished. QB %s Ended.', qb.name);
    qb.emit('end', errcallback(qb));
  });

  return qb
}

// -- Helpers --

function _listen(qb) {
  qb.on('error', function (err, next) {
    qb.log.error(err)
    next()
  })

  qb.on('process', function (type, task, next) {
    try {
      qb._types[type](task, callback);
    } catch (err) {
      callback(err);
    }

    function callback(err) {
      next(err) // We don't pass errors through because that is not for "task failure" errors
      if (err) {
        qb.emit('fail', err, type, task, errcallback(qb));
      } else {
        qb.emit('finish', type, task, errcallback(qb));
      }
    }
  });
}

function _end(qb, callback) {
  var called = false
    , timeout = 0
    , cb = once(function (err) {
        callback(err)
        clearTimeout(timeout)
      })

  qb.emit('component-end', cb)

  timeout = setTimeout(function () {
    cb(new Error('TIMEOUT on End'))
  }, qb._options.end_timeout)
}

function errcallback(qb, cb) {
  return function (err) {
    if (err && !cb)
      qb.emit('error', err)
    if (cb) cb(err)
  }
}

function _processAliasOptions(qb, aliases) {
  Object.keys(aliases).forEach(function (alias) {
    var locations = Array.isArray(aliases[alias]) ? aliases[alias] : [aliases[alias]]
    locations.forEach(function (loc) {
      qb.alias(alias, loc)
    })
  })
}


// Core dependencies
// -----------------
// [url](http://nodejs.org/api/url.html)
var Url  = require('url');

// MatchedRoute
// ============
// constructor
// -----------
// create a matched route that can construct a path
// using the matched params and route pattern
// ### required arguments
// **name**    : name for this route
//
// **params**  : params for this route
//
// **pattern** : pattern for this route
function MatchedRoute (args) {
  this.name    = args.name;
  this.params  = args.params;
  this.pattern = args.pattern;
}

// path
// ----
// return the path for this route by substituting
// params for their occurence in this route's pattern
MatchedRoute.prototype.path = function path () {
  var pattern = this.pattern;

  // clone params for building the path non-destructively
  // since it modifies the params
  var params = {};
  for (var key in this.params) {
    if (this.params.hasOwnProperty(key)) {
      params[key] = this.params[key];
    }
  }

  function _substitute (str, k) {
    var placeholder = ':' + k;
    var value = params[k];
    str = str.replace(placeholder, value);
    delete params[k];
    return str;
  }

  // build the path part
  var path = pattern.pathPattern.routeString;
  var path_param_names = pattern.pathPattern.params;
  if (path_param_names.length) {
    path_param_names.forEach(function (k) {
      path = _substitute(path, k);
    });
  }

  // build the hash part
  var hash = null;
  if (pattern.hashPattern) {
    hash_param_names = pattern.hashPattern.params;
    hash = pattern.hashPattern.routeString;
    hash_param_names.forEach(function (k) {
      hash = _substitute(hash, k);
    });
  }

  // query string is built from any remaining parameters
  var url_params = {
    pathname : path,
    query    : params
  }

  if (hash) {
    url_params.hash = hash;
  }

  return Url.format(url_params);
}

// notFound
// --------
// Convenience method used by handle in [Router](./Router.html)
MatchedRoute.prototype.notFound = function () {
  return false;
}

module.exports = MatchedRoute;


var _isString = require('./isString');
var _isObject = require('./isObject');

/**
 * 校验参数 `val` 是否为 非空字符串
 *
 * 该接口存在两种模式, 即严格模式、非严格模式;
 *
 * 默认采用 `严格模式`
 *
 * 若参数 `val` 不是 string, 则直接返回 false
 * 若参数 `val` 是 string, 并且不为空，则返回 true, 否则, 返回 false
 *
 * 若是参数 `options` 指定了严格模式, 即 options.isStrict = true,
 * 则会对参数 `val` 进行两端去除空白（空字符），之后在校验其是否为非空字符串
 *
 * e.g.
 *
 * 待校验参数 '   '
 *  > 若是严格模式, 则该参数值校验后, 返回 false
 *  > 若是非严格模式, 则该参数值校验后, 返回 true
 *
 * @param   {*}       val              待校验的参数
 * @param   {Object}  options          可选参数
 * @param   {Boolean} options.isStrict 是否严格模式
 * @return  {Booean} 返回校验结果
 * @version 0.0.7
 * @since   0.0.4
 */
function _isUnEmptyString(val, options) {
  var opts = _isObject(options) ? options : {};

  if (!_isString(val)) {
    return false;
  }

  if (opts.isStrict === false) {
    return val !== '';
  }

  // @TODO
  // 引用: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim
  // String.prototype.trim, 在 ECMAScript 5.1 定义, 在 JavaScript 1.8.1 实现
  // return val.replace(REGEX_ENUM.LEFT_WHITE_SPACE_REX, '').replace(REGEX_ENUM.RIGHT_WHITE_SPACE_REX, '') !== '';
  return val.trim() !== '';
}

module.exports = _isUnEmptyString;


if(typeof process !== "undefined")
	var define = require.define;
define(function(require, exports, module){
//get Noodles, the blank concat is to preven requirejs from getting too smart on us.
var Noodles;
/*--exports--
name:Plugin
description:Plugin Class implementation
@type{Noodles.Plugin}
*/
exports.Plugin = {
	/*--Coretags--
	name:getNoodles
	description:gets the noodles object
	@param{Noodles}
	*/
	getNoodles : function(_Noodles){
		Noodles = _Noodles;
	},
	/*--Coretags--
	name:willHandle
	description:array of tags that the coretags plugin will handle
	@param{Noodles.Template}
	@return {array}
	*/
	willHandle : function(Template){
		return [Template.language.tag('if')];
	},
	/*--Coretags--
	name:pluginName
	@type{string}
	*/
	pluginName : 'iftag',
	/*--Coretags--
	name:browserFriendly
	description:is this plugin browser friendly?
	*/
	browserFriendly: true,
	/*--Coretags--
	name:onTemplateCreate
	description:executed on template creation
	@param{Noodles.Template}
	*/
	onTemplateCreate : function(Template){
		Template.endTags = Template.endTags || {};
		Template.endTags[Template.language.tag('if')] = true;
	},
	/*--Coretags--
	name:onTemplateExecute
	description:executed when template is run
	@param{Noodles.Context}
	@param{Noodles.Template}
	*/
	onTemplateExecute : function(Context, Template){},
	/*--Coretags--
	name:handleToken
	description:executed when any tag in "willHandle" is found
	@param{expression}
	@param{Noodles.Template}
	@param{string}
	*/
	handleToken : function(Template, expression, tag){
		switch(tag){
			case Template.language.tag('if'):
				return new Conditional(Template,expression);
			default:
				return {skip:true};
		}
	}
};

/*--module--
name: Conditional
description: Conditional execution
@param {Noodles.Template}
@param {expression}
*/
var Conditional = function(Template,expression){
	this.rawString = Noodles.Utilities.grabToEndSliceRaw(Template, expression,Template.language.tag('If'));
	this.needs = {};
	this.conditions = [{
		condition:_parseCondtions.call(this,Template,expression,true)
	}];
	_parseConditional.call(this,Template);
}
/*--module--
name: parseConditional - private
description: Parses out the elses and elseifs from the tempalte,
	to put them into the Conditional class for later use.
@param {Noodles.Template}
*/
_parseConditional = function(Template){
	var reElse = new RegExp('<\\{' + Template.language.tag('else') + '(\\s*|\\}|' + Template.language.tag('if') + '\\s+)','i'),
		rawString = this.rawString,
		endTags = Object.keys(Template.endTags),
		currentIndex = 0,
		regExpArray = [],
		reEnd = new RegExp('<\\{' + Template.language.tag('end') + '(\\s|\\}){1}','i'),
		reIf = [new RegExp('<\\{' + Template.language.tag('if') + '\\s+','i')],
		ifs = 0,
		endingTags = [],
		i = endTags.length,
		nextTagThatNeedsEnding, nextEndTag,nextElse,nextIf,temp;

	nextElse = Noodles.Utilities.searchByIndex(rawString,reElse,currentIndex);
	if(nextElse !== -1){

		while(i--){
			if(endTags[i].toLowerCase() ===  Template.language.tag('if')) continue;
			regExpArray.push(new RegExp('<\\{' +  endTags[i] + '\\s{1}','i'));;
		}
		while(nextElse !== -1){
			nextTagThatNeedsEnding = Noodles.Utilities.searchByIndex(rawString,regExpArray,currentIndex);
			nextEndTag = Noodles.Utilities.searchByIndex(rawString,reEnd,currentIndex);
			nextIf = Noodles.Utilities.searchByIndex(rawString,reIf,currentIndex);
			if(nextIf !== -1 && nextIf < nextEndTag && (nextIf < nextElse || nextElse === -1) && (nextIf <= nextTagThatNeedsEnding || nextTagThatNeedsEnding === -1)){
				ifs++;
				endingTags.push('if');
				currentIndex = nextIf + 1;
				nextIf = 0;
			}
			else if(nextTagThatNeedsEnding !== -1 && nextTagThatNeedsEnding < nextEndTag){
				endingTags.push('unknown');
				currentIndex = nextTagThatNeedsEnding + 1;
				nextTagThatNeedsEnding = 0;
			}
			else if(nextEndTag !== -1){
				temp = endingTags.pop();
				currentIndex = nextEndTag + 1;
				if(temp === 'if') ifs--;
			}
			else if(nextTagThatNeedsEnding !== -1){
				this.skip = true;
				Noodles.Utilities.warning(Template,'If tag not ended properly');
				break;
			}
			if(ifs === 0 && nextElse !== -1 && nextElse >= currentIndex){
				temp = this.conditions.length - 1;
				this.conditions[temp].rawString = rawString.slice(0, nextElse);
				this.conditions[temp].template = Noodles.Utilities.createSubTemplate(Template,this.conditions[temp].rawString, this);

				Template._leftCount++;//the else tag we just sliced
				temp = rawString.indexOf('}>',nextElse);
				this.conditions.push({
					condition : _parseCondtions.call(this,Template,rawString.slice(nextElse + 2,temp))
				});
				rawString = rawString.slice(temp + 2);
				currentIndex = 0;
			}
			nextElse = Noodles.Utilities.searchByIndex(rawString,reElse,currentIndex);
		}
	}
	temp = this.conditions.length - 1;
	this.conditions[temp].rawString = rawString;
	this.conditions[temp].template = Noodles.Utilities.createSubTemplate(Template,this.conditions[temp].rawString,this);
	Template._leftCount++;//the end tag that's missing
};
/*--module--
name: parseConditions - private
description: Parses the specifics of the conditional statement
@param {Noodles.Template}
@param {string}
@return {array}
*/
var _parseCondtions = Conditional.parseConditions = function(Template,condition,first){
	var booleanParser = /(?:\s*)(!?)([^<>=!\s]+)(?:\s*)(==|!=|<|>|<=|>=)?(?:\s*)(!?)([^<>=!\s]*)(?:\s*)/,
		conditions = [],
		andStack = [],
		reAnd = new RegExp('\\s+' + Template.language.tag('and') + '\\s+','i'),
		orExpression,andExpression;

	if(new RegExp(Template.language.tag('else') + '(\\s+\\.*|)$','i').test(condition)) return 'else';

	if(!(new RegExp(Template.language.tag('elseif') + '\s+','i').test(condition)) && !first){
		Noodles.Utilities.warning(Template,'Else statement expected');
		return false;
	}
	condition = first ?  /if\s+(.+)/i.exec(condition)[1] : new RegExp(Template.language.tag('elseif') + '\\s+(.+)','i').exec(condition)[1];
	condition = condition.split(new RegExp('\\s+' + Template.language.other('or') + '\\s+','i'));

	for(var i = 0, l = condition.length; i < l; i++){
		orExpression = condition[i].split(reAnd);
		andStack = [];
		for(var i2 = 0, l2 = orExpression.length; i2 < l2; i2++){
			andExpression = booleanParser.exec(orExpression[i2]);
			andStack.push(new Condition(andExpression,this,Template));
		}
		conditions.push(andStack.slice());
	}
	return conditions;
};
/*--Conditional--
name: execute
description: executes the conditionals
@param {Noodles.Template}
@param {Noodles.Context}
@param {function=}
*/
Conditional.prototype.execute = function(Template,Context,Callback){
	var i = 0,
		l = this.conditions.length,
		bool;
	while(i < l){
		if(this.conditions[i].condition === "else"){
			return this.conditions[i].template.execute(Template,Context,Callback);
		}

		bool = this.conditions[i].condition.reduce(function(previous,current){
			if(previous) return true;
			return current.reduce(function(prev,curr){
				if(!prev) return false;
				return curr.execute(Template,Context);
			},true);
		},false);

		if(bool){
			if(this.needsCallback){
				if(this.conditions[i].template.needsCallback){
					this.conditions[i].template.execute(Template,Context,Callback);
				}
				else{
					Callback(this.conditions[i].template.execute(Template,Context));
				}
				break;
			}
			return this.conditions[i].template.execute(Template,Context);
		}
		i++;
	}
};
/*--module--
name: Condition
description: Condition
@param {array}
@param {Conditional}
@param {Noodles.Template}
*/
var Condition = function(condition,Conditional,Template){
	this.leftSide = Noodles.Utilities.parseType(Template,condition[2]);
	Conditional.needs = Noodles.Utilities.mergeObjectWith(Conditional.needs,this.leftSide.needs);
	this.leftNegate = condition[1].length > 0;
	this.super = false;
	if(typeof condition[3] !== "undefined" && condition[5].length > 0){
		switch(condition[3]){
			case Template.language.other('contains'):
				this.expression = 'contains';
				break;
			case Template.language.other('startswith'):
				this.expression = 'startswith';
				break;
			case Template.language.other('endswith'):
				this.expression = 'endswith';
				break;
			case Template.language.other('matches'):
				this.expression = 'matches';
			default:
				this.expression = condition[3].toLowerCase();
		}
		this.rightSide = Noodles.Utilities.parseType(Template,condition[5]);
		this.rightNegate = condition[4].length > 0
		Conditional.needs = Noodles.Utilities.mergeObjectWith(Conditional.needs,this.rightSide.needs);
	}
	else{
		this.expression = false;
	}
};
/*--Condition--
name: execute
description: Condition executable
@param {Noodles.Template}
@param {Noodles.Context}
*/
Condition.prototype.execute = function(Template,Context){
	var left = this.leftNegate ? !this.leftSide.execute(Template,Context) : this.leftSide.execute(Template,Context),
		right;
		left = typeof left === "string" ? left.toLowerCase() : left;
	if(this.expression){
		right = this.rightNegate ? !this.rightSide.execute(Template,Context) : this.rightSide.execute(Template,Context);
		right = typeof right === "string" ? right.toLowerCase() : right;
		switch(this.expression){
			case '==':
				return left == right;
			case '!=':
				return left != right;
			case '<':
				return left < right;
			case '>':
				return left > right;
			case '<=':
				return left <= right;
			case '>=':
				return left >= right;
			case 'contains':
				return typeof left === "string" && typeof right === "string" ? left.indexOf(right) > -1 : false;
			case 'startswith':
				return typeof left === "string" && typeof right === "string" ? new RegExp('^' + right).test(left) : false;
			case 'endswith':
				return typeof left === "string" && typeof right === "string" ? new RegExp(right + '$').test(left) : false;
			case 'matches':
				return typeof left === "string" && right.constructor.name === "RegExp" ? right.test(left) : typeof right === "string" ? new RegExp(right).test(left) : false;
			default:
				return false;
		}
	}
	else{
		return !!left;
	}
};

});


'use strict';

var esprimaTools = require('browserify-esprima-tools'),
    converter    = require('convert-source-map'),
    defaults     = require('lodash.defaults');

var getUpdater = require('./lib/get-updater');

/**
 * Esprima based explicitly @ngInject annotation with sourcemaps.
 * By default, files must be either transformed (as indicated by an embeded source-map) or js
 * extension. To process other files set an explicit opt.filter.
 * @param {object} opt An options hash
 */
function browserifyNgInject(opt) {
  var options = defaults(opt, {
        filter: defaultFilter  // remove files that cannot be parsed by esprima
      }),
      updater = getUpdater(throwError);
  return esprimaTools.createTransform(updater, options);
}

module.exports = browserifyNgInject;

/**
 * A filter that passes files that have a source-map comment or have .js extension.
 * @param {string} filename The full filename of the file being transformed
 * @param {string} content The incoming text content of the file being transformed
 * @returns {boolean} True for included, else False
 */
function defaultFilter(filename, content) {
  return /\.js$/.test(filename) || !!converter.fromSource(content);
}

/**
 * Error function that throws the message.
 * @param message The error to throw
 */
function throwError(message) {
  throw new Error(message);
}


/**
 * angular-sofa-select-box - v0.1.0 - Tue Jan 06 2015 15:52:00 GMT+0100 (CET)
 * http://www.sofa.io
 *
 * Copyright (c) 2014 CouchCommerce GmbH (http://www.couchcommerce.com / http://www.sofa.io) and other contributors
 * THIS SOFTWARE CONTAINS COMPONENTS OF THE SOFA.IO COUCHCOMMERCE SDK (WWW.SOFA.IO)
 * IT IS PROVIDED UNDER THE LICENSE TERMS OF THE ATTACHED LICENSE.TXT.
 */
;(function (angular) {
angular.module('sofa-select-box.tpl.html', []).run(['$templateCache', function($templateCache) {
  $templateCache.put('sofa-select-box.tpl.html',
    '
\n' +
    '    \n' +
    '    \n' +
    '    <select sofa-name="propertyName"\n' +
    '            ng-required="{{required}}"\n' +
    '            class="sofa-select-box__native"\n' +
    '            ng-model="model"\n' +
    '            ng-options="displayFn(val) for val in data">\n' +
    '        <option ng-if="chooseText" value="">-- {{chooseText}} --</option>\n' +
    '    </select>\n' +
    '
');
}]);

angular.module('sofa.selectBox', ['sofa-select-box.tpl.html', 'sofa.name']);

/**
* Creates a mobile friendly select box that delegates to the native picker
*
* Options:
*
*   -   `displayValueExp` optional expression that maps values to display values.
*       Can either be a string (e.g. 'some.nested.property') or a function
*       (e.g. function(value){ return value.some.nested.property; })
*/
angular.module('sofa.selectBox')
    .directive('sofaSelectBox', function() {

        'use strict';

        // a) "ngModel compares by reference, not value. This is important when binding to an array of objects."
        // b) Regardless of data type also check whether the given model exists within the options-data
        var mapModelToData = function (scope) {
            if (scope.model) {
                var modelInData = false;

                for(var i = 0; i < scope.data.length; i++) {
                    if (angular.equals(scope.data[i], scope.model)) {
                        scope.model = scope.data[i];
                        modelInData = true;
                        break;
                    }
                }

                if (!modelInData) {
                    scope.model = null;
                }
            }
            if (!scope.model && !scope.chooseText && scope.data.length) {
                scope.model = scope.data[0];
            }
        };

        return {
            restrict: 'E',
            replace: true,
            scope: {
                model: '=',
                data: '=',
                propertyName: '@',
                required: '=?',
                chooseText: '=?',
                displayValueExp: '&'
            },
            templateUrl: 'sofa-select-box.tpl.html',
            link: function (scope) {

                // Initial run to map any preselected model values
                if (scope.data) {
                    mapModelToData(scope);
                }

                // If by any reason the data object has changed, we have to map any existing model data to the new data
                scope.$watchCollection('data', function (newData, oldData) {
                    if (newData !== oldData) {
                        mapModelToData(scope);
                    }
                });

                var displayValueFormatter = scope.displayValueExp();

                //default display function that will be used if no displayValueExp is given
                scope.displayFn = function (value) {
                    return value;
                };

                if (angular.isFunction(displayValueFormatter)) {
                    scope.displayFn = displayValueFormatter;
                } else if (angular.isString(displayValueFormatter)) {

                    var properties = displayValueFormatter.split('.');

                    scope.displayFn = function (value) {

                        if (!value) {
                            return value;
                        }
                        var tempValue = value;
                        properties.forEach(function (node) {
                            tempValue = tempValue[node];
                        });
                        return tempValue;
                    };
                }
            }
        };
    });
}(angular));


"use strict";

var path = require('path');
var fs = require('fs');
var handlebars = require('handlebars');
var glob = require('glob');

function procOverWrite(overwrite) {
    if (overwrite == 'w' || overwrite == 'r' || overwrite == 'rw') {
        return overwrite;
    }

    return 'w';
}

function makedir(dir) {
    let arr = dir.split(path.sep);
    let cur = '.';
    for (let ii = 0; ii < arr.length; ++ii) {
        cur = path.join(cur, arr[ii]);
        if (!fs.existsSync(cur)) {
            fs.mkdirSync(cur);
        }
    }
}

// {tagname: str}
function getTagInfo(buff) {
    let robj = {};
    let pos = 0;
    let bi = -1;
    let ckey = '';
    let cinfo = '';

    do{
        bi = buff.indexOf('//<--', pos);
        if (bi >= 0) {
            let kbi = buff.indexOf(' Begin', bi);
            if (kbi < 0) {
                return robj;
            }

            ckey = buff.substr(bi, kbi - bi);

            let ei = buff.indexOf('//<--' + ckey + ' End', kbi);
            if (ei < 0) {
                return robj;
            }

            cinfo = buff.substr(kbi + 6, ei - kbi - 6);

            robj[ckey] = cinfo;

            pos = kbi + 5 + ckey.length + 4;
        }
    }while(bi >= 0);

    return robj;
}

function procTagBuff(buff, tagname, str) {
    let dest = '';
    let pos = 0;

    let bi = buff.indexOf('//<--' + tagname + ' Begin', pos);
    if (bi < 0) {
        return buff;
    }

    dest += buff.substr(0, bi + 5 + tagname.length + 6);
    dest += str;

    let ei = buff.indexOf('//<--' + tagname + ' End', bi);
    if (ei < 0) {
        return buff;
    }

    dest += buff.substr(ei, buff.length - ei);

    return dest;
}

function procFile(name, src, params, overwrite) {
    let fobj = path.parse(name);
    let hasfile = false;
    makedir(fobj.dir);

    if (fs.existsSync(name)) {
        hasfile = true;

        if (overwrite == 'r') {
            return ;
        }
    }

    let buf = fs.readFileSync(src, 'utf-8');

    let tmp = handlebars.compile(buf);
    let bufout = tmp(params);

    if (hasfile) {
        if (overwrite == 'rw') {
            return ;
        }

        let destbuf = fs.readFileSync(name, 'utf-8');

        let tagobj = getTagInfo(destbuf);
        for (let key in tagobj) {
            bufout = procTagBuff(bufout, key, tagobj[key]);
        }
    }

    fs.writeFileSync(name, bufout);
}

function procBinFile(name, src, params, overwrite) {
    let fobj = path.parse(name);
    makedir(fobj.dir);

    if (fs.existsSync(name)) {
        if (overwrite == 'r') {
            return ;
        }
    }

    let buf = fs.readFileSync(src, 'binary');

    fs.writeFileSync(name, buf, 'binary');
}

function procJson(params, filename, tmpdir) {
    let bufjson = fs.readFileSync(path.join(filename), 'utf-8');
    let tmpjson = handlebars.compile(bufjson);
    let bufout = tmpjson(params);
    let objout = JSON.parse(bufout);

    let ret = [];
    for (let ii = 0; ii < objout.length; ++ii) {
        let cur = objout[ii];
        if (cur.type == 'dir') {
            ret.push(cur);
        }
        else {
            let srcfilename = path.join(tmpdir, cur.src);
            if (fs.existsSync(srcfilename)) {
                cur.src = srcfilename;
                ret.push(cur);
            }
            else {
                let overwrite = 'w';
                if (cur.hasOwnProperty('overwrite')) {
                    overwrite = procOverWrite(cur.overwrite);
                }

                let arr = glob.sync(srcfilename);
                for (let jj = 0; jj < arr.length; ++jj) {
                    let cc = {name: path.join(path.dirname(cur.name), path.basename(arr[jj])), src: arr[jj], type: cur.type, overwrite: overwrite};
                    ret.push(cc);
                }
            }
        }
    }

    return ret;
}

function procProj(params, jsonfile, tmpdir) {
    let objJson = procJson(params, jsonfile, tmpdir);
    for (let ii = 0; ii < objJson.length; ++ii) {
        if (objJson[ii].hasOwnProperty('jscode')) {
            eval(objJson[ii].jscode);
        }

        let overwrite = 'w';
        if (objJson[ii].hasOwnProperty('overwrite')) {
            overwrite = procOverWrite(objJson[ii].overwrite);
        }

        if (objJson[ii].type == 'dir') {
            makedir(objJson[ii].name);
        }
        else if (objJson[ii].type == 'binfile') {
            procBinFile(objJson[ii].name, objJson[ii].src, params, overwrite);
        }
        else {
            procFile(objJson[ii].name, objJson[ii].src, params, overwrite);
        }
    }
}

exports.procProj = procProj;


var Url = require('url')
var _ = require('underscore')

module.exports = function(ngin) {
  var Model = ngin.NginModel
  var Super = Model.prototype
  var config = ngin.config

  /**
   * Scopes the url to a user, group, or persona
   *
   * @param {Object} options
   * @returns {String}
   * @api private
   */

  function scopeUrl(options, inst) {
    options = _.extend(options || {}, inst)

    if (!options.url && !options.user_id && !options.group_id && !(options.query || options.query.owner_type && options.query.owner_id)) {
      return callback(new Error('user_id or group_id or (owner_type and owner_id) are required'))
    }

    if (options.user_id) {
      return ngin.User.urlRoot() + '/' + options.user_id + '/personas'
    }

    if (options.group_id) {
      return ngin.Group.urlRoot() + '/' + options.group_id + '/personas'
    }

    if (options.url || options.query.owner_type && options.query.owner_id) {
      return Persona.urlRoot()
    }

  }

  /**
   * Persona Class
   *
   * @param {Object} attr
   * @param {Object} options
   * @api public
   */

  var Persona = Model.extend({

    fetch: function(options, callback) {
      var url = Persona.urlRoot() + '/' + this.id
      return Super.fetch.call(this, url, options, callback)
    },

    save: function(options, callback) {
      var url = Persona.urlRoot() + (this.id ? '/' + this.id : '')
      return Super.save.call(this, url, options, callback)
    },

    destroy: function(options, callback) {
      var url = Persona.urlRoot() + '/' + this.id
      return Super.destroy.call(this, url, options, callback)
    },

    roleAssignments: function(options, callback) {
      if (typeof options === 'function') {
        callback = options, options = {}
      }
      options || (options = {})
      var url = Persona.urlRoot() + '/' + this.id + '/role_assignments'
      return ngin.RoleAssignment.list(_.extend({}, options, {url:url}), callback)
    },

    groups: function(options, callback) {
      if (typeof options === 'function') {
        callback = options, options = {}
      }
      options || (options = {})
      var url = Persona.urlRoot() + '/' + this.id + '/groups'
      return ngin.RoleAssignment.list(_.extend({}, options, {url:url}), callback)
    },

    removeFromOrg: function(orgID, callback) {
      if (orgID == null || typeof orgID == 'function')
        return callback(new Error('orgID is required'))
      if (!this.id)
        return callback(new Error('persona has no ID'))
      var url = Persona.urlRoot() + '/' + this.id + '/Organization/' + orgID
      return Super.destroy.call(this, url, {}, callback)
    }

  }, {

    urlRoot: function() {
      var base = config.urls && config.urls.users || config.url
      return Url.resolve(base, '/personas')
    },

    list: function(options, callback) {
      var url = scopeUrl(options)
      return Model.list.call(this, url, options, callback)
    }

  })

  return Persona

}