# A link between two actors in a relation.
#
# The first Actor is the sender of the Tie. The second Actor is the receiver of the Tie.
#
# == Ties and Activities
# Activities are attached to ties. 
# * The sender of the tie is the target of the Activity. The wall-profile of an actor is
#   composed by the resources assigned to the ties in which the actor is the sender.
# * The receiver actor of the tie is the author of the Activity. It is the user that uploads 
#   a resource to the website or the social entity that originates the activity.
# * The Relation sets up the mode in which the Activity is shared. It sets the rules,
#    or permissions, by which actors have access to the Activity.
#
# == Authorization
# When an actor establishes a tie with other, she is granting a set of permissions to them
# (posting to her wall, reading her posts, etc..) The set of permissions granted are
# associated with the relation of the tie.
#
# == Scopes
# There are several scopes defined:
# * sent_by(actor), ties whose sender is actor
# * received_by(actor), ties whose receiver is actor
# * sent_or_received_by(actor), the union of the former
# * related_by(relation), ties with this relation. Accepts relation, relation_name, integer, array
# * replied, ties having at least another tie in the other way, a tie from a to b is replied if there is a tie from b to a
#
class Tie < ActiveRecord::Base
  attr_accessor :message

  # Facilitates relation assigment along with find_relation callback
  attr_writer :relation_name

  belongs_to :sender,
             :class_name => "Actor",
             :include => SocialStream.subjects
  belongs_to :receiver,
             :class_name => "Actor",
             :include => SocialStream.subjects

  belongs_to :relation

  has_many :tie_activities, :dependent => :destroy
  has_many :activities, :through => :tie_activities

  scope :recent, order("#{ quoted_table_name }.created_at DESC")

  scope :sent_by, lambda { |a|
    where(:sender_id => Actor.normalize_id(a))
  }

  scope :received_by, lambda { |a|
    where(:receiver_id => Actor.normalize_id(a))
  }

  scope :sent_or_received_by, lambda { |a|
    where(arel_table[:sender_id].eq(Actor.normalize_id(a)).
          or(arel_table[:receiver_id].eq(Actor.normalize_id(a))))

  }

  scope :related_by, lambda { |r|
    where(:relation_id => Relation.normalize_id(r))
  }

  scope :replied, lambda {
    select("DISTINCT ties.*").
      from("ties, ties as ties_2").
      where("ties.sender_id = ties_2.receiver_id AND ties.receiver_id = ties_2.sender_id")
  }

  validates_presence_of :sender_id, :receiver_id, :relation_id

  before_validation :find_relation

  after_create :complete_weak_set

  def relation_name
    @relation_name || relation.try(:name)
  end

  def relation!
    relation ||
      find_relation
  end

  def sender_subject
    sender.try(:subject)
  end

  def receiver_subject
    receiver.try(:subject)
  end

  # Does this tie have the same sender and receiver?
  def reflexive?
    sender_id == receiver_id
  end

  # The set of ties between sender and receiver
  #
  # Options::
  # * relations: Only ties with relations
  def relation_set(options = {})
    set = self.class.where(:sender_id => sender_id,
                           :receiver_id => receiver_id)

    if options.key?(:relations)
      set = 
        set.related_by Relation.normalize_id(options[:relations],
                                             :sender => sender)
    end

    set
  end

  # The tie with relation r inside this relation_set
  def related(r)
    relation_set(:relations => r).first
  end

  def activity_receivers
    # TODO
    Array.new
  end

  # = Access Control
  #
  # Access control enforcement in ties come from the permissions assigned to other ties through relations.
  # The access_set is the set of ties that grant some permission on a particular tie.
  #
  # Enforcing access control on activities and ties are a matter of finding its access set.
  # There are two approaches for this, checking the permissions on particular tie or finding all the ties
  # granted some permission.
  #
  # == Particular tie
  # ------------------        ------------------
  # | particular tie |--------|   access_set   |
  # |       t        |        |      ties      |
  # ------------------        ------------------
  #
  # Because t is given, the scopes are applied to the ties table
  # We get the set of ties that allow permission on t
  #
  # == Finding ties
  # ------------------  join  ------------------
  # |  finding ties  |--------|   access_set   |
  # |      ties      |        |     ties_as    |
  # ------------------        ------------------
  #
  # Because we want to find ties, an additional join table (ties_as) is needed for applying access set conditions
  # We get the set of ties that are allowing certain permission
  #

  scope :with_permissions, lambda { |action, object|
    joins(:relation => :permissions).
      where('permissions.action' => action).
      where('permissions.object' => object)
  }

  # Given a given permission (action, object), the access_set are the set of ties that grant that permission.
  scope :access_set, lambda { |tie, action, object|
    with_permissions(action, object).
      where(Permission.parameter_conditions(tie))
  }

  scope :allowing_set, lambda { |action, object|
    query = 
      select("DISTINCT ties.*").
        from("ties INNER JOIN relations ON relations.id = ties.relation_id, ties as ties_as INNER JOIN relations AS relations_as ON relations_as.id = ties_as.relation_id INNER JOIN relation_permissions ON relations_as.id = relation_permissions.relation_id INNER JOIN permissions ON permissions.id = relation_permissions.permission_id").
        where("permissions.action = ?", action).
        where("permissions.object = ?", object)

    conds = Permission.parameter_conditions
    # FIXME: Patch to support public activities
    if action == 'read' && object == 'activity'
      conds = sanitize_sql([ "( #{ conds } ) OR relations.name = ?", "public" ])
    end

    query.where(conds)
  }

  scope :allowing, lambda { |actor, action, object|
    allowing_set(action, object).
      where("ties_as.receiver_id" => Actor.normalize_id(actor))
  }

  def access_set(action, object)
    self.class.access_set(self, action, object)
  end

  def allowing(user, action, object)
    access_set(action, object).received_by(user)
  end

  def allows?(user, action, object)
    allowing(user, action, object).any?
  end

  private

  # Before validation callback
  # Infers relation from its name and the type of the actors
  def find_relation
    if relation_name.present? &&
      relation_name != relation.try(:name) &&
      sender.present?
      self.relation = sender.relation(relation_name)
    end
  end

  # After create callback
  # Creates ties with a weaker relations in the strength hierarchy of this tie
  def complete_weak_set
    return if reflexive?

    relation.weaker.each do |r|
      if relation_set(:relations => r).blank?
        t = relation_set.build :relation => r
        t.save!
      end
    end
  end
end