require "yaml"
require "tilt"

module Middleman::CoreExtensions::FrontMatter
  class << self
    def registered(app)
      app.set :frontmatter_extensions, %w(.htm .html .php)
      app.extend ClassMethods
      app.send :include, InstanceMethods
      app.delegate :frontmatter_changed, :to => :"self.class"
    end
    alias :included :registered
  end
  
  module ClassMethods
    def frontmatter_changed(matcher=nil, &block)
      @_frontmatter_changed ||= []
      @_frontmatter_changed << [block, matcher] if block_given?
      @_frontmatter_changed
    end
  end
  
  module InstanceMethods
    def initialize
      super
      
      exts = frontmatter_extensions.join("|").gsub(".", "\.")
      
      static_path = source_dir.sub(self.root, "").sub(/^\//, "").sub(/\/$/, "") + "/"

      matcher = %r{#{static_path}.*(#{exts})}
      
      self.files.changed matcher do |file|
        self.frontmatter_extension.touch_file(file)
      end

      self.files.deleted matcher do |file|
        self.frontmatter_extension.remove_file(file)
      end

      provides_metadata matcher do |path|
        relative_path = path.sub(source_dir, "")

        fmdata = if frontmatter_extension.has_data?(relative_path)
          frontmatter(relative_path)[0]
        else
          {}
        end

        data = {}
        %w(layout layout_engine).each do |opt|
          data[opt.to_sym] = fmdata[opt] if fmdata.has_key?(opt)
        end

        { :options => data, :page => fmdata }
      end
    end

    def frontmatter_did_change(path)
      frontmatter_changed.each do |callback, matcher|
        next if path.match(%r{^#{build_dir}/})
        next if !matcher.nil? && !path.match(matcher)
        instance_exec(path, &callback)
      end
    end
    
    def frontmatter_extension
      @_frontmatter_extension ||= FrontMatter.new(self)
    end
    
    def frontmatter(*args)
      frontmatter_extension.data(*args)
    end
  end
  
  class FrontMatter
    def initialize(app)
      @app = app
      @source = File.expand_path(@app.source, @app.root)
      @local_data = {}
    end
    
    def has_data?(path)
      @local_data.has_key?(path.to_s)
    end
    
    def touch_file(file)
      extension = File.extname(file).sub(/\./, "")
      return unless ::Tilt.mappings.has_key?(extension)
      
      file = File.expand_path(file, @app.root)
      content = File.read(file)
      
      result = parse_front_matter(content)
        
      if result
        data, content = result
        data = ::Middleman.recursively_enhance(data)
        file = file.sub(@app.source_dir, "")
        @local_data[file] = [data, content]
        path = File.join(@app.source_dir, file)
        @app.cache.set([:raw_template, path], result[1])
        @app.frontmatter_did_change(path)
      end
    end
    
    def remove_file(file)
      file = File.expand_path(file, @app.root)
      file = file.sub(@app.source_dir, "")
      # @app.logger.debug :frontmatter_remove, Time.now, file if @app.logging?
      
      if @local_data.has_key?(file)
        @local_data.delete(file) 
      end
    end
    
    def data(path)
      if @local_data.has_key?(path.to_s)
        @local_data[path.to_s]
      else
        nil
      end
    end
    
  private
    def parse_front_matter(content)
      yaml_regex = /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
      if content =~ yaml_regex
        begin
          data = YAML.load($1)
        rescue => e
          puts "YAML Exception: #{e.message}"
          return false
        end

        content = content.split(yaml_regex).last
      else
        return false
      end

      [data, content]
    end
  end
end
