module SimpleCalendar
  module ViewHelpers

    def calendar(events, options={}, &block)
      raise 'SimpleCalendar requires a block to be passed in' unless block_given?

      opts = default_options
      options.reverse_merge! opts
      events       ||= []
      selected_month = Date.new(options[:year], options[:month])
      current_date   = Time.zone.now.to_date
      range          = build_range selected_month, options
      month_array    = range.each_slice(7).to_a

      draw_calendar(selected_month, month_array, current_date, events, options, block)
    end

    private
    def default_options
      {
        :year           => (params[:year] || Time.zone.now.year).to_i,
        :month          => (params[:month] || Time.zone.now.month).to_i,
        :prev_text      => raw("«"),
        :next_text      => raw("»"),
        :start_day      => :sunday,
        :class          => "table table-bordered table-striped calendar",
        :params         => {},
        :time_selector  => "start_time"
      }
    end
    # Returns array of dates between start date and end date for selected month
    def build_range(selected_month, options)
      start_date = selected_month.beginning_of_month.beginning_of_week(options[:start_day])
      end_date   = selected_month.end_of_month.end_of_week(options[:start_day])

      (start_date..end_date).to_a
    end

    # Renders the calendar table
    def draw_calendar(selected_month, month, current_date, events, options, block)
      tags = []
      today = Time.zone.now.to_date
      content_tag(:table, :class => options[:class]) do
        tags << month_header(selected_month, options)
        day_names = I18n.t("date.abbr_day_names")
        day_names = day_names.rotate((Date::DAYS_INTO_WEEK[options[:start_day]] + 1) % 7)
        tags << content_tag(:thead, content_tag(:tr, day_names.collect { |name| content_tag :th, name, :class => (selected_month.month == today.month && today.strftime("%a") == name ? "current-day" : nil)}.join.html_safe))
        tags << content_tag(:tbody, :'data-month'=>selected_month.month, :'data-year'=>selected_month.year) do

          month.collect do |week|
            content_tag(:tr, :class => (week.include?(today) ? "current-week week" : "week")) do

              week.collect do |date|
                td_class = ["day"]
                td_class << "today" if today == date
                td_class << "not-current-month" if selected_month.month != date.month
                td_class << "past" if today > date
                td_class << "future" if today < date
                td_class << "wday-#{date.wday.to_s}" # <- to enable different styles for weekend, etc

                cur_events = day_events(date, events, options[:time_selector])

                td_class << (cur_events.any? ? "events" : "no-events")

                content_tag(:td, :class => td_class.join(" "), :'data-date-iso'=>date.to_s, 'data-date'=>date.to_s.gsub('-', '/')) do
                  content_tag(:div) do
                    divs = []
                    concat content_tag(:div, date.day.to_s, :class=>"day_number")

                    if cur_events.empty? && options[:empty_date]
                      concat options[:empty_date].call(date)
                    else
                      if options[:not_empty_date]
                        concat options[:not_empty_date].call(date)
                      end
                      divs << cur_events.collect{ |event| block.call(event) }
                    end

                    divs.join.html_safe
                  end #content_tag :div
                end #content_tag :td

              end.join.html_safe
            end #content_tag :tr

          end.join.html_safe
        end #content_tag :tbody

        tags.join.html_safe
      end #content_tag :table
    end

    # Returns an array of events for a given day
    def day_events(date, events, time_selector)
      events.select { |e| e.send(time_selector).to_date == date }.sort_by { |e| e.send(time_selector) }
    end

    # Generates the header that includes the month and next and previous months
    def month_header(selected_month, options)
      content_tag :h2 do
        previous_month = selected_month.advance :months => -1
        next_month = selected_month.advance :months => 1
        tags = []

        tags << month_link(options[:prev_text], previous_month, options[:params], {:class => "previous-month"})
        tags << "#{I18n.t("date.month_names")[selected_month.month]} #{selected_month.year}"
        tags << month_link(options[:next_text], next_month, options[:params], {:class => "next-month"})

        tags.join.html_safe
      end
    end

    # Generates the link to next and previous months
    def month_link(text, date, params, opts={})
      link_to(text, params.merge({:month => date.month, :year => date.year}), opts)
    end
  end
end