#!/usr/bin/env ruby

require 'yaml'
require 'rdiscount'



class Tildir

  #
  # =begin customization variables.
  # Some of these are more important than others.
  #

  def self.config_file_dirs
    [ "~", Dir.pwd ]
  end

  def self.config_file_name
    "tildir.config"
  end

  def self.page_title
    "~mirando"
  end

  def self.style_file
    "css/base.css"
  end

  def self.dir_to_read
    "src"
  end

  def self.file_to_write
    "index.html"
  end

  # Names of the keys in the YAML files.
  # The keys of this hash are used internally. Do not change them.
  # But if you want to use different keys in the YAML documents,
  # then map them to the proper keys here.
  def self.yaml_keys
    {
      :date => 'date',
      :title => 'title',
      :body => 'body'
    }
  end

  # The key to sort the YAML documents by.
  def self.order_key
    "date"
  end

  # Should be :asc or :desc
  def self.sort_order
    :desc
  end


  # Similar to the yaml file keys.
  def self.config_keys
    {
      :page_title => 'page_title',
      :style_file => 'style_file',
      :dir_to_read => 'dir_to_read',
      :file_to_write => 'file_to_write'
    }
  end

  def self.config_defaults
    {
      :page_title => Tildir.page_title,
      :style_file => Tildir.style_file,
      :dir_to_read => Tildir.dir_to_read,
      :file_to_write => Tildir.file_to_write
    }
  end

  # =end customization variables.




  # The arguments should be
  # 1. a directory name to read, and
  # 2. a file name to write to, which is optional.
  #    The default is specified above.

  def initialize( args = [ ] )
    @args = args
    @config = { }

    @dir_to_read, @file_to_write = nil, nil
    @style_file, @page_title = nil, nil

    @files_read, @yaml_docs = [ ], { }
    @html_s = nil

    self.main
  end

  attr_reader :args
  attr_accessor :config, :dir_to_read, :file_to_write, :style_file, :page_title, :files_read, :yaml_docs, :html_s



  def main
    self.parse_args
    self.read_dir
    self.read_files
    self.sort_yaml_docs
    self.html_wrap
    self.write_file
  end



  def get_config
    chk = self.config_file
    f_conf = (chk.nil?) ? { } : YAML.load_file(chk)
    d_conf = Tildir.config_defaults

    Tildir.config_keys.each do |sym,key|
      if f_conf.has_key? key
        self.config[sym] = f_conf[key]
      elsif d_conf.has_key? sym
        self.config[sym] = d_conf[sym]
      else
        self.config[sym] = nil
      end
    end
  end



  def config_file
    ret, f_name = nil, Tildir.config_file_name

    Tildir.config_file_dirs.each do |dir|
      chk = "#{dir}/#{f_name}"
      ret = chk if File.exists? chk
    end

    return ret
  end



  def parse_args
    # For this method to work, get_config must be called.
    # So call it here, rather than in main, just to be sure. 
    self.get_config

    if self.args.is_a? Array
      if !self.args.empty?
        self.args.each do |arg|
          if self.is_dir? arg
            self.dir_to_read = arg
          else
            self.file_to_write = arg            
          end
        end
      end
    end

    self.dir_to_read = self.config[:dir_to_read] if self.dir_to_read.nil?
    self.file_to_write = self.config[:file_to_write] if self.file_to_write.nil?
  end



  def is_dir?(chk = self.dir_to_read)
    ret = (File.directory? chk) ? true : nil
    return ret
  end


  def is_file?(chk = self.file_to_write)
    ret = (File.exists? chk) ? true : nil
    return ret
  end



  def read_dir
    if self.is_dir? self.dir_to_read
      Dir.foreach(self.dir_to_read) do |chk|

        if chk.match(/^.*?\.ya?ml$/)
          # puts "#{chk} matches ya?ml"
          path = "#{self.dir_to_read}/#{chk}"

          if self.is_file?(path)
            # puts "#{chk} is a file"
            self.files_read.push File.absolute_path(path)

          else
            # puts "#{chk} is not a file"
          end

        else
          # puts "#{chk} does not match ya?ml"
        end
      end

    else
      puts "Cannot read directory \"#{self.dir_to_read}\"."
    end
  end



  def read_files
    if !self.files_read.empty?
      n = 0
      self.files_read.each do |f|
        YAML.load_stream(IO.read(f)) do |ydoc|
          n += 1
          self.yaml_docs[n] = ydoc
        end
      end
    end
  end



  def write_file
    fout = File.new(self.file_to_write, 'w')
    fout.puts self.html_s
    fout.close
  end



  def sort_yaml_docs
    if !self.yaml_docs.empty?
      unsorted_h, sort_arr, sorted_arr = { }, [ ], [ ]
      title_key = Tildir.yaml_keys[:title]

      self.yaml_docs.each do |id,doc|
        unsorted_h[id] = doc

        if doc.has_key? Tildir.order_key
          sort_arr.push doc[Tildir.order_key]
        else
          puts "Yikes! YAML doc \"#{doc[title_key]}\" is missing \"#{Tildir.order_key}\" key."
          unsorted_h[id][Tildir.order_key] = nil
        end
      end

      # puts "Unsorted: #{unsorted_h.to_s}"

      sort_arr = sort_arr.uniq.sort
      sort_arr.reverse! if Tildir.sort_order == :desc

      # puts "Sorted arr: #{sort_arr.to_s}"

      sort_arr.each do |sort_key|
        unsorted_h.each do |id,doc|
          if doc[Tildir.order_key] == sort_key
            sorted_arr.push doc
            unsorted_h.delete id
          end
        end
      end

      if !unsorted_h.empty?
        unsorted_h.each do |id,doc|
          sorted_arr.push doc
        end
      end

      # puts "Sorted docs: #{sorted_arr.to_s}"
      self.yaml_docs = sorted_arr
    end
  end




  def html_wrap
    self.html_s = <<HTML
