class ListController < ApplicationController

  skip_before_filter :check_xhr

  @@categories = [
    # filtered topics lists
    Discourse.filters.map { |f| "category_#{f}".to_sym },
    Discourse.filters.map { |f| "category_none_#{f}".to_sym },
    Discourse.filters.map { |f| "parent_category_category_#{f}".to_sym },
    Discourse.filters.map { |f| "parent_category_category_none_#{f}".to_sym },
    # top summaries
    :category_top,
    :category_none_top,
    :parent_category_category_top,
    # top pages (ie. with a period)
    TopTopic.periods.map { |p| "category_top_#{p}".to_sym },
    TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym },
    TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym },
    # category feeds
    :category_feed,
  ].flatten

  before_filter :set_category, only: @@categories

  before_filter :ensure_logged_in, except: [
    :topics_by,
    # anonymous filters
    Discourse.anonymous_filters,
    Discourse.anonymous_filters.map { |f| "#{f}_feed".to_sym },
    # anonymous categorized filters
    Discourse.anonymous_filters.map { |f| "category_#{f}".to_sym },
    Discourse.anonymous_filters.map { |f| "category_none_#{f}".to_sym },
    Discourse.anonymous_filters.map { |f| "parent_category_category_#{f}".to_sym },
    Discourse.anonymous_filters.map { |f| "parent_category_category_none_#{f}".to_sym },
    # category feeds
    :category_feed,
    # top summaries
    :top,
    :category_top,
    :category_none_top,
    :parent_category_category_top,
    # top pages (ie. with a period)
    TopTopic.periods.map { |p| "top_#{p}".to_sym },
    TopTopic.periods.map { |p| "category_top_#{p}".to_sym },
    TopTopic.periods.map { |p| "category_none_top_#{p}".to_sym },
    TopTopic.periods.map { |p| "parent_category_category_top_#{p}".to_sym },
  ].flatten

  # Create our filters
  Discourse.filters.each do |filter|
    define_method(filter) do |options = nil|
      list_opts = build_topic_list_options
      list_opts.merge!(options) if options
      user = list_target_user

      if filter == :latest && params[:category].blank?
        list_opts[:no_definitions] = true
      end

      list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
      list.more_topics_url = construct_next_url_with(list_opts)
      list.prev_topics_url = construct_prev_url_with(list_opts)
      if Discourse.anonymous_filters.include?(filter)
        @description = SiteSetting.site_description
        @rss = filter
      end
      respond(list)
    end

    define_method("category_#{filter}") do
      self.send(filter, { category: @category.id })
    end

    define_method("category_none_#{filter}") do
      self.send(filter, { category: @category.id, no_subcategories: true })
    end

    define_method("parent_category_category_#{filter}") do
      self.send(filter, { category: @category.id })
    end

    define_method("parent_category_category_none_#{filter}") do
      self.send(filter, { category: @category.id })
    end
  end

  Discourse.anonymous_filters.each do |filter|
    define_method("#{filter}_feed") do
      discourse_expires_in 1.minute

      @title = "#{SiteSetting.title} - #{I18n.t("rss_description.#{filter}")}"
      @link = "#{Discourse.base_url}/#{filter}"
      @description = I18n.t("rss_description.#{filter}")
      @atom_link = "#{Discourse.base_url}/#{filter}.rss"
      @topic_list = TopicQuery.new(nil, order: 'activity').public_send("list_#{filter}")

      render 'list', formats: [:rss]
    end
  end

  [:topics_by, :private_messages, :private_messages_sent, :private_messages_unread].each do |action|
    define_method("#{action}") do
      list_opts = build_topic_list_options
      target_user = fetch_user_from_params
      guardian.ensure_can_see_private_messages!(target_user.id) unless action == :topics_by
      list = generate_list_for(action.to_s, target_user, list_opts)
      url_prefix = "topics" unless action == :topics_by
      list.more_topics_url = url_for(construct_next_url_with(list_opts, url_prefix))
      list.prev_topics_url = url_for(construct_prev_url_with(list_opts, url_prefix))
      respond(list)
    end
  end

  def category_feed
    guardian.ensure_can_see!(@category)
    discourse_expires_in 1.minute

    @title = @category.name
    @link = "#{Discourse.base_url}/category/#{@category.slug_for_url}"
    @description = "#{I18n.t('topics_in_category', category: @category.name)} #{@category.description}"
    @atom_link = "#{Discourse.base_url}/category/#{@category.slug_for_url}.rss"
    @topic_list = TopicQuery.new.list_new_in_category(@category)

    render 'list', formats: [:rss]
  end

  def popular_redirect
    # We've renamed popular to latest. Use a redirect until we're sure we can
    # safely remove this.
    redirect_to latest_path, :status => 301
  end

  def top(options = nil)
    discourse_expires_in 1.minute

    top_options = build_topic_list_options
    top_options.merge!(options) if options

    top = generate_top_lists(top_options)

    top.draft_key = Draft::NEW_TOPIC
    top.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
    top.draft = Draft.get(current_user, top.draft_key, top.draft_sequence) if current_user

    respond_to do |format|
      format.html do
        @top = top
        store_preloaded('top_lists', MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false)))
        render 'top'
      end
      format.json do
        render json: MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false))
      end
    end
  end

  def category_top
    options = { category: @category.id }
    top(options)
  end

  def category_none_top
    options = { category: @category.id, no_subcategories: true }
    top(options)
  end

  def parent_category_category_top
    options = { category: @category.id }
    top(options)
  end

  TopTopic.periods.each do |period|
    define_method("top_#{period}") do |options = nil|
      top_options = build_topic_list_options
      top_options.merge!(options) if options
      top_options[:per_page] = SiteSetting.topics_per_period_in_top_page
      user = list_target_user
      list = TopicQuery.new(user, top_options).list_top_for(period)
      list.more_topics_url = construct_next_url_with(top_options)
      list.prev_topics_url = construct_prev_url_with(top_options)
      respond(list)
    end

    define_method("category_top_#{period}") do
      self.send("top_#{period}", { category: @category.id })
    end

    define_method("category_none_top_#{period}") do
      self.send("top_#{period}", { category: @category.id, no_subcategories: true })
    end

    define_method("parent_category_category_#{period}") do
      self.send("top_#{period}", { category: @category.id })
    end
  end

  protected

  def respond(list)
    discourse_expires_in 1.minute

    list.draft_key = Draft::NEW_TOPIC
    list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
    list.draft = Draft.get(current_user, list.draft_key, list.draft_sequence) if current_user

    respond_to do |format|
      format.html do
        @list = list
        store_preloaded('topic_list', MultiJson.dump(TopicListSerializer.new(list, scope: guardian)))
        render 'list'
      end
      format.json do
        render_serialized(list, TopicListSerializer)
      end
    end
  end

  def next_page_params(opts = nil)
    page_params(opts).merge(page: params[:page].to_i + 1)
  end

  def prev_page_params(opts = nil)
    pg = params[:page].to_i
    if pg > 1
      page_params(opts).merge(page: pg - 1)
    else
      page_params(opts).merge(page: nil)
    end
  end


  private

  def page_params(opts = nil)
    opts ||= {}
    route_params = {format: 'json'}
    route_params[:category]        = @category.slug_for_url if @category
    route_params[:parent_category] = @category.parent_category.slug_for_url if @category && @category.parent_category
    route_params[:order]     = opts[:order] if opts[:order].present?
    route_params[:ascending] = opts[:ascending] if opts[:ascending].present?
    route_params
  end

  def set_category
    slug_or_id = params.fetch(:category)
    parent_slug_or_id = params[:parent_category]

    parent_category_id = nil
    if parent_slug_or_id.present?
      parent_category_id = Category.query_parent_category(parent_slug_or_id)
      raise Discourse::NotFound.new if parent_category_id.blank?
    end

    @category = Category.query_category(slug_or_id, parent_category_id)
    raise Discourse::NotFound.new if !@category

    @description_meta = @category.description
    guardian.ensure_can_see!(@category)
  end

  def build_topic_list_options
    # exclude_category = 1. from params / 2. parsed from top menu / 3. nil
    options = {
      page: params[:page],
      topic_ids: param_to_integer_list(:topic_ids),
      exclude_category: (params[:exclude_category] || select_menu_item.try(:filter)),
      category: params[:category],
      order: params[:order],
      ascending: params[:ascending],
      status: params[:status]
    }
    options[:no_subcategories] = true if params[:no_subcategories] == 'true'

    options
  end

  def select_menu_item
    menu_item = SiteSetting.top_menu_items.select do |mu|
      (mu.has_specific_category? && mu.specific_category == @category.try(:slug)) ||
      action_name == mu.name ||
      (action_name.include?("top") && mu.name == "top")
    end.first

    menu_item = nil if menu_item.try(:has_specific_category?) && menu_item.specific_category == @category.try(:slug)
    menu_item
  end

  def list_target_user
    if params[:user_id] && guardian.is_staff?
      User.find(params[:user_id].to_i)
    else
      current_user
    end
  end

  def generate_list_for(action, target_user, opts)
    TopicQuery.new(current_user, opts).send("list_#{action}", target_user)
  end

  def construct_next_url_with(opts, url_prefix = nil)
    method = url_prefix.blank? ? "#{action_name}_path" : "#{url_prefix}_#{action_name}_path"
    public_send(method, opts.merge(next_page_params(opts)))
  end

  def construct_prev_url_with(opts, url_prefix = nil)
    method = url_prefix.blank? ? "#{action_name}_path" : "#{url_prefix}_#{action_name}_path"
    public_send(method, opts.merge(prev_page_params(opts)))
  end

  def generate_top_lists(options)
    top = TopLists.new

    options[:per_page] = SiteSetting.topics_per_period_in_top_summary
    topic_query = TopicQuery.new(current_user, options)

    if current_user.present?
      periods = [ListController.best_period_for(current_user.previous_visit_at, options[:category])]
    else
      periods = TopTopic.periods
    end

    periods.each { |period| top.send("#{period}=", topic_query.list_top_for(period)) }

    top
  end

  def self.best_period_for(previous_visit_at, category_id=nil)
    best_periods_for(previous_visit_at).each do |period|
      top_topics = TopTopic.where("#{period}_score > 0")
      if category_id
        top_topics = top_topics.joins(:topic).where("topics.category_id = ?", category_id)
      end
      return period if top_topics.count >= SiteSetting.topics_per_period_in_top_page
    end
    # default period is yearly
    :yearly
  end

  def self.best_periods_for(date)
    date ||= 1.year.ago
    periods = []
    periods << :daily if date > 8.days.ago
    periods << :weekly if date > 35.days.ago
    periods << :monthly if date > 180.days.ago
    periods << :yearly
    periods
  end

end