require 'set'
require 'fileutils'

module Hobo
  
  module Dryml
    
    class DrymlGenerator
      
      TEMPLATES = "#{HOBO_ROOT}/dryml_generators"
      OUTPUT    = "#{RAILS_ROOT}/app/views/taglibs/auto"
      
      HEADER = "<!-- AUTOMATICALLY GENERATED FILE - DO NOT EDIT -->\n\n"
      
      def self.run
        @generator ||= DrymlGenerator.new
        @generator.run
      end
      
      
      def initialize
        @templates = {}
        @digests   = {}
        load_templates
      end
      
      attr_accessor :subsite
      
      
      def load_templates
        Dir["#{TEMPLATES}/**/*.dryml.erb"].each do |f|
          name = f[TEMPLATES.length + 1..-11]
          erb = File.read(f)
          @templates[name] = ERB.new(erb, nil, '-').src
          
          # Create output directories and parents as required
          [nil, *Hobo.subsites].each do |s| 
            FileUtils.mkdir_p(File.dirname("#{output_dir s}/#{name}"))
          end
        end
      end
      
      
      def run
        [nil, *Hobo.subsites].each { |s| run_for_subsite(s) }
      end
      
      
      def run_for_subsite(subsite)
        self.subsite = subsite
        @templates.each_pair do |name, src|
          run_one(name, src)
        end        
      end
      
      
      def output_dir(s=subsite)
        s ? "#{OUTPUT}/#{s}" : OUTPUT
      end
      
      
      def run_one(name, src)
        dryml = instance_eval(src, name)
        if dryml_changed?(name, dryml)
          out = HEADER + dryml
          File.open("#{output_dir}/#{name}.dryml", 'w') { |f| f.write(out) }
        end
      end
      
      
      def dryml_changed?(name, dryml)
        key = "#{subsite}/#{name}"
        d = digest dryml
        if d != @digests[key]
          @digests[key] = d
          true
        else
          false
        end
      end
      
      
      def digest(s)
        OpenSSL::Digest::Digest.digest('sha1', s)
      end
      
      
      # --- Helper methods for the templates --- #
      
      attr_reader :controller
      
      
      def controllers
        Hobo::ModelController.all_controllers(subsite).sort_by &:name
      end
      
      
      def models 
        Hobo::Model.all_models.sort_by &:name
      end
      
      def each_controller
        controllers.each do |controller|
          @controller = controller
          yield
        end
        @controller = nil
      end
      
      
      def each_model
        models.each do |model|
          @model = model
          yield
        end
        @model = nil
      end
      
      
      def model
        @model || @controller.model
      end
      
      
      def model_name(*options)
        name = model.name
        name = name.pluralize                                 if :plural.in?(options)
        name = name.titleize                                  if :title.in?(options)
        name = name.titleize.downcase                         if :lowercase.in?(options)
        name = name.underscore.gsub('_', '-').gsub('/', '--') if :dashed.in?(options)
        name
      end
      
      
      def model_class
        model_name(:dashed)
      end

      
      def primary_collection_name(klass=model)
        dependent_collection_names = klass.reflections.values.select do |refl|
          refl.macro == :has_many && refl.options[:dependent]
        end.*.name

        (dependent_collection_names - through_collection_names(klass)).first
      end


      def through_collection_names(klass=model)
        klass.reflections.values.select do |refl|
          refl.macro == :has_many && refl.options[:through]
        end.map {|x| x.options[:through]}
      end


      def linkable?(*args)
        options = args.extract_options!
        options[:subsite] = subsite
        klass, action = if args.length == 1
                          [model, args.first]
                        else
                          args
                        end
        Hobo::ModelRouter.linkable?(klass, action, options)
      end  


      def sortable_collection?(collection, model=self.model)
        # There's no perfect way to detect for this, given that acts_as_list
        # does not provide any metadata to reflect on, but if the :order
        # option is the same as the target classes position_column, that's a
        # pretty safe bet
        if defined? ActiveRecord::Acts::List::InstanceMethods
          refl = model.reflections[collection]
          klass = refl.klass
          klass < ActiveRecord::Acts::List::InstanceMethods && 
            klass.new.position_column == refl.options[:order].to_s
        end
      end
      
      
      def standard_fields(*args)
        klass = args.first.is_a?(Class) ? args.shift : model
        extras = args
        
        fields = klass.attr_order.*.to_s & klass.content_columns.*.name

        fields -= %w{created_at updated_at created_on updated_on deleted_at} unless extras.include?(:include_timestamps)
        
        bt = extras.include?(:belongs_to)
        hm = extras.include?(:has_many)
        klass.reflections.values.sort_by { |refl| refl.name.to_s }.map do |refl|
          fields << refl.name.to_s if bt && refl.macro == :belongs_to
          fields << refl.name.to_s if hm && refl.macro == :has_many
        end

        fields.reject! { |f| model.never_show? f }
        fields
      end
      
      
      def a_or_an(word)
        (word =~ /^[aeiou]/i ? "an " : "a ") + word
      end
            
    end
    
  end
  
end