module Migrant
  module ModelExtensions
    attr_accessor :schema
    
    def belongs_to(*args)
      super
      create_migrant_schema
      @schema.add_association(self.reflect_on_association(args.first))
    end
        
    def create_migrant_schema
      if self.superclass == ActiveRecord::Base
       @schema ||= Schema.new
      else
        @schema ||= InheritedSchema.new(self.superclass.schema)
      end
    end
    
    def structure(type=nil, &block)
      # Using instance_*evil* to get the neater DSL on the models.
      # So, my_field in the structure block actually calls Migrant::Schema.my_field

      create_migrant_schema
      @structure_defined = true

      if self.superclass == ActiveRecord::Base
        @schema.define_structure(type, &block)
        @schema.validations.each do |field, validation_options|
          validations = (validation_options.class == Array)? validation_options : [validation_options]
          validations.each do |validation|
            validation = (validation.class == Hash)? validation : { validation => true }
            self.validates(field, validation)
          end
        end
        # Set up serialized columns as required
        @schema.columns.select do |name, column|
          if column.serialized?
            serialize(name, column.serialized_class_name)
          end
        end
      else
        self.superclass.structure(&block) # For STI, cascade all fields onto the parent model
      end
    end

    def structure_defined?
      @schema && @structure_defined || false
    end

    # Same as defining a structure block, but with no attributes besides
    # relationships (such as in a many-to-many)
    def no_structure
      structure {}
    end
    
    def reset_structure!
      @schema = nil
    end

    def mock(attributes={}, recursive=true)
      raise NoStructureDefined.new("In order to mock() #{self.to_s}, you need to define a Migrant structure block") unless @schema
 
      attribs = {}
      attribs.merge!(self.superclass.mock_attributes(attributes, recursive)) unless self.superclass == ActiveRecord::Base
      new attribs.merge(mock_attributes(attributes, recursive))
    end
    
    def mock_attributes(attributes={}, recursive=true)
      attribs = @schema.columns.collect { |name, data_type| [name, data_type.mock] }.flatten(1)

      # Only recurse to one level, otherwise things get way too complicated
      if recursive
        attribs += self.reflect_on_all_associations(:belongs_to).collect do |association|
                    begin
                      (association.klass.respond_to?(:mock))? [association.name, association.klass.mock({}, false)] : nil
                    rescue NameError; nil; end # User hasn't defined association, just skip it
                   end.compact.flatten
      end
      Hash[*attribs].merge(attributes)
    end
    
    def mock!(attributes={}, recursive=true)
      mock(attributes, recursive).tap do |mock|
        mock.save!
      end
    end
  end

  class NoStructureDefined < Exception; end;
end

