module RecurringTodos
  class WeeklyRecurrencePattern < AbstractRecurrencePattern
    def initialize(user)
      super user
    end

    def every_x_week
      get :every_other1
    end

    { monday: 1, tuesday: 2, wednesday: 3, thursday: 4, friday: 5, saturday: 6, sunday: 0 }.each do |day, number|
      define_method("on_#{day}") do
        on_xday number
      end
    end

    def on_xday(n)
      get(:every_day) && get(:every_day)[n, 1] != ' '
    end

    def recurrence_pattern
      if every_x_week > 1
        I18n.t("todos.recurrence.pattern.every_n", :n => every_x_week) + " " + I18n.t("common.weeks")
      else
        I18n.t('todos.recurrence.pattern.weekly')
      end
    end

    def validate
      super
      validate_not_blank(every_x_week, "Every other nth week may not be empty for weekly recurrence setting")
      something_set = %w{ sunday monday tuesday wednesday thursday friday saturday }.inject(false) { |set, day| set || send("on_#{day}") }
      errors.add(:base, "You must specify at least one day on which the todo recurs") unless something_set
    end

    def get_next_date(previous)
      start = determine_start_date(previous)

      day = find_first_day_in_this_week(start)
      return day unless day == -1

      # we did not find anything this week, so check the nth next, starting from
      # sunday
      start = start + every_x_week.week - (start.wday).days

      start = find_first_day_in_this_week(start)
      return start unless start == -1

      raise Exception.new, "unable to find next weekly date (#{every_day})"
    end

    private

    def determine_start_date(previous)
      if previous.nil?
        return start_from || Time.zone.now
      else
        start = previous + 1.day
        if start.wday == 0
          # we went to a new week, go to the nth next week and find first match
          # that week. Note that we already went into the next week, so -1
          start += (every_x_week - 1).week
        end
        unless start_from.nil?
          # check if the start_from date is later than previous. If so, use
          # start_from as start to search for next date
          start = start_from if start_from > previous
        end
        return start
      end
    end

    def find_first_day_in_this_week(start)
      # check if there are any days left this week for the next todo
      start.wday.upto 6 do |i|
        return start + (i - start.wday).days if on_xday(i)
      end
      -1
    end
  end
end
