/*global window*/

(function (window, document) {
    'use strict';

    var SquareComponent,
        Square;
    /**
     * The master component prototype object, extended by all Square library
     *
     * @param element
     * @param defaults
     */
    SquareComponent = function () {
        this.name = 'component';
        this.defaults = {};
        this.element = null;
        this.config = {};

        /**
         * Constructor method
         */
        this.construct = function () {
            this
                .setConfig()
                .init();
        };

        /**
         * Set the component config
         *
         * @param config
         * @returns {component}
         */
        this.setConfig = function () {
            var component = this;

            // Merge the defaults and any passed config
            component.config = this.extend(component.defaults, this.config);

            return this;
        };

        /**
         * Trigger a named callback function event
         *
         * @param event
         * @returns {component}
         */
        this.triggerEvent = function (event) {
            if (this.config[event] !== undefined) {
                this.config[event].call(this);
            }

            return this;
        };

        /**
         * Empty initialization
         *
         * @returns {SquareComponent}
         */
        this.init = function () {
            return this;
        };

        /**
         * Merges multiple objects
         *
         * @param out
         * @returns {*|{}}
         */
        this.extend = function (out) {
            var i,
                key,
                len = arguments.length;

            out = out || {};
            for (i = 1; i < len; i++) {
                if (arguments[i]) {
                    for (key in arguments[i]) {
                        if (arguments[i].hasOwnProperty(key)) {
                            out[key] = arguments[i][key];
                        }
                    }
                }
            }

            return out;
        };

        /**
         * Checks if an element has a specified class
         *
         * @param element
         * @param classname
         * @returns {*}
         */
        this.hasClass = function (element, classname) {
            var result;

            if (element.classList) {
                result = element.classList.contains(classname);
            } else {
                result = new RegExp('(^| )' + classname + '( |$)', 'gi').test(element.className);
            }

            return result;
        };

        /**
         * Adds a specified class to an element
         *
         * @param element
         * @param classname
         */
        this.addClass = function (element, classname) {
            if (element.classList) {
                element.classList.add(classname);
            } else {
                element.className += ' ' + classname;
            }
        };

        /**
         * Removes a specified class from an element
         *
         * @param element
         * @param classname
         */
        this.removeClass = function (element, classname) {
            if (element.classList) {
                element.classList.remove(classname);
            } else {
                element.className = element.className.replace(new RegExp('(^|\\b)' + classname.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
            }
        };

        /**
         * Toggles a specified class from an element
         *
         * @param element
         * @param classname
         */
        this.toggleClass = function (element, classname) {
            if (element.classList) {
                element.classList.toggle(classname);
            } else {
                var classes = element.className.split(' '),
                    existingIndex = classes.indexOf(classname);

                if (existingIndex >= 0) {
                    classes.splice(existingIndex, 1);
                } else {
                    classes.push(classname);
                }

                element.className = classes.join(' ');
            }
        };
    };

    /**
     * Namespace for the Square library and JS to avoid conflicts with other libraries
     *
     * @constructor
     */
    Square = function () {
        // Container object for plugins
        this.library = {};
        this.components = {};
        this.media = {};

        /**
         * Initialise
         */
        this.init = function () {
            var library = this.library,
                component;

            // Instantiate each component type
            for (component in library) {
                if (library.hasOwnProperty(component)) {
                    this.registerComponent(component);
                }
            }
        };

        /**
         * Registers a new component
         *
         * @param component
         * @returns {Square}
         */
        this.registerComponent = function (component) {
            var square = this,
                elements = document.querySelectorAll('[data-' + component + ']');

            this.createFunction(component);

            Array.prototype.forEach.call(elements, function (element) {
                var config_str = element.getAttribute('data-' + component),
                    config_obj = {};

                if (config_str) {
                    try {
                        config_obj = JSON.parse(config_str);
                    } catch (err) {
                        return;
                    }
                }

                square.components[component](element, config_obj);
            });

            return this;
        };

        /**
         * Creates a reusable function to bind a Square component to a DOM element/s
         *
         * @param component
         * @returns {Square}
         */
        this.createFunction = function (component) {
            var square = this;

            // Function creation
            this.components[component] = function (elements, config) {
                var library = square.library,
                    data = {},
                    instance;

                // Ensure that the elements are iterable
                if (elements !== '[object NodeList]' && !Array.isArray(elements)) {
                    elements = [elements];
                }

                // Loop each element
                Array.prototype.forEach.call(elements, function (element) {
                    // Pass the square data if it already exists, allowing an element to contain multiple component types
                    if (element.square !== undefined) {
                        data = element.square;
                    }

                    // Only instantiate if the seelcted component type does not already exist
                    if (data[component] === undefined) {
                        library[component].prototype = new SquareComponent();
                        instance = new library[component](element, config);
                        instance.construct();
                        data[component] = instance;

                        // Bind the square data with the element
                        element.square = data;
                    }
                });
            };

            return this;
        };
    };

    window.square = new Square();

    // Initialise on page load
    window.onload = function () {
        window.square.init();
    };

}(window, window.document));
