module Commontator
  class Thread < ActiveRecord::Base
    belongs_to :closer, :polymorphic => true
    belongs_to :commontable, :polymorphic => true

    has_many :comments, :dependent => :destroy
    has_many :subscriptions, :dependent => :destroy

    validates_presence_of :commontable, :unless => :is_closed?
    validates_uniqueness_of :commontable_id,
                            :scope => :commontable_type,
                            :allow_nil => true

    def config
      @config ||= commontable.try(:commontable_config) || Commontator
    end

    def will_paginate?
      return false if config.comments_per_page.nil? || !comments.respond_to?(:paginate)
      require 'commontator/link_renderer'
      true
    end

    def is_filtered?
      will_paginate? || config.comment_filter
    end

    def filtered_comments
      cf = config.comment_filter
      return comments if cf.nil?
      comments.where(cf)
    end

    def ordered_comments(unfiltered = false)
      vc = unfiltered ? comments : filtered_comments
      case config.comment_order.to_sym
      when :l then vc.order('created_at DESC')
      when :e then vc.order('created_at ASC')
      when :ve then vc.order('cached_votes_down - cached_votes_up', 'created_at ASC')
      when :vl then vc.order('cached_votes_down - cached_votes_up', 'created_at DESC')
      else vc
      end
    end

    def paginated_comments(page = 1, per_page = config.comments_per_page)
      oc = ordered_comments
      return oc unless will_paginate?
      oc.paginate(:page => page, :per_page => per_page)
    end

    def new_comment_page(per_page = config.comments_per_page)
      return 1 if per_page.nil? || per_page.to_i <= 0
      comment_index = \
        case config.comment_order.to_sym
        when :l
          1 # First comment
        when :ve
          comment_arel = Comment.arel_table
          # Last comment with rating = 0
          filtered_comments.where((comment_arel[:cached_votes_up] - comment_arel[:cached_votes_down]).gteq 0).count
        when :vl
          comment_arel = Comment.arel_table
          # First comment with rating = 0
          filtered_comments.where((comment_arel[:cached_votes_up] - comment_arel[:cached_votes_down]).gt 0).count + 1
        else
          filtered_comments.count # Last comment
        end
      (comment_index.to_f/per_page.to_i).ceil
    end

    def is_closed?
      !closed_at.blank?
    end

    def close(user = nil)
      return false if is_closed?
      self.closed_at = Time.now
      self.closer = user
      save
    end

    def reopen
      return false unless is_closed? && !commontable.nil?
      self.closed_at = nil
      save
    end

    def subscribers
      subscriptions.collect{|s| s.subscriber}
    end

    def subscription_for(subscriber)
      return nil if !subscriber || !subscriber.is_commontator
      subscriber.subscriptions.where(:thread_id => self.id).first
    end

    def subscribe(subscriber)
      return false if subscriber.no_email == true
      return false unless subscriber.is_commontator && !subscription_for(subscriber)
      subscription = Subscription.new
      subscription.subscriber = subscriber
      subscription.thread = self
      subscription.save
    end

    def unsubscribe(subscriber)
      subscription = subscription_for(subscriber)
      return false unless subscription
      subscription.destroy
    end

    def mark_as_read_for(subscriber)
      subscription = subscription_for(subscriber)
      return false unless subscription
      subscription.touch
    end

    # Creates a new empty thread and assigns it to the commontable
    # The old thread is kept in the database for archival purposes
    def clear
      return if commontable.blank? || !is_closed?
      new_thread = Thread.new
      new_thread.commontable = commontable

      with_lock do
        self.commontable = nil
        save!
        new_thread.save!
        subscriptions.each do |s|
          s.thread = new_thread
          s.save!
        end
      end
    end

    ##################
    # Access Control #
    ##################

    # Reader capabilities (user can be nil or false)
    def can_be_read_by?(user)
      return true if can_be_edited_by?(user)
      !commontable.nil? &&\
      config.thread_read_proc.call(self, user)
    end

    # Thread moderator capabilities
    def can_be_edited_by?(user)
      !commontable.nil? && !user.nil? && user.is_commontator &&\
      config.thread_moderator_proc.call(self, user)
    end

    def can_subscribe?(user)
      thread_sub = config.thread_subscription.to_sym
      !is_closed? && !user.nil? && user.is_commontator &&\
      (thread_sub == :m || thread_sub == :b) &&\
      can_be_read_by?(user)
    end
  end
end
