module CollectiveIdea
  module Acts
    module NestedSet
      module Model
        module Relatable

          # Returns an collection of all parents
          def ancestors
            without_self self_and_ancestors
          end

          # Returns the collection of all parents and self
          def self_and_ancestors
            nested_set_scope.
              where(arel_table[left_column_name].lteq(left)).
              where(arel_table[right_column_name].gteq(right))
          end

          # Returns the collection of all children of the parent, except self
          def siblings
            without_self self_and_siblings
          end

          # Returns the collection of all children of the parent, including self
          def self_and_siblings
            nested_set_scope.children_of parent_id
          end

          # Returns a set of all of its nested children which do not have children
          def leaves
            descendants.where(
              "#{quoted_right_column_full_name} - #{quoted_left_column_full_name} = 1"
            )
          end

          # Returns the level of this object in the tree
          # root level is 0
          def level
            parent_id.nil? ? 0 : compute_level
          end

          # Returns a collection including all of its children and nested children
          def descendants
            without_self self_and_descendants
          end

          # Returns a collection including itself and all of its nested children
          def self_and_descendants
            # using _left_ for both sides here lets us benefit from an index on that column if one exists
            nested_set_scope.right_of(left).left_of(right)
          end

          def is_descendant_of?(other)
            within_node?(other, self) && same_scope?(other)
          end

          def is_or_is_descendant_of?(other)
            (other == self || within_node?(other, self)) && same_scope?(other)
          end

          def is_ancestor_of?(other)
            within_node?(self, other) && same_scope?(other)
          end

          def is_or_is_ancestor_of?(other)
            (self == other || within_node?(self, other)) && same_scope?(other)
          end

          # Check if other model is in the same scope
          def same_scope?(other)
            Array(acts_as_nested_set_options[:scope]).all? do |attr|
              self.send(attr) == other.send(attr)
            end
          end

          # Find the first sibling to the left
          def left_sibling
            siblings.left_of(left).last
          end

          # Find the first sibling to the right
          def right_sibling
            siblings.right_of(left).first
          end

          def root
            return self_and_ancestors.children_of(nil).first if persisted?

            if parent_id && current_parent = nested_set_scope.find(parent_id)
              current_parent.root
            else
              self
            end
          end

          protected

          def compute_level
            node, nesting = determine_depth

            node == self ? ancestors.count : node.level + nesting
          end

          def determine_depth(node = self, nesting = 0)
            while (association = node.association(:parent)).loaded? && association.target
              nesting += 1
              node = node.parent
            end if node.respond_to?(:association)

            [node, nesting]
          end

          def within_node?(node, within)
            node.left < within.left && within.left < node.right
          end

        end
      end
    end
  end
end
