(function() {
    'use strict';

    function maxValidator(ctrl, value, limit) {
        var max = parseFloat(limit);
        var validity = ctrl.$isEmpty(value) || isNaN(max)|| value <= max;
        ctrl.$setValidity('max', validity);
        return value;
    }

    function minValidator(ctrl, value, limit) {
        var min = parseFloat(limit);
        var validity = ctrl.$isEmpty(value) || isNaN(min) || value >= min;
        ctrl.$setValidity('min', validity);
        return value;
    }

    var cnpjPattern = new StringMask('00.000.000\/0000-00');
    var cpfPattern = new StringMask('000.000.000-00');

    function numberViewMask (decimals, decimalDelimiter, thousandsDelimiter) {
        var mask = '#' + thousandsDelimiter + '##0';

        if(decimals > 0) {
            mask += decimalDelimiter;
            for (var i = 0; i < decimals; i++) {
                mask += '0';
            }
        }

        return new StringMask(mask, {
            reverse:true
        });
    }

    function numberModelMask (decimals) {
        var mask = '###0';

        if(decimals > 0) {
            mask += '.';
            for (var i = 0; i < decimals; i++) {
                mask += '0';
            }
        }

        return new StringMask(mask, {
            reverse:true
        });
    }

    function clearDelimitersAndLeadingZeros (value) {
        var cleanValue = value.replace(/^0*/, '');
        cleanValue = cleanValue.replace(/[^0-9]/g, '');
        return cleanValue;
    }

    function preparePercentageToFormatter (value, decimals) {
        return clearDelimitersAndLeadingZeros((parseFloat(value)*100).toFixed(decimals));
    }

    function prepareNumberToFormatter (value, decimals) {
        return clearDelimitersAndLeadingZeros((parseFloat(value)).toFixed(decimals));
    }

    function uiBrCpfMask() {
        function applyCpfMask (value) {
            if(!value) {
                return value;
            }
            var formatedValue = cpfPattern.apply(value);
            return formatedValue.trim().replace(/[^0-9]$/, '');
        }

        return {
            restrict: 'A',
            require: '?ngModel',
            link: function (scope, element, attrs, ctrl) {
                if (!ctrl) {
                    return;
                }

                ctrl.$formatters.push(function(value) {
                    return applyCpfMask(value);
                });

                ctrl.$parsers.push(function(value) {
                    if(!value) {
                        return value;
                    }

                    var actualNumber = value.replace(/[^\d]/g,'');
                    var formatedValue = applyCpfMask(actualNumber);
                    ctrl.$setValidity('cpf', BrV.cpf.validate(formatedValue));

                    if (ctrl.$viewValue !== formatedValue) {
                        ctrl.$setViewValue(formatedValue);
                        ctrl.$render();
                    }

                    return formatedValue.replace(/[^\d]+/g,'');
                });
            }
        };
    }

    function uiBrCnpjMask() {
        function applyCnpjMask (value) {
            if(!value) {
                return value;
            }
            var formatedValue = cnpjPattern.apply(value);
            return formatedValue.trim().replace(/[^0-9]$/, '');
        }
        return {
            restrict: 'A',
            require: '?ngModel',
            link: function (scope, element, attrs, ctrl) {
                if (!ctrl) {
                    return;
                }

                ctrl.$formatters.push(function(value) {
                    return applyCnpjMask(value);
                });

                ctrl.$parsers.push(function(value) {
                    if(!value) {
                        return value;
                    }

                    var actualNumber = value.replace(/[^\d]+/g,'');
                    var formatedValue = applyCnpjMask(actualNumber);
                    ctrl.$setValidity('cnpj', BrV.cnpj.validate(formatedValue));

                    if (ctrl.$viewValue !== formatedValue) {
                        ctrl.$setViewValue(formatedValue);
                        ctrl.$render();
                    }

                    return formatedValue.replace(/[^\d]+/g,'');
                });
            }
        };
    }

    function uiBrCpfCnpjMask() {
        function applyCpfCnpjMask (value) {
            if(!value) {
                return value;
            }
            var formatedValue;
            if (value.length > 11) {
                formatedValue = cnpjPattern.apply(value);
            } else {
                formatedValue = cpfPattern.apply(value);
            }
            return formatedValue.trim().replace(/[^0-9]$/, '');
        }
        return {
            restrict: 'A',
            require: '?ngModel',
            link: function (scope, element, attrs, ctrl) {
                if (!ctrl) {
                    return;
                }

                ctrl.$formatters.push(function(value) {
                    return applyCpfCnpjMask(value);
                });

                ctrl.$parsers.push(function(value) {
                    if(!value) {
                        return value;
                    }
                    var actualNumber = value.replace(/[^\d]+/g,'');

                    var formatedValue = applyCpfCnpjMask(actualNumber);
                    if (actualNumber.length > 11) {
                        ctrl.$setValidity('cnpj', BrV.cnpj.validate(formatedValue));
                        ctrl.$setValidity('cpf', true);
                    } else {
                        ctrl.$setValidity('cpf', BrV.cpf.validate(formatedValue));
                        ctrl.$setValidity('cnpj', true);
                    }

                    if (ctrl.$viewValue !== formatedValue) {
                        ctrl.$setViewValue(formatedValue);
                        ctrl.$render();
                    }

                    return formatedValue.replace(/[^\d]+/g,'');
                });
            }
        };
    }

    angular.module('ui.utils.masks', [])
        .directive('uiPercentageMask', ['$locale', function ($locale) {
            var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
                thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP;

            return {
                restrict: 'A',
                require: '?ngModel',
                scope: {
                    min: '=?min',
                    max: '=?max'
                },
                link: function (scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    var decimals = parseInt(attrs.uiPercentageMask);
                    if(isNaN(decimals)) {
                        decimals = 2;
                    }
                    var numberDecimals = decimals + 2;
                    var viewMask = numberViewMask(decimals, decimalDelimiter, thousandsDelimiter),
                        modelMask = numberModelMask(numberDecimals);

                    ctrl.$formatters.push(function(value) {
                        if(!value) {
                            return ' %';
                        }

                        var valueToFormat = preparePercentageToFormatter(value, decimals);
                        return viewMask.apply(valueToFormat) + ' %';
                    });

                    ctrl.$parsers.push(function(value) {
                        function renderValue(formatedValue) {
                            if (ctrl.$viewValue !== formatedValue) {
                                ctrl.$setViewValue(formatedValue);
                                ctrl.$render();
                            }
                        }
                        if(!value) {
                            renderValue(' %');
                            return value;
                        }

                        var valueToFormat = clearDelimitersAndLeadingZeros(value);
                        if(value && value.indexOf('%') < 0 && valueToFormat.length >= 1) {
                            valueToFormat = valueToFormat.substr(0,valueToFormat.length-1);
                        }
                        var formatedValue = ' %';
                        var actualNumber;
                        if (valueToFormat) {
                            formatedValue = viewMask.apply(valueToFormat) + ' %';
                            actualNumber = parseFloat(modelMask.apply(valueToFormat));
                        }
                        renderValue(formatedValue);

                        return actualNumber;
                    });

                    if(attrs.min){
                        ctrl.$parsers.push(function(value) {
                            return minValidator(ctrl, value, scope.min);
                        });

                        scope.$watch('min', function() {
                            minValidator(ctrl, ctrl.$modelValue, scope.min);
                        });
                    }

                    if(attrs.max) {
                        ctrl.$parsers.push(function(value) {
                            return maxValidator(ctrl, value, scope.max);
                        });

                        scope.$watch('max', function() {
                            maxValidator(ctrl, ctrl.$modelValue, scope.max);
                        });
                    }
                }
            };
        }])
        .directive('uiNumberMask', ['$locale', function ($locale) {
            var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP,
                thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP;

            return {
                restrict: 'A',
                require: '?ngModel',
                scope: {
                    min: '=?min',
                    max: '=?max'
                },
                link: function (scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    var decimals = parseInt(attrs.uiNumberMask);
                    if(isNaN(decimals)) {
                        decimals = 2;
                    }
                    var viewMask = numberViewMask(decimals, decimalDelimiter, thousandsDelimiter),
                        modelMask = numberModelMask(decimals);

                    ctrl.$formatters.push(function(value) {
                        if(!value) {
                            return value;
                        }

                        var valueToFormat = prepareNumberToFormatter(value, decimals);
                        return viewMask.apply(valueToFormat);
                    });

                    ctrl.$parsers.push(function(value) {
                        if(!value) {
                            return value;
                        }

                        var valueToFormat = clearDelimitersAndLeadingZeros(value);
                        var formatedValue = viewMask.apply(valueToFormat);
                        var actualNumber = parseFloat(modelMask.apply(valueToFormat));

                        if(angular.isDefined(attrs.uiNegativeNumber)){
                            var isNegative = (value[0] === '-'),
                                needsToInvertSign = (value.slice(-1) === '-');

                            //only apply the minus sign if is negative or(exclusive) needs to be negative
                            if(needsToInvertSign ^ isNegative) {
                                actualNumber *= -1;
                                formatedValue = '-' + formatedValue;
                            }
                        }

                        if (ctrl.$viewValue !== formatedValue) {
                            ctrl.$setViewValue(formatedValue);
                            ctrl.$render();
                        }

                        return actualNumber;
                    });

                    if(attrs.min){
                        ctrl.$parsers.push(function(value) {
                            return minValidator(ctrl, value, scope.min);
                        });

                        scope.$watch('min', function() {
                            minValidator(ctrl, ctrl.$modelValue, scope.min);
                        });
                    }

                    if(attrs.max) {
                        ctrl.$parsers.push(function(value) {
                            return maxValidator(ctrl, value, scope.max);
                        });

                        scope.$watch('max', function() {
                            maxValidator(ctrl, ctrl.$modelValue, scope.max);
                        });
                    }
                }
            };
        }])
        .directive('uiBrCpfMask', [uiBrCpfMask])
        .directive('uiBrCnpjMask', [uiBrCnpjMask])
        .directive('uiBrCpfcnpjMask', [uiBrCpfCnpjMask])
        // deprecated: will be removed in the next major version
        .directive('uiCpfMask', [uiBrCpfMask])
        // deprecated: will be removed in the next major version
        .directive('uiCnpjMask', [uiBrCnpjMask])
        // deprecated: will be removed in the next major version
        .directive('uiCpfcnpjMask', [uiBrCpfCnpjMask])
        .directive('uiMoneyMask', ['$locale', function ($locale) {
            var decimalDelimiter = $locale.NUMBER_FORMATS.DECIMAL_SEP;
            var thousandsDelimiter = $locale.NUMBER_FORMATS.GROUP_SEP;
            var currencySym = $locale.NUMBER_FORMATS.CURRENCY_SYM;
            return {
                restrict: 'A',
                require: '?ngModel',
                link: function (scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    var decimals = parseInt(attrs.uiMoneyMask);
                    if(isNaN(decimals)) {
                        decimals = 2;
                    }
                    var decimalsPattern = decimals > 0 ? decimalDelimiter + new Array(decimals + 1).join('0') : '';
                    var maskPattern = currencySym+' #'+thousandsDelimiter+'##0'+decimalsPattern;
                    var moneyMask = new StringMask(maskPattern, {reverse: true});

                    ctrl.$formatters.push(function(value) {
                        if(!value) {
                            return value;
                        }

                        return moneyMask.apply(value.toFixed(decimals).replace(/[^\d]+/g,''));
                    });

                    ctrl.$parsers.push(function(value) {
                        if (!value) {
                            return value;
                        }

                        var actualNumber = value.replace(/[^\d]+/g,'');
                        actualNumber = actualNumber.replace(/^[0]+([1-9])/,'$1');
                        var formatedValue = moneyMask.apply(actualNumber);

                        if (value !== formatedValue) {
                            ctrl.$setViewValue(formatedValue);
                            ctrl.$render();
                        }

                        return parseInt(formatedValue.replace(/[^\d]+/g,''))/Math.pow(10,decimals);
                    });
                }
            };
        }])
        .directive('uiBrPhoneNumber',function() {
            /**
             * FIXME: all numbers will have 9 digits after 2016.
             * see http://portal.embratel.com.br/embratel/9-digito/
             */
            var phoneMask8D = new StringMask('(00) 0000-0000'),
                phoneMask9D = new StringMask('(00) 00000-0000');

            function clearValue (value) {
                if(!value) {
                    return value;
                }

                return value.replace(/[^0-9]/g, '');
            }

            function applyPhoneMask (value) {
                if(!value) {
                    return value;
                }

                var formatedValue;
                if(value.length < 11){
                    formatedValue = phoneMask8D.apply(value);
                }else{
                    formatedValue = phoneMask9D.apply(value);
                }

                return formatedValue.trim().replace(/[^0-9]$/, '');
            }

            return {
                restrict: 'A',
                require: '?ngModel',
                link: function(scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    ctrl.$formatters.push(function(value) {
                        return applyPhoneMask(value);
                    });

                    ctrl.$parsers.push(function(value) {
                        if (!value) {
                            return value;
                        }

                        var cleanValue = clearValue(value);
                        var formatedValue = applyPhoneMask(cleanValue);

                        if (ctrl.$viewValue !== formatedValue) {
                            ctrl.$setViewValue(formatedValue);
                            ctrl.$render();
                        }

                        return clearValue(formatedValue);
                    });
                }
            };
        })
        .directive('uiBrCepMask',function() {
            var cepMask = new StringMask('00000-000');

            function clearValue (value) {
                if(!value) {
                    return value;
                }

                return value.replace(/[^0-9]/g, '');
            }

            function applyCepMask (value, ctrl) {
                if(!value) {
                    return value;
                }
                var processed = cepMask.process(value);
                ctrl.$setValidity('cep', processed.valid);
                var formatedValue = processed.result;
                return formatedValue.trim().replace(/[^0-9]$/, '');
            }

            return {
                restrict: 'A',
                require: '?ngModel',
                link: function(scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    ctrl.$formatters.push(function(value) {
                        return applyCepMask(value, ctrl);
                    });

                    ctrl.$parsers.push(function(value) {
                        if (!value) {
                            return value;
                        }

                        var cleanValue = clearValue(value);
                        var formatedValue = applyCepMask(cleanValue, ctrl);

                        if (ctrl.$viewValue !== formatedValue) {
                            ctrl.$setViewValue(formatedValue);
                            ctrl.$render();
                        }

                        return clearValue(formatedValue);
                    });
                }
            };
        })
        .directive('uiBrIeMask',function() {

            var ieMasks = {
                'AC': [{mask: new StringMask('00.000.000/000-00')}],
                'AL': [{mask: new StringMask('000000000')}],
                'AM': [{mask: new StringMask('00.000.000-0')}],
                'AP': [{mask: new StringMask('000000000')}],
                'BA': [{chars: 8, mask: new StringMask('000000-00')},
                    {mask: new StringMask('0000000-00')}],
                'CE': [{mask: new StringMask('00000000-0')}],
                'DF': [{mask: new StringMask('00000000000-00')}],
                'ES': [{mask: new StringMask('00000000-0')}],
                'GO': [{mask: new StringMask('00.000.000-0')}],
                'MA': [{mask: new StringMask('000000000')}],
                'MG': [{mask: new StringMask('000.000.000/0000')}],
                'MS': [{mask: new StringMask('000000000')}],
                'MT': [{mask: new StringMask('0000000000-0')}],
                'PA': [{mask: new StringMask('00-000000-0')}],
                'PB': [{mask: new StringMask('00000000-0')}],
                'PE': [{chars: 9, mask: new StringMask('0000000-00')},
                    {mask: new StringMask('00.0.000.0000000-0')}],
                'PI': [{mask: new StringMask('000000000')}],
                'PR': [{mask: new StringMask('000.00000-00')}],
                'RJ': [{mask: new StringMask('00.000.00-0')}],
                'RN': [{chars: 9, mask: new StringMask('00.000.000-0')},
                    {mask: new StringMask('00.0.000.000-0')}],
                'RO': [{mask: new StringMask('0000000000000-0')}],
                'RR': [{mask: new StringMask('00000000-0')}],
                'RS': [{mask: new StringMask('000/0000000')}],
                'SC': [{mask: new StringMask('000.000.000')}],
                'SE': [{mask: new StringMask('00000000-0')}],
                'SP': [{mask: new StringMask('000.000.000.000')},
                    {mask: new StringMask('-00000000.0/000')}],
                'TO': [{mask: new StringMask('00000000000')}]
            };

            function clearValue (value) {
                if(!value) {
                    return value;
                }
                return value.replace(/[^0-9]/g, '');
            }

            function getMask(uf, value) {
                if(!uf || !ieMasks[uf]) {
                    return undefined;
                }
                var _uf = uf.toUpperCase();
                if (_uf === 'SP' && /^P/i.test(value)) {
                    return ieMasks.SP[1].mask;
                }
                var masks = ieMasks[uf];
                var i = 0;
                while(masks[i].chars && masks[i].chars < clearValue(value).length && i < masks.length - 1) {
                    i++;
                }
                return masks[i].mask;
            }

            function applyIEMask (value, uf, ctrl) {
                var mask = getMask(uf, value);
                if(!value || !mask) {
                    return value;
                }
                var processed = mask.process(clearValue(value));
                ctrl.$setValidity('ie', BrV.ie(uf).validate(value));
                var formatedValue = processed.result;
                if (uf && uf.toUpperCase() === 'SP' && /^p/i.test(value)) {
                    return 'P'+(formatedValue ? formatedValue.trim().replace(/[^0-9]$/, '') : '');
                }
                return formatedValue.trim().replace(/[^0-9]$/, '');
            }

            return {
                restrict: 'A',
                require: '?ngModel',
                scope: {
                    state: '=uiBrIeMask'
                },
                link: function(scope, element, attrs, ctrl) {
                    if (!ctrl) {
                        return;
                    }

                    scope.$watch('state', function(state) {
                        applyIEMask(ctrl.$viewValue, state, ctrl);
                    });

                    ctrl.$formatters.push(function(value) {
                        return applyIEMask(value, scope.state, ctrl);
                    });

                    ctrl.$parsers.push(function(value) {
                        if (!value) {
                            return value;
                        }

                        var formatedValue = applyIEMask(value, scope.state, ctrl);

                        if (ctrl.$viewValue !== formatedValue) {
                            ctrl.$setViewValue(formatedValue);
                            ctrl.$render();
                        }

                        if (scope.state && scope.state.toUpperCase() === 'SP' && /^p/i.test(value)) {
                            return 'P'+clearValue(formatedValue);
                        }
                        return clearValue(formatedValue);
                    });
                }
            };
        });
})();