define(['angular', 'angular-ui-bootstrap'], function(angular) {
  var liveTableFilters = angular.module('liveTableFilters', ['ui.bootstrap.typeahead',
    'template/typeahead/typeahead-match.html', 'template/typeahead/typeahead-popup.html',
    'driveServices']);

  liveTableFilters.factory('DateSuggest', function() {
    var tokenSeparator = /[\s.,\-\/:]+/;
    var getTokens = function(input) {
      // Split the input text into tokens.
      var tokens = input.toLowerCase().split(tokenSeparator);
      // Filter the tokens.
      for (var i = 0; i < tokens.length;) {
        var token = tokens[i];
        if (token === '') {
          tokens.splice(i, 1);
        } else {
          // Remove leading 0s.
          var number = parseInt(token);
          if (!isNaN(number)) {
            tokens[i] = number + '';
          }
          i++;
        }
      }
      return tokens;
    };

    var numberSeparator = /\D+/;
    var numberLimit = new Date().getFullYear() + 30;
    var getNumbers = function(input) {
      var parts = input.split(numberSeparator);
      var numbers = [];
      angular.forEach(parts, function(part) {
        var number = parseInt(part);
        if (number > 0 && number < numberLimit) {
          numbers.push(number);
        }
      });
      return numbers;
    };

    var getDatePattern = function(day, month, year) {
      return [day || '*', month || '*', year || '*'].join('.');
    };

    var staticOptions = [
      {label: 'Today', value: '-0d'},
      {label: 'Yesterday', value: '-1d'},
      {label: 'This week', value: '-0w'},
      {label: 'This month', value: '-0m'},
      {label: 'This year', value: '-0y'},
      {label: 'Last week', value: '-1w'},
      {label: 'Last month', value: '-1m'},
      {label: 'Last year', value: '-1y'}
    ];

    /*
                                                            */
    var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
    var monthOptions = [];
    angular.forEach(months, function(month, index) {
      monthOptions.push({
        label: month,
        hiddenLabel: index + 1,
        value: getDatePattern(null, index + 1, null)
      });
    });

    var replace = function(str, params) {
      angular.forEach(params, function(param, index) {
        str = str.replace('{' + index + '}', param);
      });
      return str;
    };

    var getMatchingMonths = function(input) {
      var tokens = getTokens(input);
      var matchingMonths = [];
      angular.forEach(tokens, function(token) {
        angular.forEach(months, function(month, index) {
          if (month.substr(0, token.length).toLowerCase() == token) {
            matchingMonths.push(index + 1);
          }
        });
      });
      if (matchingMonths.length == 0) {
        // Suggest the current month.
        matchingMonths.push(new Date().getMonth() + 1);
      }
      return matchingMonths;
    };

    var formats = {
      year: 'year {0}',
      dayMonth: '{0} {1}',
      monthYear: '{0} {1}',
      dayMonthYear: '{0} {1} {2}',
      lastDays: 'Last {0} days',
      lastWeeks: 'Last {0} weeks',
      lastMonths: 'Last {0} months',
      lastYears: 'Last {0} years'
    };

    var minYear = 1000;

    var dayMonthYear = function(day, month, year, hiddenLabel) {
      return {
        label: replace(formats.dayMonthYear, [day, months[month - 1], year]),
        hiddenLabel: hiddenLabel || month,
        value: getDatePattern(day, month, year)
      };
    };

    var dayMonth = function(day, month) {
      return {
        label: replace(formats.dayMonth, [day, months[month - 1]]),
        hiddenLabel: month,
        value: getDatePattern(day, month, null)
      };
    };

    var monthYear = function(month, year, hiddenLabel) {
      return {
        label: replace(formats.monthYear, [months[month - 1], year]),
        hiddenLabel: hiddenLabel || month,
        value: getDatePattern(null, month, year)
      };
    };

    var year = function(year, hiddenLabel) {
      return {
        label: replace(formats.year, [year]),
        hiddenLabel: hiddenLabel,
        value: getDatePattern(null, null, year)
      };
    };

    var lastDays = function(days) {
      return {
        label: replace(formats.lastDays, [days]),
        value: '-' + days + 'd'
      };
    };

    var lastWeeks = function(weeks) {
      return {
        label: replace(formats.lastWeeks, [weeks]),
        value: '-' + weeks + 'w'
      };
    };

    var lastMonths = function(months) {
      return {
        label: replace(formats.lastMonths, [months]),
        value: '-' + months + 'm'
      };
    };

    var lastYears = function(years) {
      return {
        label: replace(formats.lastYears, [years]),
        value: '-' + years + 'y'
      };
    };

    //
    // 0 numbers
    //
    var queryWithoutNumbers = function(input) {
      var options = staticOptions.concat(monthOptions);
      // We use the first token as a hidden label to force some options to be shown.
      var firstToken = getTokens(input)[0];
      var now = new Date();
      options.push(
        lastDays(7),
        lastWeeks(2),
        lastMonths(3),
        lastYears(2),
        dayMonthYear(now.getDate(), now.getMonth() + 1, now.getFullYear(), firstToken),
        monthYear(now.getMonth() + 1, now.getFullYear(), firstToken),
        year(now.getFullYear(), firstToken)
      );
      return options;
    };

    var asc = function(alice, bob) {
      return alice - bob;
    };

    //
    // 1 number
    //
    var queryWithOneNumber = function(input, numbers) {
      var number = numbers[0];
      var now = new Date();
      var options = [];
      if (number <= 12) {
        // Month
        options.push(
          monthOptions[number - 1],
          monthYear(number, now.getFullYear())
        );
      }
      if (number <= 31) {
        // Day
        angular.forEach(getMatchingMonths(input), function(month) {
          options.push(
            dayMonthYear(number, month, now.getFullYear()),
            dayMonth(number, month)
          );
        });
      }
      if (number >= minYear) {
        // Year
        options.push(year(number));
        angular.forEach(getMatchingMonths(input), function(month) {
          options.push(
            dayMonthYear(now.getDate(), month, number),
            monthYear(month, number)
          );
        });
      }
      if (number > 1) {
        options.push(lastDays(number));
        number <= 52 && options.push(lastWeeks(number));
        number <= 48 && options.push(lastMonths(number));
        number <= 5 && options.push(lastYears(number));
      }
      return options;
    };

    //
    // 2 numbers
    //
    var queryWithTwoNumbers = function(input, numbers) {
      var options = [];
      var now = new Date();
      numbers.sort(asc);
      if (numbers[0] <= 31 && numbers[1] >= minYear) {
        // Day/Month & Year
        var matchingMonths = getMatchingMonths(input);
        angular.forEach(matchingMonths, function(month) {
          options.push(dayMonthYear(numbers[0], month, numbers[1]));
        });
        if (numbers[0] <= 12 && matchingMonths.indexOf(numbers[0]) < 0) {
          options.push(monthYear(numbers[0], numbers[1]));
        }
      } else if (numbers[0] <= 12 && numbers[1] <= 31) {
        // Day & Month
        options.push(dayMonth(numbers[1], numbers[0]));
        options.push(dayMonthYear(numbers[1], numbers[0], now.getFullYear()));
        if (numbers[1] <= 12 && numbers[0] != numbers[1]) {
          options.push(dayMonth(numbers[0], numbers[1]));
          options.push(dayMonthYear(numbers[0], numbers[1], now.getFullYear()));
        }
      }
      return options;
    };

    //
    // 3 numbers
    //
    var queryWithThreeNumbers = function(input, numbers) {
      var options = [];
      numbers.sort(asc);
      if (numbers[0] <= 12 && numbers[1] <= 31 && numbers[2] >= minYear) {
        options.push(dayMonthYear(numbers[1], numbers[0], numbers[2]));
        if (numbers[1] <= 12 && numbers[0] != numbers[1]) {
          options.push(dayMonthYear.apply(this, numbers));
        }
      }
      return options;
    };

    var cases = [queryWithoutNumbers, queryWithOneNumber, queryWithTwoNumbers, queryWithThreeNumbers];

    return {
      query: function(input) {
        var numbers = getNumbers(input);
        return numbers.length < 4 ? cases[numbers.length](input, numbers) : [];
      },

      getTokens: getTokens
    };
  });
  liveTableFilters.filter('dateSuggestFilter', ['DateSuggest', function(DateSuggest) {
    return function(items, input) {
      var tokens = DateSuggest.getTokens(input);
      var resultsWithScore = [];
      angular.forEach(items, function(item) {
        var score = 0;
        var label = item.label.toLowerCase();
        var hiddenLabel = ((item.hiddenLabel || '') + '').toLowerCase();
        angular.forEach(tokens, function(token, index) {
          if (label.indexOf(token) >= 0 || hiddenLabel.indexOf(token) >= 0) {
            // A lower token index generates a bigger bonus.
            var indexBonus = (tokens.length - index) / tokens.length;
            score += 1 + indexBonus;
          }
        });
        if (score > 0) {
          resultsWithScore.push({
            item: item,
            score: score
          });
        }
      });

      // Sort by score, descending.
      resultsWithScore.sort(function(alice, bob) {
        return bob.score - alice.score;
      });

      var results = [];
      angular.forEach(resultsWithScore, function(result) {
        results.push(result.item);
      });
      return results;
    }
  }]);
  liveTableFilters.directive('userFilter',  ['User', function(User) {
    return {
      restrict: 'A',
      scope: {
        'value': '=userFilter'
      },
      templateUrl: "http://wiki.carville.ru/bin/get/FileManagerCode/LiveTableFilter?template=userFilter",
      link: function(scope, element, attrs) {
        // Retrieve the users that match the given input.
        scope.getUsers = function(input) {
          return User.query({input: input, nb: 5}).$promise;
        };

        // We need to keep the selected user id in order to determine when the filter value is modified from outside,
        // e.g. when the Back/Forward buttons are used to navigate the browser history.
        var selectedUser;
        // Update the filter value whenever a user is selected from the list of suggestions.
        scope.setValue = function(item, model, label) {
          scope.value = selectedUser = model.id;
          element.find('input').css('background-image', 'url("' + model.avatarURL + '")');
        };

        // Reset the filter value when the input text is deleted.
        scope.$watch('userName', function(newValue, oldValue) {
          if (newValue === '') {
            scope.value = selectedUser = '';
            element.find('input').css('background-image', '');
          }
        });

        // Observe when the filter value is modified from outside (e.g. Back/Forward buttons).
        scope.$watch('value', function(newValue, oldValue) {
          if (newValue != selectedUser) {
            // The filter value was modified from outside.
            selectedUser = newValue;
            if (newValue) {
              // Fill the filter input with the user name.
              scope.loading = true;
              User.getById(newValue, function(user) {
                scope.loading = false;
                scope.userName = user.name;
                element.find('input').css('background-image', 'url("' + user.avatarURL + '")');
              }, function(error) {
                scope.loading = false;
                scope.userName = newValue;
                element.find('input').css('background-image', '');
              });
            } else {
              // Reset the filter input.
              scope.userName = '';
            }
          }
        });
      }
    };
  }]);

  liveTableFilters.directive('dateFilter', ['DateSuggest', function(DateSuggest) {
    return {
      restrict: 'A',
      scope: {
        'value': '=dateFilter'
      },
      templateUrl: "http://wiki.carville.ru/bin/get/FileManagerCode/LiveTableFilter?template=dateFilter",
      link: function(scope, element, attrs) {
        // Retrieve the date suggestions that match the text input.
        scope.getOptions = function(input) {
          return DateSuggest.query(input);
        };

        // Update the filter value whenever an option is selected from the list of suggestions.
        scope.setValue = function(item, model, label) {
          scope.value = model.value;
        };

        // Reset the filter value when the input text is deleted.
        scope.$watch('date', function(newValue, oldValue) {
          if (newValue === '') {
            scope.value = '';
          }
        });
      }
    }
  }]);
});
