require_relative './access_group_group_member'
require_relative './access_group_user_member'

module Incline
  class AccessGroup < ActiveRecord::Base

    # hide the Groups<=>Users relationship association
    has_many :access_group_user_members, class_name: 'Incline::AccessGroupUserMember', foreign_key: 'group_id', dependent: :delete_all
    private :access_group_user_members, :access_group_user_members=

    # and expose the users instead.
    has_many :users, class_name: 'Incline::User', through: :access_group_user_members, source: :member

    # hide the Groups<=>Groups relationship association
    has_many :access_group_group_members, class_name: 'Incline::AccessGroupGroupMember', foreign_key: 'group_id', dependent: :delete_all
    private :access_group_group_members, :access_group_group_members=

    # and expose the groups instead.
    has_many :groups, class_name: 'Incline::AccessGroup', through: :access_group_group_members, source: :member

    ##
    # Gets a list of memberships for this group.  (Read-only)
    def memberships(refresh = false)
      @memberships = nil if refresh
      @memberships ||= AccessGroupGroupMember.where(member_id: id).includes(:group).map{|v| v.group}.to_a.freeze
    end

    ##
    # Gets a list of all of the member users for this group.  (Read-only)
    def members(refresh = false)
      @members = nil if refresh
      @members ||= safe_members.sort{|a,b| a.to_s <=> b.to_s}
    end

    validates :name,
              presence: true,
              length: { maximum: 100 },
              uniqueness: { case_sensitive: false }

    scope :sorted, ->{ order(:name) }

    ##
    # Determines if this group belongs to the specified group.
    def belongs_to?(group)
      group = AccessGroup.get(group) unless group.is_a?(::Incline::AccessGroup)
      return false unless group
      safe_belongs_to?(group)
    end

    ##
    # Gets a list of all the groups this group provides effective membership to.
    def effective_groups
      ret = [ self ]
      memberships.each do |m|
        unless ret.include?(m)  # prevent infinite recursion
          tmp = m.effective_groups
          tmp.each do |g|
            ret << g unless ret.include?(g)
          end
        end
      end
      ret.sort{|a,b| a.name <=> b.name}
    end

    ##
    # Gets the user IDs for the members of this group.
    def user_ids
      users.pluck(:id)
    end

    ##
    # Gets the group IDs for the members of this group.
    def group_ids
      groups.pluck(:id)
    end

    ##
    # Sets the user IDs for the members of this group.
    def user_ids=(values)
      values ||= []
      values = [ values ] unless values.is_a?(::Array)
      values = values.reject{|v| v.blank?}.map{|v| v.to_i}
      self.users = Incline::User.where(id: values).to_a
    end

    ##
    # Sets the group IDs for the members of this group.
    def group_ids=(values)
      values ||= []
      values = [ values ] unless values.is_a?(::Array)
      values = values.reject{|v| v.blank?}.map{|v| v.to_i}
      self.groups = Incline::AccessGroup.where(id: values).to_a
    end

    protected

    def safe_belongs_to?(group, already_tried = [])
      return true if self == group
      already_tried << self
      memberships.each do |parent|
        unless already_tried.include?(parent)
          return true if parent.safe_belongs_to?(group, already_tried)
        end
      end
      false
    end

    def safe_members(already_tried = [])
      return [] if already_tried.include?(self)
      already_tried << self
      ret = users.to_a
      groups.each do |g|
        g.send(:safe_members, already_tried).each do |u|
          ret << u unless ret.include?(u)
        end
      end
      ret
    end

  end
end
