class Post
  include Mongoid::Document
  include Mongoid::Timestamps
  include Tanker if defined? Tanker

  STATES = ['draft', 'published']

  # Scopes =========================================================================================
  scope :drafts,      :where => {:state => 'draft'}
  scope :published,   :where => {:state => 'published'}, :descending => :publication_date
  scope :by_slug, lambda {|slug| {:where => {:slug.in => ["#{slug}".gsub('//','/'), "/#{slug}/".gsub('//','/')] } } }

  # Mongoid ========================================================================================
  field :author
  field :content
  field :meta_keywords
  field :publication_date, :type => DateTime
  field :published_at, :type => Date
  field :slug
  field :state
  field :summary
  field :tags, :type => Array
  field :title
  field :title_short # because "title" is often too long for a decent layout
  index :slug
  index :state, :unique => false
  belongs_to :blog
  has_and_belongs_to_many :blog_categories

  # Behavior =======================================================================================
  before_save :set_slug
  validates_uniqueness_of :slug

  # Tanker =========================================================================================
  tankit 'idx' do
    indexes :author
    indexes :content
    indexes :summary
    indexes :tags
    indexes :title
  end if defined? Tanker

  after_destroy :delete_tank_indexes unless Rails.application.config.tanker_disabled
  after_save :update_tank_indexes unless Rails.application.config.tanker_disabled

  # Validations ====================================================================================
  validates_presence_of :content, :title

  # Instance methods: Overrides ====================================================================

  # Returns this post's publication date, defaulting to published-at.
  #
  # @return [DateTime] publication or published-at date
  def publication_date
    self[:publication_date] || self.published_at
  end

  # Instance methods ===============================================================================

  # Returns true if this post is an unpublished draft.
  #
  # @return [Boolean] true if draft
  def draft?
    self.state == 'draft' || self.state.nil?
  end

  # Joins this post to its associated blog categories, insuring data integrity at both ends of the
  # join.
  def fix_blog_category_join
    self.blog_categories.each do |cat|
      cat.post_ids << self.id
      cat.save
    end
  end

  # @deprecated Please use {#path} instead
  def full_path
    warn "[DEPRECATION] `full_path` is deprecated.  Please use `path` instead."
    self.path
  end

  # @deprecated Please use {#path} instead
  def humanize_path
    warn "[DEPRECATION] `humanize_path` is deprecated.  Please use `path` instead."
    self.path
  end

  # Returns the index of this post in the blog's collection of posts.
  #
  # @return [Fixnum] the path for this post
  def my_index
    self.blog.posts.published.to_a.index self
  end

  # Returns the next post in the blog.
  #
  # @return [Post] the next post
  def next_post
    return if draft?
    i = self.my_index - 1
    self.blog.posts.published[i]
  end

  # Returns this post's path.
  #
  # @return [String] the path for this post
  def path
    "#{self.blog.path}/#{self.slug}".gsub('//', '/')
  end

  # Returns this post's path with a trailing slash.
  #
  # @return [String] the path for this post
  def path_ts
    "#{self.path}/"
  end

  # Returns the previous post in the blog.
  #
  # @return [Post] the previous post
  def previous_post
    return if draft?
    i = self.my_index + 1
    i = 0 if i == self.blog.posts.published.size
    self.blog.posts.published[i]
  end

  # Publishes this post so it's publically available.
  def publish!
    self.update_attributes :state => 'published', :published_at => Time.zone.now
  end

  # Returns whether this post is publically available.
  #
  # @return [Boolean] true if published
  def published?
    self.state == 'published'
  end

  # Returns the first 200 characters of this post's summary, suitable for use as a meta description.
  #
  # @return [String] search description
  def search_description
    self.summary.gsub(/<\/?[^>]*>/, '')[0..199].html_safe
  end

  # Returns this post's title.
  #
  # @return [String] title
  def search_title
    self.title
  end

  # Reverts this post's status to draft so it is no longer publically available.
  def unpublish!
    self.update_attributes :state => 'draft'
  end

  # Sets the specified state for this post, forcing lowercase.
  #
  # @param [String] state
  def state=(arg)
    self[:state] = arg.downcase
  end

  # Returns this post's status.
  #
  # @return [String] status
  def status
    self.state ? self.state.capitalize : 'Draft'
  end

  private

  def set_slug
    self.slug = self.title.to_s.to_url if self.slug.blank?
  end
end