/**
 * Getter for user cookies
 * @param  {String} name Cookie name
 * @return {String}      Cookie value
 */

function getCookie(name) {
  var cookieValue = null;
  if (document.cookie && document.cookie != '') {
    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
      var cookie = jQuery.trim(cookies[i]);
      if (cookie.substring(0, name.length + 1) == (name + '=')) {
        cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
        break;
      }
    }
  }
  return cookieValue;
}
/**
 * Extract CSRF token for AJAX calls
 */
var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
  crossDomain: false,
  beforeSend: function(xhr, settings) {
    //attach CSRF token to every AJAX call
    if (!csrfSafeMethod(settings.type)) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

/**
 * Collection handling boilerplate code
 *
 * New entity gets __created flag, deleted entity gets __destoryed flag,
 * so on the server-side we know what changed
 * @param  Object $scope AngularJS scope
 * @param  String name   Object name, add{name} and remove{name} methods will be generated
 * @param  Object model  Model name (the collections name) in the entity
 */
function makeAddRemove($scope, name, model) {
  /**
   * Add entity to collection
   * @param  String entity Name of the entity
   */
  $scope['add' + name] = function(entity) {
    //check if already exists
    for (var i in $scope.entity[model]) {
      var item = $scope.entity[model][i];
      //if it was removed previously, just remove the destroyed flag
      if (item.name == entity && item.__destroyed) {
        item.__destroyed = false;
        return;
      } else if (item.name == entity) {
        //if it is already in the collection, do nothing
        return;
      }
    }
    //add the new entity, with the proper created flag
    $scope.entity[model].push({
      name: entity,
      __created: true,
    });
  }
  /**
   * Remove entity from collection
   * @param  Object entity
   */
  $scope['remove' + name] = function(entity) {
    for (var i in $scope.entity[model]) {
      var item = $scope.entity[model][i];
      //if it was not saved on the server before, just remove it
      if (item.name == entity.name && item.__created) {
        $scope.entity[model].splice(i, 1);
      } else if (item.name == entity.name) {
        //else just set the destoryed flag
        item.__destroyed = true;
        return;
      }
    }
  }
}

/**
 * List of firewall collections, controllers/routes will be dynamically created from them.
 *
 * E.g., from the `rule` controller, the RESTful url `/rules/` will be generated,
 * and the `/static/partials/rule-list.html` template will be used.
 * @type {Array}
 */
var controllers = {
  rule: function($scope) {
    $('#targetName').typeahead({
      source: function(query, process) {
        $.ajax({
          url: '/firewall/autocomplete/' + $scope.entity.target.type + '/',
          type: 'post',
          data: 'name=' + query,
          success: function autocompleteSuccess(data) {
            process(data.map(function(obj) {
              return obj.name;
            }));
          }
        });
      },
      matcher: function() {
        return true;
      },
      updater: function(item) {
        var self = this;
        $scope.$apply(function() {
          $scope.entity.target.name = item;
        })
        return item;
      }
    });
  },
  host: function($scope) {
    makeAddRemove($scope, 'HostGroup', 'groups');
  },
  vlan: function($scope) {
    makeAddRemove($scope, 'Vlan', 'vlans');
  },
  vlangroup: function($scope) {
    makeAddRemove($scope, 'Vlan', 'vlans');
  },
  hostgroup: function() {},
  firewall: function() {},
  domain: function() {},
  record: function() {},
  blacklist: function() {},
}
/**
 * Configures AngularJS with the defined controllers
 */
var module = angular.module('firewall', []).config(
['$routeProvider',
  function($routeProvider) {
    for (var controller in controllers) {
      var init = controllers[controller];
      $routeProvider.when('/' + controller + 's/', {
        templateUrl: '/static/partials/' + controller + '-list.html',
        controller: ListController('/firewall/' + controller + 's/')
      }).when('/' + controller + 's/:id/', {
        templateUrl: '/static/partials/' + controller + '-edit.html',
        controller: EntityController('/firewall/' + controller + 's/', init)
      });
    }
    $routeProvider.otherwise({
      redirectTo: '/rules/'
    });
  }
]);

/**
 * Generate range [a, b)
 * @param  {Number} a Lower limit
 * @param  {Number} b Upper limit
 * @return {Array}   Number from a to b
 */

function range(a, b) {
  var res = [];
  do res.push(a++);
  while (a <= b)
  return res;
}

/**
 * Smart (recursive) match function for any object
 * @param  {Object} obj    Object to be checked
 * @param  {String} query  Regexp to be checked against
 * @return {Boolean}       True, if object matches (somehow) with query
 */

function matchAnything(obj, query) {
  var expr = new RegExp(query, 'i')
  for (var i in obj) {
    var prop = obj[i];
    if (typeof prop === 'number' && prop == query) return true;
    if (typeof prop === 'string' && prop.match(expr)) return true;
    if (typeof prop === 'object' && matchAnything(prop, query)) return true;
  }
  return false;
}

/**
 * Factory for the given collection URL
 * @param  {String} url REST endpoint for collection
 * @return {Function}   ListController for the given REST endpoint
 */

function ListController(url) {
  /**
   * ListController for the given REST endpoint
   * @param  {Object} $scope Current controllers scope
   * @param  {Object} $http  Helper for AJAX calls
   */
  return function($scope, $http) {
    $scope.page = 1;
    var rules = [];
    var pageSize = 10;
    var itemCount = 0;
    /**
     * Does filtering&paging
     * @return {Array} Items to be displayed
     */
    $scope.getPage = function() {
      var res = [];
      if ($scope.query) {
        for (var i in rules) {
          var rule = rules[i];
          if (matchAnything(rule, $scope.query)) {
            res.push(rule);
          }
        }

      } else {
        res = rules;
      }
      $scope.pages = range(1, Math.ceil(res.length / pageSize));
      $scope.page = Math.min($scope.page, $scope.pages.length);
      return res.slice(($scope.page - 1) * pageSize, $scope.page * pageSize);
    };
    /**
     * Setter for current page
     * @param {Number} page Page to navigate to
     */
    $scope.setPage = function(page) {
      $scope.page = page;
    };
    /**
     * Jumps to the next page (if available)
     */
    $scope.nextPage = function() {
      $scope.page = Math.min($scope.page + 1, $scope.pages.length);
    };
    /**
     * Jumps to the previous page (if available)
     */
    $scope.prevPage = function() {
      $scope.page = Math.max($scope.page - 1, 1);
    };

    /**
     * Delete the given entity, then reload the list
     * @param  Number id
     */
    $scope.deleteEntity = function(id) {
      $.ajax({
        url: url.split('/')[2] + '/' + id + '/delete/',
        type: 'post',
        success: reloadList
      });
    };

    /**
     * Reloads the entities
     */
    function reloadList() {
      $http.get(url).success(function success(data) {
        rules = data;
        $scope.pages = range(1, Math.ceil(data.length / pageSize));
      });
    }

    // Initial load...
    reloadList();
  }
}

/**
 * Factory for the given URL
 * @param {Object} url  REST endpoint of the model
 * @param {Object} init Init function for model-specic behaviour
 */

function EntityController(url, init) {
  /**
   * Entity Controller for the given model URL
   * @param  {Object} $scope       Current controllers scope
   * @param  {Object} $http        Helper for AJAX calls
   * @param  {Object} $routeParams Helper for route parameter parsing
   */
  return function($scope, $http, $routeParams) {
    init($scope);
    var id = $routeParams.id;
    $scope.errors = {};
    /**
     * Generic filter for collections
     *
     * Hides destroyed items
     * @param  {Object} item Current item in collection
     * @return {Boolean}     Item should be displayed, or not
     */
    $scope.destroyed = function(item) {
      return !item.__destroyed;
    }
    $scope.hasError = function(name) {
      return $scope.errors[name] ? 'error' : null;
    }
    $scope.getError = function(name) {
      return $scope.errors[name] ? $scope.errors[name] : '';
    }
    $scope.save = function() {
      $scope.errors = {};
      $.ajax({
        url: url + 'save/',
        type: 'post',
        data: JSON.stringify($scope.entity),
        success: function(data) {
          console.log(data);
          $scope.$apply(function() {
            $scope.errors = {};
          });
          window.location.hash = '/' + url.split('/')[2] + '/' + data + '/';
        }
      }).error(function(data) {
        try {
          data = JSON.parse(data.responseText);
          var newErrors = {};
          for (var i in data) {
            var id = $('#' + i).length ? i : 'targetName';
            newErrors[id] = data[i];
          }
          $scope.$apply(function() {
            $scope.errors = newErrors;
          })
        } catch (ex) {

        }
      })
    }

    function reloadEntity() {
      $http.get(url + id + '/').success(function success(data) {
        $scope.entity = data;
        $('input[type=text], input[type=number], select, textarea, .has-tooltip').tooltip({
          placement: 'right'
        });
        ['vlan', 'vlangroup', 'host', 'hostgroup', 'firewall', 'owner', 'domain', 'record'].forEach(function(t) {
          $('.' + t).typeahead({
            /**
             * Typeahead does AJAX queries
             * @param  {String}   query   Partial name of the entity
             * @param  {Function} process Callback function after AJAX returned result
             */
            source: function(query, process) {
              $.ajax({
                url: '/firewall/autocomplete/' + t + '/',
                type: 'post',
                data: 'name=' + query,
                success: function autocompleteSuccess(data) {
                  process(data.map(function(obj) {
                    return obj.name;
                  }));
                }
              });
            },
            /**
             * Filtering is done on server-side, show all results
             * @return {Boolean} Always true, so all result are visible
             */
            matcher: function() {
              return true;
            },
            /**
             * Typeahead does not trigger proper DOM events, so we have to refresh
             * the model manually.
             * @param  {String} item Selected entity name
             * @return {String}      Same as `item`, the input value is set to this
             */
            updater: function(item) {
              var self = this;
              console.log(this);
              $scope.$apply(function() {
                var model = self.$element[0].getAttribute('ng-model').split('.')[1];
                console.log(self.$element[0].getAttribute('ng-model'), model);
                try {
                  $scope.entity[model].name = item;
                } catch (ex) {
                  try {
                    $scope[self.$element[0].getAttribute('ng-model')] = item;
                  } catch (ex) {

                  }
                }
              })
              return item;
            }
          });
        })
      });
    }
    reloadEntity();
  }
}
