<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    
    <!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" integrity="sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-mocks.js"></script>
    
    
    <script src="QueueModule.js"></script>
    <script src="backend.js"></script>
  </head>

  <body>
    <h1 class="text-center" style="padding-bottom: 1em;">Managing Queue</h1>
    
    
    <article ng-app="myApp">
      <div ng-controller="MyAppCtrl">
    
    
        <div class="container-fluid">
          <div class="" ng-repeat="task in tasks">
          <div class="row form-group">
            <div class="col-xs-12 text-center" ng-bind="$index"></div>
          </div>
          <div class="col-xs-10 col-xs-offset-1">
            <form name="taskForm" ng-submit="onTaskEdited($event, task, taskForm)">
              <input class="form-control form-group" ng-model="task.name" placeholder="Task Name" />
              <textarea placeholder="Description" class="form-control form-group" ng-model="task.description"></textarea>
              <button class="btn btn-primary pull-right" type="submit">Send</button>
            </form>
          </div>
          <div class="row">
            <div class="col-xs-12"><hr /></div>
          </div>
        </div>
        </div>
    
    
      </div>
    </article>
    <script src="script.js"></script>
  </body>

</html>
angular
  .module('myApp', ['Queue', 'Backend'])
  .controller('MyAppCtrl', function($httpQueue, $scope, $interval) { 
    var vm = $scope;
    var 
      pollingCount = 0, // infinite polling
      pollingDelay = 1000
    ;
    
    // using a route.resolve could be better!
    $httpQueue
      .pull()
      .then(function(tasks) { vm.tasks = tasks;  })
      .catch(function() { vm.tasks = [{ name: '', description: '' }]; })
      .finally(function() { return $interval(vm.updateViewModel.bind(vm), pollingDelay, pollingCount, true); })
    ;
  
    var isLastPullFinished = false;
    vm.updateViewModel = function() {
      if(!isLastPullFinished) { return; }
      
      return $http
        .pull()
        .then(function(tasks) {
          for(var i = 0, len = tasks.length; i < len; i++) {
            
            for(var j = 0, jLen = vm.tasks.length; j < jLen; j++) {
              if(tasks[i].id !== vm.tasks[j].id) { continue; }
              
              // todo: manage recursively merging, in angular 1.3+ there is a
              // merge method https://docs.angularjs.org/api/ng/function/angular.merge
              // todo: control if the task model is $dirty (if the user is editing it)
              angular.extend(vm.tasks[j], tasks[i]);
            }
            
          };
          
          return vm.tasks;
        })
        .finally(function() {
          isLastPullfinished = true;
        })
      ;
    };
  
    
    
    vm.onTaskEdited = function(event, task, form) {
      event.preventDefault();
      if(form.$invalid || form.$pristine ) { return; }
      
      
      return $httpQueue.push(task);
      
    };
  })
;
/* Styles go here */

(function(window, angular, APP) {
  'use strict';

  function $httpQueueFactory($q, $http) {
    var self = this;

    var api = '/api/v1/tasks';
    
    self.MAX_ERRORS = 5;
    var errorCount = 0;

    self.queue = [];
    var processing = false;


    //Assume it as a private method, never call it directly
    self._each = function() {
      var configs = { cache: false };
			
			if(errorCount >= self.MAX_ERRORS) {
			  return;
			}
			
      return self
        .isQueueEmpty()
        .then(function(count) {
          processing = false;
          return count;
        })
        .catch(function() {
          if(processing) {
            return;
          }
        
          processing = true;
          var payload = self.queue.shift();
     
          var route = api;
          var task = 'post';
          if(payload.id) {
            task = 'put';
            route = api + '/' + payload.id;
          }
        
          return $http
            [task](route, payload, configs)
            .catch(function(error) {
              console.error('$httpQueue._each:error', error, payload);
              //because of the error we re-append this task to the queue;
              errorCount += 1;
              return self.push(payload);
            })
            .finally(function() {
              processing = false;
              return self._each();
            })
          ;
        })
      ;
    };


    self.isQueueEmpty = function() {
      var length = self.queue.length;
      var task = length > 0 ? 'reject' : 'when';
      
      return $q[task](length);
    };

    self.push = function(data) {
      self.queue.push(data);
      self._each();

      return self;
    };

    self.pull = function(params) {
      var configs = { cache: false };
      
      configs.params = angular.extend({}, params || {});

      return $http
        .get(api, configs)
        .then(function(result) {
          console.info('$httpQueue.pull:success', result);

          return result.data;
        })
        .catch(function(error) {
          console.error('$httpQueue.pull:error', error);
        
          return $q.reject(error);
        })
      ;
    };
  }



  APP
    .service('$httpQueue', ['$q', '$http', $httpQueueFactory])
  ;	

})(window, window.angular, window.angular.module('Queue', []));
(function(window, angular, APP) {
  'use strict';
  var api = '/api/v1/tasks';
  
  function decorate$httpBackend($provide) {
    $provide.decorator('$httpBackend', function($delegate) {
        var proxy = function(method, url, data, callback, headers) {
            var interceptor = function() {
                var _this = this,
                    _arguments = arguments;
                setTimeout(function() {
                  console.log('TaskProcessed:respond', _arguments);
                    callback.apply(_this, _arguments);
                }, 3500);
            };
            return $delegate.call(this, method, url, data, interceptor, headers);
        };
        for(var key in $delegate) {
            proxy[key] = $delegate[key];
        }
        return proxy;
    });
}
  
  function backend($httpBackend) {
    
    
    $httpBackend
      .whenGET(api)
      .respond(function(method, url, data, headers, params) {
        
        return [200, [
          {name: 'Foo', description: 'Baz'},
          {name: 'Foo1', description: 'Baz1'},
          {name: 'Foo2', description: 'Baz2'},
          {name: 'Foo3', description: 'Baz3'},
          {name: 'Foo4', description: 'Baz4'},
          {name: 'Foo5', description: 'Baz5'},
          {name: 'Foo6', description: 'Baz6'},
          {name: 'Foo7', description: 'Baz7'},
          {name: 'Foo8', description: 'Baz8'}
          ], {}, 'TaskList']
      })
    ;
    
    $httpBackend
      .whenPOST(api)
      .respond(function(method, url, data, headers, params) {
        console.count('New Create Task Request');
        return [200, null, {}, 'TaskCreated']
      })
    ;
    
  }
  
  APP
  .config(decorate$httpBackend)
  .run(backend)
  ;
})(window, window.angular, window.angular.module('Backend', ['ngMockE2E']));