<!doctype html>
<html>
  #{self.html_head}
  #{self.html_body}
</html>
HTML
  end




  def html_head
    ret = <<HTML
<head>
  <title>#{self.config[:page_title]}</title>
	<link rel="stylesheet" type="text/css" href="#{self.config[:style_file]}" />
	<meta name="viewport" content="initial-scale=1, width=device-width" />
</head>
HTML

    return ret
  end



  def html_body
    ret = <<HTML
<body>
  <div id="carapace">
HTML

    keys = Tildir.yaml_keys
    self.yaml_docs.each { |doc| ret << self.wrap_yaml(doc, keys) }

    ret << <<HTML
  </div>
</body>
HTML

    # puts ret

    return ret
  end



  def wrap_yaml( doc = { }, keys = Tildir.yaml_keys )
    ret = ''

    # puts "Wrapping #{doc.to_s} with keys #{keys}"

    if doc.is_a?(Hash) and keys.is_a?(Hash)
      # puts doc.to_s
      ret = <<HTML
<article class="post-case">
  <h1>#{doc[keys[:title]]}</h1>
  #{RDiscount.new(doc[keys[:body]]).to_html.strip}
HTML

      ret << self.wrap_post_date(doc[keys[:date]]) if doc[keys[:date]]

      ret << <<HTML
</article>
HTML
    end

    return ret
  end



  def wrap_post_date( date )
    year, month, day = 0, 0, 0
    chk = date.to_s

    if parts = chk.match(/^([0-9]{4})([0-9]{2})([0-9]{2})$/)
      year, month, day = parts[1], parts[2], parts[3]
      t = Time.new(year, month, day)
    else
      t = Time.now
    end

    ret = <<HTML
  <time datetime="#{t.strftime("%Y-%m-%d")}">#{t.strftime("%d %b %Y")}</time>
HTML
    return ret
  end

end



td = Tildir.new ARGV
exit
