module IdPlease
  module ModelExtensions  #:nodoc:
    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods
      def acts_as_authorization_subject(options = {})
      	assoc = options[:association_name] || IdPlease.config[:default_association_name]
        role = options[:role_class_name] || IdPlease.config[:default_role_class_name]
        role_assoc = role.downcase.underscore.to_sym
        
        group = options[:group_class_name] || IdPlease.config[:default_group_class_name]
        
        assign = options[:assignment_class_name] || IdPlease.config[:default_assignment_class_name]
        assign_table = assign.constantize.table_name
        
        #TODO
        groups_enabled = options.has_key?(:groups_enabled) ? options[:groups_enabled] : IdPlease.config[:groups_enabled]
        nested_groups = options.has_key?(:nested_groups) ? options[:nested_groups] : IdPlease.config[:nested_groups]
        group_role = options[:group_role] || IdPlease.config[:default_group_role]

        has_many assign_table.to_sym, :class_name => assign, :as => :subject, :dependent => :destroy
        has_many assoc, :through => assign_table.to_sym, :source => role_assoc


        cattr_accessor :_auth_role_class_name, :_auth_subject_class_name,
                       :_auth_groups_enabled, :_auth_nested_groups,
                       :_auth_group_class_name, :_auth_assign_class_name,
                       :_auth_role_assoc_name, :_auth_group_role,
                       :_auth_is_group

        self._auth_role_class_name = role
        self._auth_subject_class_name = self.to_s
        self._auth_assign_class_name = assign
        self._auth_group_class_name = group
        self._auth_role_assoc_name = assoc
        self._auth_group_role = group_role
        self._auth_groups_enabled = groups_enabled
        self._auth_nested_groups = nested_groups
        self._auth_is_group = false
        

        include IdPlease::ModelExtensions::ForSubject
      end
      
      def acts_as_authorization_group(options = {})
      	assoc = options[:association_name] || IdPlease.config[:default_association_name]
        role = options[:role_class_name] || IdPlease.config[:default_role_class_name]
        role_assoc = role.downcase.underscore.to_sym
 
        subject = options[:subject_class_name] || IdPlease.config[:default_subject_class_name]

        assign = options[:assignment_class_name] || IdPlease.config[:default_assignment_class_name]
        assign_table = assign.constantize.table_name
        
        #TODO

        groups_enabled = options.has_key?(:groups_enabled) ? options[:groups_enabled] : IdPlease.config[:groups_enabled]
        nested_groups = options.has_key?(:nested_groups) ? options[:nested_groups] : IdPlease.config[:nested_groups]
        raise "Do not use acts_as_authorization_group if groups are disabled." unless groups_enabled


        group_role = options[:group_role] || IdPlease.config[:default_group_role]

        has_many assign_table.to_sym, :class_name => assign, :as => :subject, :dependent => :destroy
        has_many assoc, :through => :assignments, :class_name => role, :source => role_assoc


        cattr_accessor :_auth_role_class_name, :_auth_subject_class_name,
                       :_auth_groups_enabled, :_auth_nested_groups,
                       :_auth_group_class_name, :_auth_assign_class_name,
                       :_auth_role_assoc_name, :_auth_group_role,
                       :_auth_is_group

        self._auth_role_class_name = role
        self._auth_subject_class_name = subject
        self._auth_assign_class_name = assign
        self._auth_group_class_name = self.to_s
        self._auth_role_assoc_name = assoc
        self._auth_groups_enabled = groups_enabled
        self._auth_nested_groups = nested_groups
        self._auth_group_role = group_role
        self._auth_is_group = true

        include IdPlease::ModelExtensions::ForSubject
        include IdPlease::ModelExtensions::ForGroup
        include IdPlease::ModelExtensions::ForObject
      end
      


      def acts_as_authorization_object(options = {})
        subject = options[:subject_class_name] || IdPlease.config[:default_subject_class_name]
        subj_table = subject.constantize.table_name
      
        role       = options[:role_class_name] || IdPlease.config[:default_role_class_name]
        role_table = role.constantize.table_name
      
        assign = options[:assignment_class_name] || IdPlease.config[:default_assignment_class_name]
        
        groups_enabled = options[:groups_enabled] || IdPlease.config[:groups_enabled]
        nested_groups = options[:nested_groups] || IdPlease.config[:nested_groups]
        group_role = options[:group_role] || IdPlease.config[:group_role]
        
        
        has_many role_table.to_sym, :dependent => :destroy, :as => :authorizable
        
        
        cattr_accessor :_auth_role_class_name, :_auth_groups_enabled, 
                       :_auth_nested_groups, :_auth_assign_class_name,
                       :_auth_group_role

        
                       
        self._auth_role_class_name = role
        self._auth_assign_class_name = assign
        self._auth_group_role = group_role
        self._auth_groups_enabled = groups_enabled
        self._auth_nested_groups = nested_groups
      
      
        include IdPlease::ModelExtensions::ForObject
      end

      def acts_as_authorization_role(options = {})
        validates_presence_of :name
        validates_uniqueness_of :name, :scope => [:authorizable_type, :authorizable_id]
        
        subject = options[:subject_class_name] || IdPlease.config[:default_subject_class_name]

        assign = options[:assignment_class_name] || IdPlease.config[:default_assignment_class_name]
        assign_table = assign.constantize.table_name

        groups_enabled = options[:groups_enabled] || IdPlease.config[:groups_enabled]

        nested_groups = options[:nested_groups] || IdPlease.config[:nested_groups]
        group_role = options[:group_role] || IdPlease.config[:group_role]

        role_table = self.table_name
      
        has_many assign_table.to_sym, :dependent => :destroy
        belongs_to :authorizable, :polymorphic => true


        self.named_scope :authorizable_eq, lambda { |*objs| 
          obj_hash = {}
          cond_sql = []
          cond_parameters = []

          if objs.include?(nil) || objs.empty? 
            cond_sql << "#{role_table}.authorizable_type IS NULL AND #{role_table}.authorizable_id IS NULL"
          end

          objs.compact!

          unless objs.nil? || objs.length == 0
            obj_hash = {}

            objs.each { |obj| 
              obj_hash.has_key?(obj.class) ? obj_hash[obj.class] << obj.id : obj_hash[obj.class] = [obj.id]
            }
            

            obj_hash.each_pair { |class_name, ids|
              cond_sql << "#{role_table}.authorizable_type = '#{class_name}' AND #{role_table}.authorizable_id IN(?)"
              cond_parameters << ids
            }

          end

          {:conditions => [cond_sql.join(" OR "), *cond_parameters].compact} 
        }

        
        include IdPlease::ModelExtensions::ForRole

        
        cattr_accessor :_auth_role_class_name, :_auth_groups_enabled, 
                       :_auth_nested_groups, :_auth_assign_class_name
        
        self._auth_role_class_name = self.to_s
        self._auth_assign_class_name = assign
        
        self._auth_groups_enabled = groups_enabled
        self._auth_nested_groups = nested_groups
        
        

      end


      def acts_as_authorization_assignment(options = {})
        role = options[:role_class_name] || IdPlease.config[:default_role_class_name]
        role_assoc = role.downcase.underscore.to_sym
        
        assignment_table = self.table_name
        
        belongs_to :subject, :polymorphic => true
        #TODO :fix:
        belongs_to role_assoc


        self.named_scope :subject_eq, lambda { |*objs| 
          obj_hash = {}
          cond_sql = []
          cond_parameters = []

          if objs.include?(nil) || objs.empty? 
            cond_sql << "#{assignment_table}.subject_type IS NULL AND #{assignment_table}.subject_id IS NULL"
          end
          
          objs.compact!
          
          unless objs.nil? || objs.length == 0
            obj_hash = {}
          
            objs.each { |obj| 
              obj_hash.has_key?(obj.class) ? obj_hash[obj.class] << obj.id : obj_hash[obj.class] = [obj.id]
            }


            obj_hash.each_pair { |class_name, ids|
              cond_sql << "#{assignment_table}.subject_type = '#{class_name}' AND #{assignment_table}.subject_id IN(?)"
              cond_parameters << ids
            }

          end
          {:conditions => [cond_sql.join(" OR "), *cond_parameters].compact} 

        }
      end

    end
  end
end