module Jekyll

  class Post
    include Comparable
    include Convertible

    class << self
      attr_accessor :lsi
    end

    attr_accessor :site, :date, :slug, :ext, :published, :data, :content, :output, :tags
    attr_writer :categories

    def categories
      @categories ||= []
    end

    # Initialize this Post instance.
    #   +site+ is the Site
    #   +base+ is the String path to the dir containing the post file
    #   +name+ is the String filename of the post file
    #   +categories+ is an Array of Strings for the categories for this post
    #
    # Returns <Post>
    def initialize(site, source, dir, name)
      @site = site
      @base = File.join(source, dir, '_posts')
      @name = name

      if File.file?(File.join(@base, name))
        self.read_yaml(@base, name)
      else
        self.data = {}
      end

      self.categories = dir.split('/').reject { |x| x.empty? }

      if self.data.has_key?('published') && self.data['published'] == false
        self.published = false
      else
        self.published = true
      end

      if self.data.has_key?("tag")
        self.tags = [self.data["tag"]]
      elsif self.data.has_key?("tags")
        self.tags = self.data['tags']
      else
        self.tags = []
      end

      if self.categories.empty?
        if self.data.has_key?('category')
          self.categories << self.data['category']
        elsif self.data.has_key?('categories')
          # Look for categories in the YAML-header, either specified as
          # an array or a string.
          if self.data['categories'].kind_of? String
            self.categories = self.data['categories'].split
          else
            self.categories = self.data['categories']
          end
        end
      end

      self.process(name) if published?
    end

    # Spaceship is based on Post#date, slug
    #
    # Returns -1, 0, 1
    def <=>(other)
      cmp = self.date <=> other.date
      if 0 == cmp
       cmp = self.slug <=> other.slug
      end
      return cmp
    end

    # Returns <Bool>
    def published?
      published
    end

    # Post validator. Every post must have e.g. a title.
    #
    # Returns <Bool>
    def valid?
      (self.ext && self.slug && self.date && self.url) ? true : false
    end

    # Extract information from the post filename
    #   +name+ is the String filename of the post file
    #
    # Returns nothing
    def process(nn)
      name = nn.dup
      if name.sub!(/(\d+-\d+-\d+)-/, '')
        self.date = Time.parse($1)
      else
        if date = self.data["date"]
          self.date = date.kind_of?(Time) ? date : Time.parse(date)
        end
      end
      matcher = /^(.+\/)*(.*)(\.[^.]+)$/
      m, cats, slug, self.ext = *name.match(matcher)
      if self.data && self.data["title"]
        self.slug = self.data["title"].split.collect { |i| i.downcase }.join("-")
      else
        self.slug = slug
      end
    end

    # The generated directory into which the post will be placed
    # upon generation. This is derived from the permalink or, if
    # permalink is absent, set to the default date
    # e.g. "/2008/11/05/" if the permalink style is :date, otherwise nothing
    #
    # Returns <String>
    def dir
      File.dirname(url)
    end

    # The full path and filename of the post.
    # Defined in the YAML of the post body
    # (Optional)
    #
    # Returns <String>
    def permalink
      self.data && self.data['permalink']
    end

    def template
      case self.site.permalink_style
      when :pretty
        "/:categories/:year/:month/:day/:title/"
      when :none
        "/:categories/:title.html"
      when :date
        "/:categories/:year/:month/:day/:title.html"
      else
        self.site.permalink_style.to_s
      end
    end

    # The generated relative url of this post
    # e.g. /2008/11/05/my-awesome-post.html
    #
    # Returns <String>
    def url
      return permalink if permalink

      @url ||= {
        "year"       => date.strftime("%Y"),
        "month"      => date.strftime("%m"),
        "day"        => date.strftime("%d"),
        "title"      => CGI.escape(slug),
        "categories" => categories.sort.join('/')
      }.inject(template) { |result, token|
        result.gsub(/:#{token.first}/, token.last)
      }.gsub(/\/\//, "/")
    end

    # The UID for this post (useful in feeds)
    # e.g. /2008/11/05/my-awesome-post
    #
    # Returns <String>
    def id
      File.join(self.dir, self.slug)
    end

    # Calculate related posts.
    #
    # Returns [<Post>]
    def related_posts(posts)
      return [] unless posts.size > 1

      if self.site.lsi
        self.class.lsi ||= begin
          puts "Running the classifier... this could take a while."
          lsi = Classifier::LSI.new
          posts.each { |x| $stdout.print(".");$stdout.flush;lsi.add_item(x) }
          puts ""
          lsi
        end

        related = self.class.lsi.find_related(self.content, 11)
        related - [self]
      else
        (posts - [self])[0..9]
      end
    end

    # Add any necessary layouts to this post
    #   +layouts+ is a Hash of {"name" => "layout"}
    #   +site_payload+ is the site payload hash
    #
    # Returns nothing
    def render(layouts, site_payload)
      # construct payload
      payload =
      {
        "site" => { "related_posts" => related_posts(site_payload["site"]["posts"]) },
        "page" => self.to_liquid
      }
      payload = payload.deep_merge(site_payload)

      do_layout(payload, layouts)
    end

    # Write the generated post file to the destination directory.
    #   +dest+ is the String path to the destination dir
    #
    # Returns nothing
    def write(dest)
      FileUtils.mkdir_p(File.join(dest, dir))

      # The url needs to be unescaped in order to preserve the correct filename
      path = File.join(dest, CGI.unescape(self.url))

      if template[/\.html$/].nil?
        FileUtils.mkdir_p(path)
        path = File.join(path, "index.html")
      end

      File.open(path, 'w') do |f|
        f.write(self.output)
      end
    end

    # Convert this post into a Hash for use in Liquid templates.
    #
    # Returns <Hash>
    def to_liquid
      { "title"      => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
        "url"        => self.url,
        "date"       => self.date,
        "id"         => self.id,
        "categories" => self.categories,
        "next"       => self.next,
        "previous"   => self.previous,
        "tags"       => self.tags,
        "content"    => self.content }.deep_merge(self.data)
    end

    def inspect
      "<Post: #{self.id}>"
    end

    def next
      pos = self.site.posts.index(self)

      if pos && pos < self.site.posts.length-1
        self.site.posts[pos+1]
      else
        nil
      end
    end

    def previous
      pos = self.site.posts.index(self)
      if pos && pos > 0
        self.site.posts[pos-1]
      else
        nil
      end
    end
  end

end