(function($) {
    $.fxbConditions = function(el, options) {
            this.el = el;
            this.$el = $(el);
            this.options = $.extend({}, $.fxbConditions.defaultOptions, options);
    },
        $.fxbConditions.parse = function (formId) {
            var $form = $(formId);
            $form.init_formConditions();
        },

        $.extend($.fxbConditions,
            {
                defaultOptions: {
                    fieldConditions: [],
                    animate: true
                },

                helpers: {
                    normalize: function(value, preserveCase) {
                        if (value == null) {
                            return "";
                        }

                        return preserveCase ? String(value) : String(value).toLowerCase();
                    },

                    toNumber: function(value) {
                        value = Number(value);
                        return isNaN(value) ? undefined : value;
                    },

                    indexOf: function(str, value, startIndex, preserveCase) {
                        str = this.normalize(str, preserveCase);
                        value = this.normalize(value, preserveCase);

                        return str.indexOf(value, startIndex);
                    },

                    endsWith: function(str, value, preserveCase) {
                        str = this.normalize(str, preserveCase);
                        value = this.normalize(value, preserveCase);

                        var lengthDiff = str.length - value.length;
                        return lengthDiff >= 0 && str.substring(lengthDiff) === value;
                    }
                },

                actions: {
                    show: function($target) {
                        if (this.loaded && this.options.animate) {
                            if ($target.is(":hidden")) {
                                var $self = this;
                                $target.slideDown(function () {
                                    $self.setRequired($target);
                                });

                                return;
                            }
                        } else {
                            $target.show();
                        }

                        this.setRequired($target);
                    },

                    hide: function($target) {
                        if (this.loaded && this.options.animate) {
                            if ($target.is(":visible")) {
                                var $self = this;
                                $target.slideUp(function() {
                                    $self.setRequired($target);
                                });

                                return;
                            }
                        } else {
                            $target.hide();
                        }

                        this.setRequired($target);
                    },

                    enable: function($target) {
                        if ($target.is("input,select,textarea,button")) {
                            $target.prop("disabled", false);
                            this.setRequired($target);
                            $('[data-valmsg-for="' + $target.prop("name") + '"]').show();
                        }
                    },

                    disable: function($target) {
                        if ($target.is("input,select,textarea,button")) {
                            $target.prop("disabled", true);
                            this.setRequired($target);
                            $('[data-valmsg-for="' + $target.prop("name") + '"]').hide();
                        }
                    },

                    "go to page": function($target, action, conditionsResult) {
                        $target.each(function(idx, target) {
                            if (target.name &&
                                target.name.length &&
                                $(target).is("input[type='submit'], button[type='submit']")) {
                                var $nextPageEl = this.$el.find("[name=\"" + target.name + "\"][data-sc-next-page]");
                                if (!$nextPageEl.length) {
                                    if (conditionsResult && action.value) {
                                        var $currentEl = this.$el.find("[name=\"" + target.name + "\"]");
                                        $('<input>').attr({
                                            type: 'hidden',
                                            name: target.name,
                                            value: action.value,
                                            'data-sc-next-page': ""
                                        }).insertAfter($currentEl.last());
                                    }

                                    return;
                                }

                                var value = action.value;
                                if (!conditionsResult) {
                                    value = $nextPageEl.data("sc-next-page");

                                    for (var i = (this.executedActions.length - 1); i >= 0; i--) {
                                        var prevAction = this.executedActions[i];
                                        if (prevAction.fieldId === action.fieldId &&
                                            prevAction.conditionsResult &&
                                            prevAction.actionType.toLowerCase() === "go to page") {
                                            value = prevAction.value;
                                            break;
                                        }
                                    }
                                }

                                $nextPageEl.val(value);
                                $nextPageEl.prop("disabled", !value);
                            }
                        }.bind(this));
                    },

                    // action pairs used for finding the opposite action to execute when conditions are not satisfied
                    actionLinks: {
                        show: "hide",
                        enable: "disable",
                        "go to page": "go to page"
                    },

                    addAction: function(actionName, actionFn, oppositeActionName, oppositeActionFn) {
                        if (actionName && actionName.length) {
                            actionName = actionName.toLowerCase();

                            this[actionName] = actionFn;

                            if (arguments.length === 2) {
                                return;
                            }

                            if (oppositeActionName && oppositeActionName.length) {
                                oppositeActionName = oppositeActionName.toLowerCase();

                                this.actionLinks[actionName] = oppositeActionName;
                                if (arguments.length > 3) {
                                    this[oppositeActionName] = oppositeActionFn;
                                }
                            } else {
                                delete this.actionLinks[actionName];
                            }
                        }
                    },

                    getAction: function(actionName, conditionsResult) {
                        if (actionName && actionName.length) {
                            actionName = actionName.toLowerCase();

                            if (conditionsResult) {
                                return this[actionName];
                            }

                            if (this.actionLinks.hasOwnProperty(actionName)) {
                                var oppositeActionName = this.actionLinks[actionName];
                                return this[oppositeActionName];
                            } else {
                                for (var property in this.actionLinks) {
                                    if (this.actionLinks.hasOwnProperty(property) &&
                                        this.actionLinks[property] === actionName) {
                                        return this[property];
                                    }
                                }
                            }
                        }

                        return this[actionName];
                    }
                },

                operators: {
                    "contains": function(conditionValue, fieldValue) {
                        return $.fxbConditions.helpers.indexOf(fieldValue, conditionValue) >= 0;
                    },

                    "does not contain": function(conditionValue, fieldValue) {
                        return $.fxbConditions.helpers.indexOf(fieldValue, conditionValue) === -1;
                    },

                    "starts with": function(conditionValue, fieldValue) {
                        return $.fxbConditions.helpers.indexOf(fieldValue, conditionValue) === 0;
                    },

                    "does not start with": function(conditionValue, fieldValue) {
                        return $.fxbConditions.helpers.indexOf(fieldValue, conditionValue) !== 0;
                    },

                    "ends with": function(conditionValue, fieldValue) {
                        return $.fxbConditions.helpers.endsWith(fieldValue, conditionValue);
                    },

                    "does not end with": function(conditionValue, fieldValue) {
                        return !$.fxbConditions.helpers.endsWith(fieldValue, conditionValue);
                    },

                    "is equal to": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);
                        if (fieldValue === conditionValue) {
                            return true;
                        }

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right === "number" && left === right;
                            }
                        }

                        return false;
                    },

                    "is not equal to": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);
                        if (fieldValue === conditionValue) {
                            return false;
                        }

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right !== "number" || left !== right;
                            }
                        }

                        return true;
                    },

                    "is greater than": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right === "number" && right > left;
                            }
                        }

                        return fieldValue > conditionValue;
                    },

                    "is greater than or equal to": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);

                        if (fieldValue === conditionValue) {
                            return true;
                        }

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right === "number" && right >= left;
                            }
                        }

                        return fieldValue >= conditionValue;
                    },

                    "is less than": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right === "number" && right < left;
                            }
                        }

                        return fieldValue < conditionValue;
                    },

                    "is less than or equal to": function(conditionValue, fieldValue) {
                        conditionValue = $.fxbConditions.helpers.normalize(conditionValue);
                        fieldValue = $.fxbConditions.helpers.normalize(fieldValue);

                        if (fieldValue === conditionValue) {
                            return true;
                        }

                        if (conditionValue.length) {
                            var left = $.fxbConditions.helpers.toNumber(conditionValue);
                            if (typeof left === "number") {
                                var right = $.fxbConditions.helpers.toNumber(fieldValue);
                                return typeof right === "number" && right <= left;
                            }
                        }

                        return fieldValue <= conditionValue;
                    },

                    addOperator: function(operatorName, operatorFn) {
                        if (operatorName && operatorName.length) {
                            this[operatorName.toLowerCase()] = operatorFn;
                        }
                    },

                    getOperator: function(operatorName) {
                        return operatorName && operatorName.length ? this[operatorName.toLowerCase()] : null;
                    }
                },

                prototype: {
                    initConditions: function(options) {
                        this.options = $.extend(true, this.options || {}, options);

                        if (!this.options.fieldConditions) {
                            return;
                        }

                        var sourceFields = [];
                        this.options.fieldConditions.forEach(function(fieldCondition) {
                            if (!fieldCondition || !fieldCondition.conditions) {
                                return;
                            }

                            fieldCondition.conditions.forEach(function(condition) {
                                if (condition.fieldId && sourceFields.indexOf(condition.fieldId) === -1) {
                                    sourceFields.push(condition.fieldId);
                                    var $source = this.$el.find("[data-sc-field-key=\"" + condition.fieldId + "\"]")
                                        .filter(function() {
                                            return $.fxbConditions.helpers.endsWith(this.name, "value");
                                        });
                                    if ($source.length) {
                                        $source.on("change", this.applyConditions.bind(this));
                                    }
                                }
                            }.bind(this));
                        }.bind(this));

                        this.applyConditions();
                        this.loaded = true;
                    },

                    applyConditions: function() {
                        if (!this.options.fieldConditions) {
                            return;
                        }

                        this.executedActions = [];
                        this.fieldActions = {};

                        this.options.fieldConditions.forEach(function(fieldCondition) {
                            if (fieldCondition && fieldCondition.actions && fieldCondition.actions.length) {
                                var conditionsResult = this.evaluateConditions(fieldCondition);
                                fieldCondition.actions.forEach(function(action) {
                                    this.gatherFieldActions(action, conditionsResult);
                                }.bind(this));
                            }
                        }.bind(this));

                        for (var fieldId in this.fieldActions) {
                            if (!this.fieldActions.hasOwnProperty(fieldId)) continue;
                            for (var actionType in this.fieldActions[fieldId]) {
                                if (!this.fieldActions[fieldId].hasOwnProperty(actionType)) continue;
                                var action = this.fieldActions[fieldId][actionType].action;
                                var conditionsResult = this.fieldActions[fieldId][actionType].conditionsResult;
                                this.executeAction(action, conditionsResult);
                            }
                        }
                    },

                    setRequired: function($targets) {
                        $targets.each(function(idx, target) {
                            var $target = $(target);
                            if ($target.is("input:not([type='submit']), select, textarea")) {
                                var name = target.name;
                                if (!$.fxbConditions.helpers.endsWith(name, "value") && !$.fxbConditions.helpers.endsWith(name, "Files") )
                                    return;

                                name = name.slice(0, -(name.length - name.lastIndexOf(".") - 1)) + "Required";
                                var $requiredEl = this.$el.find("[name=\"" + name + "\"][data-sc-conditions-required]"),
                                    isNotAccessible = $target.is(":hidden") ||
                                        $target.css("visibility") === "hidden" ||
                                        target.disabled;

                                if (!$requiredEl.length) {
                                    if (isNotAccessible) {
                                        var $currentEl = this.$el.find("[name=\"" + target.name + "\"]");
                                        $('<input>').attr({
                                            type: 'hidden',
                                            name: name,
                                            value: false,
                                            "data-sc-conditions-required": ""
                                        }).insertAfter($currentEl.last());
                                    }

                                    return;
                                }

                                $requiredEl.val(false);
                                $requiredEl.prop("disabled", !isNotAccessible);
                            } else {
                                this.setRequired($target.find("input:not([type='submit']), select, textarea"));
                            }
                        }.bind(this));
                    },

                    executeAction: function(action, conditionsResult) {
                        if (action && action.fieldId && action.actionType) {
                            var $target = this.$el.find("[data-sc-field-key=\"" + action.fieldId + "\"]");
                            if ($target.length) {
                                var actionFn = $.fxbConditions.actions.getAction(action.actionType, conditionsResult);
                                if (actionFn && typeof actionFn === "function") {
                                    actionFn.call(this, $target, action, conditionsResult);
                                    var executedAction = $.extend(true,
                                        action,
                                        {
                                            conditionsResult: conditionsResult
                                        });
                                    this.executedActions.push(executedAction);
                                }
                            }
                        }
                    },

                    evaluateConditions: function(fieldCondition) {
                        if (!fieldCondition || !fieldCondition.conditions) return true;

                        var matchType = (fieldCondition.matchType || "").toLowerCase();
                        switch (matchType) {
                        case "all":
                            return fieldCondition.conditions.every(this.isConditionSatisfied.bind(this));
                        case "any":
                        default:
                            return fieldCondition.conditions.some(this.isConditionSatisfied.bind(this));
                        }
                    },

                    gatherFieldActions: function(action, conditionsResult) {
                        if (action && action.fieldId && action.actionType) {
                            if (typeof this.fieldActions[action.fieldId] === "undefined") {
                                this.fieldActions[action.fieldId] = {};
                            }

                            if (typeof this.fieldActions[action.fieldId][action.actionType] === "undefined") {
                                this.fieldActions[action.fieldId][action.actionType] = {
                                    action: action,
                                    conditionsResult: conditionsResult
                                };
                            }
                            // if a true conditionsResult finally appears for this particular fieldId and actionType, set it to true.
                            // from now on, it can never be set back to false again.
                            // (existing is deleted first to ensure this latest action appears at the bottom of the queue)
                            else if (!this.fieldActions[action.fieldId][action.actionType].conditionsResult && conditionsResult) {
                                delete this.fieldActions[action.fieldId][action.actionType];
                                this.fieldActions[action.fieldId][action.actionType] = {
                                    action: action,
                                    conditionsResult: true
                                };
                            }
                        }
                    },

                    getValueList: function(fieldId) {
                        var $fieldEl = this.$el.find("[data-sc-field-key=\"" + fieldId + "\"]").filter(function() {
                            return $.fxbConditions.helpers.endsWith(this.name, "value");
                        });

                        var $listElements = $fieldEl.filter(function(idx, element) {
                            return element.type === "checkbox" || element.type === "radio";
                        });

                        if (!$listElements.length && $fieldEl.prop("multiple")) {
                            $listElements = $fieldEl.find("option");
                        }

                        var value;
                        if ($listElements.length) {
                            if ($listElements.length > 1) {
                                value = $listElements.filter(":checked").map(function() {
                                    return $(this).val();
                                }).get();
                                if (!value.length) {
                                    value.push("");
                                }
                            } else {
                                value = [$listElements[0].checked ? "true" : "false"];
                            }
                        } else {
                            value = [$fieldEl.val()];
                        }

                        return value;
                    },

                    isConditionSatisfied: function(condition) {
                        if (condition && condition.operator) {
                            var operatorFn = $.fxbConditions.operators.getOperator(condition.operator);
                            if (operatorFn && typeof operatorFn === "function") {

                                var valueList = this.getValueList(condition.fieldId);
                                var result = condition.operator === "is not equal to"
                                    ? valueList.every(operatorFn.bind(this, condition.value))
                                    : valueList.some(operatorFn.bind(this, condition.value));
                                return result;
                            };
                        }

                        return false;
                    }
                }
            });

    $.fn.init_fxbConditions = function(options) {
        return this.each(function() {
            var conditions = $.data(this, "fxbForms.conditions");
            if (conditions) {
                conditions.initConditions(options);
            } else {
                conditions = new $.fxbConditions(this, options);
                $.data(this, "fxbForms.conditions", conditions);
                conditions.initConditions();
            }
        });
    };

    $.fn.init_formConditions = function() {
        var input = this.find('input[data-sc-fxb-condition]');
        if (input) {
            var conditions = JSON.parse(input.attr('value'));
            if (conditions) {
                if (typeof this.init_fxbConditions === 'function') {
                    this.init_fxbConditions(conditions);
                }
            }
        }
    };

    var forms = $("form[data-sc-fxb]");
    if (forms.length) {
        forms.each(function () {
            var formEl = $(this);
            formEl.init_formConditions();
        }).parent().on("submit", "form[data-sc-fxb]", function () {
            var $submitBtns = $(this).find("input[type='submit'], button[type='submit']");
            if ($submitBtns.length) {
                $('html, body').animate({
                    scrollTop: $(this).offset().top
                }, 500);
                $submitBtns.on("click", function () {
                    return false;
                });
            }
        });
    }
})(jQuery);