/* ***** BEGIN LICENSE BLOCK *****
 * Distributed under the BSD license:
 *
 * Copyright (c) 2013 Matthew Christopher Kastor-Inare III, Atropa Inc. Intl
 * All rights reserved.
 *
 * Contributed to Ajax.org under the BSD license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Ajax.org B.V. nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ***** END LICENSE BLOCK ***** */

/*jslint indent: 4, maxerr: 50, white: true, browser: true, vars: true*/
/*global define*/

/**
 * Generates the settings menu
 * @fileOverview Generates the settings menu.
 * @author <a href="mailto:matthewkastor@gmail.com">
 *  Matthew Christopher Kastor-Inare III </a><br />
 *  ☭ Hial Atropa!! ☭
 */

define(function (require, exports, module) {
    'use strict';
    var egen = require('./element_generator');
    var addEditorMenuOptions = require('./add_editor_menu_options').addEditorMenuOptions;
    var getSetFunctions = require('./get_set_functions').getSetFunctions;

    /**
     * Generates an interactive menu with settings useful to end users.
     * @author <a href="mailto:matthewkastor@gmail.com">
     *  Matthew Christopher Kastor-Inare III </a><br />
     *  ☭ Hial Atropa!! ☭
     * @param {ace.Editor} editor An instance of the ace editor.
     */
    module.exports.generateSettingsMenu = function generateSettingsMenu(editor) {
        /**
         * container for dom elements that will go in the menu.
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         */
        var elements = [];

        /**
         * Sorts the menu entries (elements var) so they'll appear in alphabetical order
         *  the sort is performed based on the value of the contains property
         *  of each element. Since this is an `array.sort` the array is sorted
         *  in place.
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         */
        function cleanupElementsList() {
            elements.sort(function (a, b) {
                var x = a.getAttribute('contains');
                var y = b.getAttribute('contains');
                return x.localeCompare(y);
            });
        }

        /**
         * Wraps all dom elements contained in the elements var with a single
         *  div.
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         */
        function wrapElements() {
            var topmenu = document.createElement('div');
            topmenu.setAttribute('id', 'ace_settingsmenu');
            elements.forEach(function (element) {
                topmenu.appendChild(element);
            });

            var el = topmenu.appendChild(document.createElement('div'));
            var version = require("../../ace").version;
            el.style.padding = "1em";
            el.textContent = "Ace version " + version;

            return topmenu;
        }

        /**
         * Creates a new menu entry.
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         * @param {object} obj This is a reference to the object containing the
         *  set function. It is used to set up event listeners for when the
         *  menu options change.
         * @param {string} clss Maps to the class of the dom element. This is
         *  the name of the object containing the set function e.g. `editor`,
         *  `session`, `renderer`.
         * @param {string} item  This is the set function name. It maps to the
         *  id of the dom element (check, select, input) and to the "contains"
         *  attribute of the div holding both the element and its label.
         * @param {mixed} val This is the value of the setting. It is mapped to
         *  the dom element's value, checked, or selected option accordingly.
         */
        function createNewEntry(obj, clss, item, val) {
            var el;
            var div = document.createElement('div');
            div.setAttribute('contains', item);
            div.setAttribute('class', 'ace_optionsMenuEntry');
            div.setAttribute('style', 'clear: both;');

            div.appendChild(egen.createLabel(
                item.replace(/^set/, '').replace(/([A-Z])/g, ' $1').trim(),
                item
            ));

            if (Array.isArray(val)) {
                el = egen.createSelection(item, val, clss);
                el.addEventListener('change', function (e) {
                    try {
                        editor.menuOptions[e.target.id].forEach(function (x) {
                            if (x.textContent !== e.target.textContent) {
                                delete x.selected;
                            }
                        });
                        obj[e.target.id](e.target.value);
                    } catch (err) {
                        throw new Error(err);
                    }
                });
            } else if (typeof val === 'boolean') {
                el = egen.createCheckbox(item, val, clss);
                el.addEventListener('change', function (e) {
                    try {
                        // renderer['setHighlightGutterLine'](true);
                        obj[e.target.id](!!e.target.checked);
                    } catch (err) {
                        throw new Error(err);
                    }
                });
            } else {
                // this aids in giving the ability to specify settings through
                // post and get requests.
                // /ace_editor.html?setMode=ace/mode/html&setOverwrite=true
                el = egen.createInput(item, val, clss);
                el.addEventListener('change', function (e) {
                    try {
                        if (e.target.value === 'true') {
                            obj[e.target.id](true);
                        } else if (e.target.value === 'false') {
                            obj[e.target.id](false);
                        } else {
                            obj[e.target.id](e.target.value);
                        }
                    } catch (err) {
                        throw new Error(err);
                    }
                });
            }
            el.style.cssText = 'float:right;';
            div.appendChild(el);
            return div;
        }

        /**
         * Generates selection fields for the menu and populates their options
         *  using information from `editor.menuOptions`
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         * @param {string} item The set function name.
         * @param {object} esr A reference to the object having the set function.
         * @param {string} clss The name of the object containing the set function.
         * @param {string} fn The matching get function's function name.
         * @returns {DOMElement} Returns a dom element containing a selection
         *  element populated with options. The option whose value matches that
         *  returned from `esr[fn]()` will be selected.
         */
        function makeDropdown(item, esr, clss, fn) {
            var val = editor.menuOptions[item];
            var currentVal = esr[fn]();
            if (typeof currentVal == 'object')
                currentVal = currentVal.$id;
            val.forEach(function (valuex) {
                if (valuex.value === currentVal)
                    valuex.selected = 'selected';
            });
            return createNewEntry(esr, clss, item, val);
        }

        /**
         * Processes the set functions returned from `getSetFunctions`. First it
         *  checks for menu options defined in `editor.menuOptions`. If no
         *  options are specified then it checks whether there is a get function
         *  (replace set with get) for the setting. When either of those
         *  conditions are met it will attempt to create a new entry for the
         *  settings menu and push it into the elements array defined above.
         *  It can only do so for get functions which return
         *  strings, numbers, and booleans. A special case is written in for
         *  `getMode` where it looks at the returned objects `$id` property and
         *  forwards that through instead. Other special cases could be written
         *  in but that would get a bit ridiculous.
         * @author <a href="mailto:matthewkastor@gmail.com">
         *  Matthew Christopher Kastor-Inare III </a><br />
         *  ☭ Hial Atropa!! ☭
         * @param {object} setObj An item from the array returned by
         *  `getSetFunctions`.
         */
        function handleSet(setObj) {
            var item = setObj.functionName;
            var esr = setObj.parentObj;
            var clss = setObj.parentName;
            var val;
            var fn = item.replace(/^set/, 'get');
            if (editor.menuOptions[item] !== undefined) {
                // has options for select element
                elements.push(makeDropdown(item, esr, clss, fn));
            } else if (typeof esr[fn] === 'function') {
                // has get function
                try {
                    val = esr[fn]();
                    if (typeof val === 'object') {
                        // setMode takes a string, getMode returns an object
                        // the $id property of that object is the string
                        // which may be given to setMode...
                        val = val.$id;
                    }
                    // the rest of the get functions return strings,
                    // booleans, or numbers.
                    elements.push(
                        createNewEntry(esr, clss, item, val)
                    );
                } catch (e) {
                    // if there are errors it is because the element
                    // does not belong in the settings menu
                }
            }
        }

        addEditorMenuOptions(editor);
        // gather the set functions
        getSetFunctions(editor).forEach(function (setObj) {
            // populate the elements array with good stuff.
            handleSet(setObj);
        });
        // sort the menu entries in the elements list so people can find
        // the settings in alphabetical order.
        cleanupElementsList();
        // dump the entries from the elements list and wrap them up in a div
        return wrapElements();
    };

});