ScaffoldingExtensions::MODEL_SUPERCLASSES << DataMapper::Resource

  def get_key_array_safe(key)
    if key.is_a?(Array) then
      if key.length==1
        key.first
      else
        key
      end
    else
      key
    end
  end

  def get_ordering_options(ordopts)
    result = []
    if ordopts then
      ordering = ordopts.dup
    else
      return nil
    end
    ordering = ordering.split(',') unless ordering.is_a?(Array)
    ordering.each do |ord|
      asc = :asc
      if ord.upcase =~ /DESC/
        asc = :desc
      end
      ord.gsub!(/[Dd][Ee][Ss][Cc]|[Aa][Ss][Cc]/,"")
      ord.strip!
      if ord =~ /(\w+)\.(\w+)/
        tablename = $1
        propertyname = $2
        #TODO handling of associated orderings
        #optionshash[:order] << DataMapper::Query::Direction.new(eval("#{tablename.downcase.gsub(/\b[a-z]/) { |a| a.upcase }.gsub(/\s/, "")
        #}.properties[:#{propertyname.downcase}]"),asc)
        result << eval(":#{propertyname}.#{asc.to_s}")
      elsif ord =~ /(\w+)/
        propertyname = $1
        result << eval(":#{propertyname}.#{asc.to_s}")
      else
        # TODO Warning message
      end
    end
    result
  end

# Instance methods added to DataMapper::Resource to allow it to work with Scaffolding Extensions.
module ScaffoldingExtensions::DataMapper
  # Get value for given attribute
  def scaffold_attribute_value(field)
    self[field]
  end

  # the value of the primary key for this object
  def scaffold_id
    get_key_array_safe(self.key)
  end
end

