class Model  
  def Model.build_and_route(name, route)
    klass = name.to_s.singularize.camelize.constantize rescue nil
    if klass
      klass.routes << route unless (klass.routes.include?(route) or route.blank? or !route.member?(klass)) #FIXME: see why nil is getting called in the first place
      klass
    else
      x = Model.make_model_class(name)
      x.routes << (route.to_a.clone << x)
      x
    end
  end

  def Model.db_type_map
    { # definitely have to add high-level datatypes.  This will need to be partly replicated in generators
      "str" => "string", "string" => "string", "s" => "string",
      "int" => "integer", "integer" => "integer", "i" => "integer",
      "bool" => "boolean", "boolean" => "boolean", "b" => "boolean",
      "text" => "text", "t" => "text",
      "email" => "string", "url" => "string",
      "datetime" => "datetime"
    }
  end
  
  def Model.make_model_class(name)
    # routes is the set of urls by which you can get to an instance of a class
    Object.class_eval "
    class #{ name.to_s.singularize.camelize} < Model
      cattr_accessor :associations, :fields, :options, :routes, :foreign_keys, :validations
      self.associations = { }
      self.fields = { }
      self.options = { }
      self.routes = [] # of the form [:classname, [:otherclass, :classname], ...]
      self.foreign_keys = []
      self.validations = []

      class << self
        # TODO: can some or all of this be moved out of here and into the model class?
        # thanks to Josh Ladieu for this: it's the array of things needed to get to an instance of this class
        def resource_tuple  # this returns the minimal route to this model, or nothing, if there is no unambiguous minimal route
          routelist = self.routes.sort {|x, y| x.length <=> y.length }
          if (routelist.length == 1) || (routelist.first.length == 1) #take the trivial route or the only route
            routelist.first
          else #TODO: maybe should deal with a case where there's a simplest route that all the others contain.
            nil
          end
        end

        def nested_resources
          APP.models.reject {|k,v| v.routes != [(self.resource_tuple + [v])] }
        end
        
        def cs
          self.to_s
        end

        def cp
          self.to_s.pluralize
        end

        def s
          self.to_s.underscore
        end

        def p
          self.to_s.underscore.pluralize
        end
      end
    end"
    name.to_s.singularize.camelize.constantize
  end
  
  class << self
    def nice_name
      self.to_s.underscore
    end

    def sym_name
      self.nice_name.pluralize.to_sym
    end
    
    def default_assoc_name(meth)
      :has_many == meth ? nice_name.pluralize : nice_name
    end

    def validates(validation_type, field, opts = { })
      self.validations << opts.merge({ :val_type => validation_type, :field => field})
    end
    
    def belongs_to(m, o = { }) associate(:belongs_to, m, o) end

    def has_many(m, o = { })
      associate(:has_many, m, o)
      m.associate(:belongs_to, self, o) unless o[:skip_belongs_to]
    end

    def has_one(m, o = { })
      associate(:has_one, m, o)
      m.associate(:belongs_to, self, o) unless o[:skip_belongs_to]
    end

    def hmt(o = { })
      
    end

    def associate(meth, model, options = { })
      assoc_name = options[:assoc_name] || model.default_assoc_name(meth)
      self.foreign_keys << ( assoc_name.to_s + "_id" ) if meth == :belongs_to  #FIXME: might be something other than assoc_name_id
      self.associations[assoc_name] = { :model => model, :name => assoc_name, :type => meth}.merge(options)
    end

    def add_attrs(*args)
      if args.is_a? Array and args.length == 1
        args.first.split.each do |s|
          name, field_type = s.split(":")
          add_field(name, field_type)
        end
      else # TODO: add handling for hashes or arrays
        raise "bad argument type in add_attrs"
      end 
    end

    def add_field(name, field_type)
      self.fields[name] = Model.db_type_map[field_type]
    end
  end
end