(function(requirejs, require, define) {
    define([], function() {
        return configParser;

        function configParser(state, config) {
            state.config = {
                'draggables': [],
                'baseImage': '',
                'targets': [],
                'onePerTarget': null, // Specified by user. No default.
                'targetOutline': true,
                'labelBgColor': '#d6d6d6',
                'individualTargets': null, // Depends on 'targets'.
                'foundErrors': false // Whether or not we find errors while processing the config.
            };

            getDraggables(state, config);
            getBaseImage(state, config);
            getTargets(state, config);
            getOnePerTarget(state, config);
            getTargetOutline(state, config);
            getLabelBgColor(state, config);

            setIndividualTargets(state);

            if (state.config.foundErrors !== false) {
                return false;
            }

            return true;
        }

        function getDraggables(state, config) {
            if (config.hasOwnProperty('draggables') === false) {
                console.log('ERROR: "config" does not have a property "draggables".');
                state.config.foundErrors = true;
            } else if ($.isArray(config.draggables) === true) {
                config.draggables.every(function(draggable) {
                    if (processDraggable(state, draggable) !== true) {
                        state.config.foundErrors = true;

                    // Exit immediately from .every() call.
                        return false;
                    }

                // Continue to next .every() call.
                    return true;
                });
            } else {
                console.log('ERROR: The type of config.draggables is no supported.');
                state.config.foundErrors = true;
            }
        }

        function getBaseImage(state, config) {
            if (config.hasOwnProperty('base_image') === false) {
                console.log('ERROR: "config" does not have a property "base_image".');
                state.config.foundErrors = true;
            } else if (typeof config.base_image === 'string') {
                state.config.baseImage = config.base_image;
            } else {
                console.log('ERROR: Property config.base_image is not of type "string".');
                state.config.foundErrors = true;
            }
        }

        function getTargets(state, config) {
            if (config.hasOwnProperty('targets') === false) {
            // It is possible that no "targets" were specified. This is not an error.
            // In this case the default value of "[]" (empty array) will be used.
            // Draggables can be positioned anywhere on the image, and the server will
            // get an answer in the form of (x, y) coordinates for each draggable.
            } else if ($.isArray(config.targets) === true) {
                config.targets.every(function(target) {
                    if (processTarget(state, target) !== true) {
                        state.config.foundErrors = true;

                    // Exit immediately from .every() call.
                        return false;
                    }

                // Continue to next .every() call.
                    return true;
                });
            } else {
                console.log('ERROR: Property config.targets is not of a supported type.');
                state.config.foundErrors = true;
            }
        }

        function getOnePerTarget(state, config) {
            if (config.hasOwnProperty('one_per_target') === false) {
                console.log('ERROR: "config" does not have a property "one_per_target".');
                state.config.foundErrors = true;
            } else if (typeof config.one_per_target === 'string') {
                if (config.one_per_target.toLowerCase() === 'true') {
                    state.config.onePerTarget = true;
                } else if (config.one_per_target.toLowerCase() === 'false') {
                    state.config.onePerTarget = false;
                } else {
                    console.log('ERROR: Property config.one_per_target can either be "true", or "false".');
                    state.config.foundErrors = true;
                }
            } else {
                console.log('ERROR: Property config.one_per_target is not of a supported type.');
                state.config.foundErrors = true;
            }
        }

        function getTargetOutline(state, config) {
        // It is possible that no "target_outline" was specified. This is not an error.
        // In this case the default value of 'true' (boolean) will be used.

            if (config.hasOwnProperty('target_outline') === true) {
                if (typeof config.target_outline === 'string') {
                    if (config.target_outline.toLowerCase() === 'true') {
                        state.config.targetOutline = true;
                    } else if (config.target_outline.toLowerCase() === 'false') {
                        state.config.targetOutline = false;
                    } else {
                        console.log('ERROR: Property config.target_outline can either be "true", or "false".');
                        state.config.foundErrors = true;
                    }
                } else {
                    console.log('ERROR: Property config.target_outline is not of a supported type.');
                    state.config.foundErrors = true;
                }
            }
        }

        function getLabelBgColor(state, config) {
        // It is possible that no "label_bg_color" was specified. This is not an error.
        // In this case the default value of '#d6d6d6' (string) will be used.

            if (config.hasOwnProperty('label_bg_color') === true) {
                if (typeof config.label_bg_color === 'string') {
                    state.config.labelBgColor = config.label_bg_color;
                } else {
                    console.log('ERROR: Property config.label_bg_color is not of a supported type.');
                }
            }
        }

        function setIndividualTargets(state) {
            if (state.config.targets.length === 0) {
                state.config.individualTargets = false;
            } else {
                state.config.individualTargets = true;
            }
        }

        function processDraggable(state, obj) {
            if (
            (attrIsString(obj, 'id') === false) ||
            (attrIsString(obj, 'icon') === false) ||
            (attrIsString(obj, 'label') === false) ||

            (attrIsBoolean(obj, 'can_reuse', false) === false) ||

            (obj.hasOwnProperty('target_fields') === false)
        ) {
                return false;
            }

        // Check that all targets in the 'target_fields' property are proper target objects.
        // We will be testing the return value from .every() call (it can be 'true' or 'false').
            if (obj.target_fields.every(
            function(targetObj) {
                return processTarget(state, targetObj, false);
            }
        ) === false) {
                return false;
            }

            state.config.draggables.push(obj);

            return true;
        }

    // We need 'pushToState' parameter in order to simply test an object for the fact that it is a
    // proper target (without pushing it to the 'state' object). When
    //
    //     pushToState === false
    //
    // the object being tested is not going to be pushed to 'state'. The function will onyl return
    // 'true' or 'false.
        function processTarget(state, obj, pushToState) {
            if (
            (attrIsString(obj, 'id') === false) ||

            (attrIsInteger(obj, 'w') === false) ||
            (attrIsInteger(obj, 'h') === false) ||

            (attrIsInteger(obj, 'x') === false) ||
            (attrIsInteger(obj, 'y') === false)
        ) {
                return false;
            }

            if (pushToState !== false) {
                state.config.targets.push(obj);
            }

            return true;
        }

        function attrIsString(obj, attr) {
            if (obj.hasOwnProperty(attr) === false) {
                console.log('ERROR: Attribute "obj.' + attr + '" is not present.');

                return false;
            } else if (typeof obj[attr] !== 'string') {
                console.log('ERROR: Attribute "obj.' + attr + '" is not a string.');

                return false;
            }

            return true;
        }

        function attrIsInteger(obj, attr) {
            var tempInt;

            if (obj.hasOwnProperty(attr) === false) {
                console.log('ERROR: Attribute "obj.' + attr + '" is not present.');

                return false;
            }

            tempInt = parseInt(obj[attr], 10);

            if (isFinite(tempInt) === false) {
                console.log('ERROR: Attribute "obj.' + attr + '" is not an integer.');

                return false;
            }

            obj[attr] = tempInt;

            return true;
        }

        function attrIsBoolean(obj, attr, defaultVal) {
            if (obj.hasOwnProperty(attr) === false) {
                if (defaultVal === undefined) {
                    console.log('ERROR: Attribute "obj.' + attr + '" is not present.');

                    return false;
                } else {
                    obj[attr] = defaultVal;

                    return true;
                }
            }

            if (obj[attr] === '') {
                obj[attr] = defaultVal;
            } else if ((obj[attr] === 'false') || (obj[attr] === false)) {
                obj[attr] = false;
            } else if ((obj[attr] === 'true') || (obj[attr] === true)) {
                obj[attr] = true;
            } else {
                console.log('ERROR: Attribute "obj.' + attr + '" is not a boolean.');

                return false;
            }

            return true;
        }
    }); // End-of: define([], function () {
}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); // End-of: (function (requirejs, require, define) {