# Class methods added to DataMapper::Resource to allow it to work with Scaffolding Extensions.
module ScaffoldingExtensions::MetaDataMapper
  SCAFFOLD_OPTIONS = ::ScaffoldingExtensions::MetaModel::SCAFFOLD_OPTIONS

  # Add the associated object to the object's association
  def scaffold_add_associated_object(association, object, associated_object)
    ap = object.send(association)
    ap << associated_object unless ap.include?(associated_object)
    object.save
  end

  # Array of all association reflections for this model
  # only shows the associations that are scaffolding_enabled
  def scaffold_all_associations
    relationships.values.select { |v|
      v.send(:target_model).respond_to?(:scaffold_name)
    }
  end

  # The class that this model is associated with via the association
  def scaffold_associated_class(association)
    relationships[association].target_model
  end

  # The association reflection for this association
  def scaffold_association(association)
    relationships[association]
  end

  # The type of association, either :new for :one_to_many (as you can create new objects
  # associated with the current object), :edit for :many_to_many (since you
  # can edit the list of associated objects), or :one for :many_to_one.
  def scaffold_association_type(association)
    if relationships[association].class == DataMapper::Associations::OneToMany::Relationship
        :new
    elsif relationships[association].class == DataMapper::Associations::ManyToMany::Relationship
        :edit
    else
        :one
    end
  end

  # List of symbols for associations to display on the scaffolded edit page. Defaults to
  # all associations for which the scaffolding is enabled. Can be set with an instance variable.
  def scaffold_associations
    @scaffold_associations ||= relationships.keys.select { |v|
      relationships[v].send(:target_model).respond_to?(:scaffold_name)
    }.sort_by{|name| name.to_s}
  end

  # Destroys the object
  def scaffold_destroy(object)
    object.destroy
  end

  # The error to raise, should match other errors raised by the underlying library.
  def scaffold_error_raised
    DataMapper::ObjectNotFoundError
  end

  # Returns the list of fields to display on the scaffolded forms. Defaults
  # to displaying all columns with the exception of the primary key column.
  # Also includes :many_to_one associations, replacing
  # the foriegn keys with the association itself.  Can be set with an instance variable.
  def scaffold_fields(action = :default)
    return @scaffold_fields if @scaffold_fields
    fields = (properties.map {|a| a.name}) - [scaffold_primary_key]
    scaffold_all_associations.each do |reflection|
      next unless reflection.class == DataMapper::Associations::ManyToOne::Relationship
      fields.delete(get_key_array_safe(reflection.send(:child_key)).name)
      fields.push(reflection.name)
    end
    @scaffold_fields = fields.sort_by{|f| f.to_s}
  end

  # The foreign key for the given reflection
  def scaffold_foreign_key(reflection)
    get_key_array_safe(reflection.child_key).name
  end

  # Retrieve a single model object given an id
  def scaffold_get_object(id)
    self.get(id) || (raise scaffold_error_raised)
  end

  # All objects that are currently associated with the given object. This method does not
  # check that the returned associated objects meet the associated class's scaffold_session_value
  # constraint, as it is assumed that all objects currently assocated with the given object
  # have already met the criteria.  If that is not the case, you should override this method.
  def scaffold_associated_objects(association, object, options)
    object.send(association,:order => get_ordering_options(scaffold_select_order_association(association)))
  end

  # Retrieve multiple objects given a hash of options
  def scaffold_get_objects(options)
    optionshash = {}
    data = self.all
    if options[:conditions]
      conditions = options[:conditions]
      if conditions && Array === conditions && conditions.length > 0
        if String === conditions[0]
          data = data.all(:conditions => conditions)
        else
          conditions.each do |cond|
            next if cond.nil?
            data = case cond
              when Hash, String then data.all(:conditions => [cond.gsub("NULL","?"),nil])
              when Array then 
                if cond.length==1
                  data.all(:conditions => [cond[0].gsub("NULL","?"),nil])
                else
                  data.all(:conditions => cond)
                end
              when Proc then data.all(&cond)
            end
          end
        end
      end
    end
    slice = nil
    if options[:limit]
      startpos = options[:offset] || 0
      endpos = options[:limit]
      slice = [startpos,endpos]
    end
    # TODO includes break SQL generation
    # optionshash[:links] = options[:include] if options[:include]
    # optionshash[:links] = [optionshash[:links]] unless optionshash[:links].is_a?(Array)
    if options[:order] then
      optionshash[:order] = get_ordering_options(options[:order])
    end
    if slice then
      q = data.all(optionshash).slice(*slice)
    else
      q = data.all(optionshash)
    end
    #p repository.adapter.send("select_statement",q.query)
    q.to_a
  end

  # Return the class, left foreign key, right foreign key, and join table for this habtm association
  def scaffold_habtm_reflection_options(association)
    habtm = relationships[association]
    [
      habtm.target_model,
      get_key_array_safe(habtm.through.child_key).name,
      get_key_array_safe(habtm.via.child_key).name,
      habtm.send(:through_model).storage_name
    ]
  end

  # Returns a hash of values to be used as url parameters on the link to create a new
  # :has_many associated object.  Defaults to setting the foreign key field to the
  # record's primary key.
  def scaffold_new_associated_object_values(association, record)
    {scaffold_foreign_key(scaffold_association(association))=>record.scaffold_id}
  end

  # The primary key for the given table
  def scaffold_primary_key
    get_key_array_safe(key).name
  end
  
  # Saves the object.
  def scaffold_save(action, object)
    object.save
  end
  
  # The column type for the given table column, or nil if it isn't a table column
  def scaffold_table_column_type(c)
    column = self.properties[c]
    if column then
      if column.type == DataMapper::Property::Text
        :text
      else
        column.class.to_s.split("::").last.downcase.intern
      end
    else
      nil
    end
  end

  # The name of the underlying table
  def scaffold_table_name
    storage_name
  end

  private
    # Updates associated records for a given reflection and from record to point to the
    # to record
    def scaffold_reflection_merge(reflection, from, to)
      if reflection.class == DataMapper::Associations::OneToMany::Relationship
        foreign_key = get_key_array_safe(reflection.target_key).name
        table = reflection.target_model
      elsif reflection.class == DataMapper::Associations::ManyToMany::Relationship
        foreign_key = get_key_array_safe(reflection.through.child_key).name
        table = reflection.send(:through_model)
      else
        return
      end
      table.all(foreign_key => from).update(foreign_key => to)
    end

    # Remove the associated object from object's association
    def scaffold_remove_associated_object(association, object, associated_object)
      object.send(association).delete(associated_object)
      object.save
    end

    # Set the object's attributes with the given attributes
    def scaffold_set_attributes(object, attributes)
      attributes.each do |k,v|
        v = nil if v.empty? and (scaffold_table_column_type(k) == :boolean or scaffold_table_column_type(k) == :integer)
        object.send("#{k}=", v)
      end 
    end 

end

def add_scaffolding_methods(classes)
  unless classes.is_a?(Array)
    classes = [classes]
  end
  classes.each do |cl|
    cl.class_eval <<-ECLASS
      SCAFFOLD_OPTIONS = ::ScaffoldingExtensions::MetaModel::SCAFFOLD_OPTIONS
      include ScaffoldingExtensions::Model
      include ScaffoldingExtensions::DataMapper
      extend ScaffoldingExtensions::MetaModel
      extend ScaffoldingExtensions::MetaDataMapper
      extend ScaffoldingExtensions::Overridable
    ECLASS
  end
end

