( function( angular ) {
    'use strict';
    if( !angular ) {
        throw 'Missing something? Please add angular.js to your project or move this script below the angular.js reference';
    }

    var directiveId = 'ngRemoteValidate',
        remoteValidate = function( $http, $timeout, $q ) {
            return {
                restrict: 'A',
                require: [ '^form','ngModel' ],
                scope: {
                    ngRemoteInterceptors: '=?'
                },
                link: function( scope, el, attrs, ctrls ) {
                    var cache = {},
                        handleChange,
                        setValidation,
                        addToCache,
                        request,
                        shouldProcess,
                        ngForm = ctrls[ 0 ],
                        ngModel = ctrls[ 1 ],
                        options = {
                            ngRemoteThrottle: 400,
                            ngRemoteMethod: 'POST'
                        };

                    angular.extend( options, attrs );
					//TODO: Use Cain of Responsibility to reduce complexity.
                    if( options.ngRemoteValidate.charAt( 0 ) === '[' ) {
                        options.urls = eval( options.ngRemoteValidate );
                    } else if (options.ngRemoteValidate.charAt( 0 ) === '{') {
                        options.keys = eval( '(' + options.ngRemoteValidate + ')' );
                        options.urls = Object.keys( options.keys );
                    } else {
                        options.urls = [ options.ngRemoteValidate ];
                    }

                    addToCache = function( response ) {
                        var value = response[ 0 ].data.value;
                        if ( cache[ value ] ) return cache[ value ];
                        cache[ value ] = response;
                    };

                    shouldProcess = function( value ) {
                        var otherRulesInValid = false;
                        for ( var p in ngModel.$error ) {
                            var checkedKey = !options.hasOwnProperty( 'keys' ) ||
                                             !( Object.keys(options.keys )
								.filter( function( k ) {
                                    return options.keys[ k ] === p;
                                } )[ 0 ] );
                            if ( ngModel.$error[ p ] && p != directiveId && checkedKey ) {
                                otherRulesInValid = true;
                                break;
                            }
                        }
                        return !( ngModel.$pristine || otherRulesInValid );
                    };

                    setValidation = function( response, skipCache ) {
                        var i = 0,
                            l = response.length,
                            useKeys = options.hasOwnProperty( 'keys' ),
                            isValid = true;
                        for( ; i < l; i++ ) {

                            if( scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.response ) {
                                response[ i ] = scope.ngRemoteInterceptors.response( response[ i ] );
                            }

                            if( !response[ i ].data.isValid ) {
                                isValid = false;
                                if (!useKeys) {
                                    break;
                                }
                            }
							
                            var canSetKey = ( useKeys &&
                                              response[ i ].hasOwnProperty( 'config' ) &&
                                              options.keys[ response[ i ].config.url ] );

                            if ( canSetKey ) {
                                var key = options.keys[ response[ i ].config.url ];
                                ngModel.$setValidity( key, response[ i ].data.isValid );
                            }
                        }
                        if( !skipCache ) {
                            addToCache( response );    
                        }
                        ngModel.$setValidity( directiveId, isValid );
                        ngModel.$processing = ngModel.$pending = ngForm.$pending = false;
                    };

                    handleChange = function( value ) {
                        if( typeof value === 'undefined' || value === '' ) {
                            ngModel.$setPristine();
                            return;
                        }

                        if ( !shouldProcess( value ) ) {
                            return setValidation( [ { data: { isValid: true, value: value } } ], true );
                        }

                        if ( cache[ value ] ) {
                            return setValidation( cache[ value ], true );
                        }
                        
                        //Set processing now, before the delay. 
                        //Check first to reduce DOM updates
                        if( !ngModel.$pending ) {
                            ngModel.$processing = ngModel.$pending = ngForm.$pending = true;
                        }
                        
                        if ( request ) {
                            $timeout.cancel( request );
                        }

                        request = $timeout( function( ) {
							var calls = [],
                                i = 0,
                                l = options.urls.length,
                                toValidate = { value: value },
                                httpOpts = { method: options.ngRemoteMethod };
                            
                            if ( scope[ el[0].name + 'SetArgs' ] ) {
                                toValidate = scope[el[0].name + 'SetArgs'](value, el, attrs, ngModel);
                            }

                            if(options.ngRemoteMethod == 'POST'){
                                httpOpts.data = toValidate;
                            } else {
                                httpOpts.params = toValidate;
                            }

                            for( ; i < l; i++ ) {
                                httpOpts.url = options.urls[i];
                                httpOpts.isWebApiRequest = true;
                                if(scope.ngRemoteInterceptors && scope.ngRemoteInterceptors.request){
                                    httpOpts = scope.ngRemoteInterceptors.request(httpOpts);
                                }

                                calls.push( $http( httpOpts ) );
                            }

                            $q.all( calls ).then( setValidation );
                            
                        }, options.ngRemoteThrottle );
                        return true;
                    };

                    //ngModel.$parsers.unshift( handleChange );
                    scope.$watch( function( ) {
                        return ngModel.$viewValue;
                    }, handleChange );
                }
            };
        };

    angular.module( 'remoteValidation', [] )
           .constant('MODULE_VERSION', '0.6.1')
           .directive( directiveId, [ '$http', '$timeout', '$q', remoteValidate ] );
           
})( this.angular );