require 'eventmachine'

class MQRouter

  # monostate pattern:
  # You can initialize MQRouter instances as you want,
  # but ultimately there are internal static state variables to represent global MQ exchange connections

  class << self
    attr_accessor :client_exchange, :user_exchange, :subscription_exchange
    @@log = Logging.logger[MQRouter]
  end

  def access_music_session(music_session, user)
    if music_session.nil?
      raise ArgumentError, 'specified session not found'
    end

    if !music_session.access? user
      raise JamPermissionError, 'not allowed to join the specified session'
    end

    return music_session
  end

  # sends a message to a session on behalf of a user
  # if this is originating in the context of a client, it should be specified as :client_id => "value"
  # client_msg should be a well-structure message (jam-pb message)
  def user_publish_to_session(music_session, user, client_msg, sender = {:client_id => nil})
    access_music_session(music_session, user)

    client_ids = music_session.get_connection_ids(as_musician: true, exclude_client_id: sender[:client_id])

    publish_to_session(music_session.id, client_ids, client_msg.to_s, sender)
  end

  # sends a message to a session from the server
  # no access check as with user_publish_to_session
  # client_msg should be a well-structure message (jam-pb message)
  def server_publish_to_session(music_session, client_msg, sender = {:client_id => nil})
    # gather up client_ids in the session
    client_ids = music_session.get_connection_ids(as_musician: true, exclude_client_id: sender[:client_id])

    publish_to_session(music_session.id, client_ids, client_msg.to_s, sender)
  end

  # sends a message to a session AND fans/listeners from the server
  # client_msg should be a well-structure message (jam-pb message)
  def server_publish_to_everyone_in_session(music_session, client_msg, sender = {:client_id => nil})
    # gather up client_ids in the session
    client_ids = music_session.get_connection_ids(exclude_client_id: sender[:client_id])
    publish_to_session(music_session.id, client_ids, client_msg.to_s, sender)
  end

  # sends a message to a client with no checking of permissions (RAW USAGE)
  # this method deliberately has no database interactivity/active_record objects
  def publish_to_client(client_id, client_msg, sender = {:client_id => ""})
    @@log.error "EM not running in publish_to_client" unless EM.reactor_running?
    EM.schedule do
      sender_client_id = sender[:client_id]

      @@log.debug "publishing to client:#{client_id} from client:#{sender_client_id}"
      # put it on the topic exchange for clients
      self.class.client_exchange.publish(client_msg, :routing_key => "client.#{client_id}")
    end
  end

  # sends a message to all clients
  def publish_to_all_clients(client_msg)
    publish_to_client(MessageFactory::ALL_NATIVE_CLIENTS, client_msg)
  end


  # sends a message to all clients
  def publish_to_active_clients(client_msg)
    publish_to_client(MessageFactory::ALL_ACTIVE_CLIENTS, client_msg)
  end

  # sends a message to a session with no checking of permissions (RAW USAGE)
  # this method deliberately has no database interactivity/active_record objects
  def publish_to_session(music_session_id, client_ids, client_msg, sender = {:client_id => nil})
    @@log.error "EM not running in publish_to_session" unless EM.reactor_running?
    EM.schedule do
      sender_client_id = sender[:client_id]

      # iterate over each person in the session, and send a p2p message
      client_ids.each do |client_id|

        @@log.debug "publishing to session:#{music_session_id} / client:#{client_id} from client:#{sender_client_id}"
        # put it on the topic exchange for clients
        self.class.client_exchange.publish(client_msg, :routing_key => "client.#{client_id}")
      end
    end
  end

  # sends a message to a user with no checking of permissions (RAW USAGE)
  # this method deliberately has no database interactivity/active_record objects
  def publish_to_user(user_id, user_msg, sender = {:client_id => nil})
    @@log.error "EM not running in publish_to_user" unless EM.reactor_running?

    EM.schedule do
      sender_client_id = sender[:client_id]

      @@log.debug "publishing to user:#{user_id} from server from client:#{sender_client_id}"

      # put it on the topic exchange for users
      self.class.user_exchange.publish(user_msg, :routing_key => "user.#{user_id}")
    end
  end

  # sends a message to a list of friends with no checking of permissions (RAW USAGE)
  # this method deliberately has no database interactivity/active_record objects
  def publish_to_friends(friend_ids, user_msg, from_user_id)
    @@log.error "EM not running in publish_to_friends" unless EM.reactor_running?

    EM.schedule do
      friend_ids.each do |friend_id|
        @@log.debug "publishing to friend:#{friend_id} from user/band #{from_user_id}"
        # put it on the topic exchange for users
        self.class.user_exchange.publish(user_msg, :routing_key => "user.#{friend_id}")
      end
    end
  end

  def publish_to_subscription(type, id, msg)
    @@log.error "EM not running in publish_to_subscription" unless EM.reactor_running?

    EM.schedule do
      routing_key = "subscription.#{type}.#{id}"
      @@log.debug "publishing to #{routing_key} from server"
      # put it on the topic exchange for users
      self.class.subscription_exchange.publish(msg, :routing_key => routing_key)
    end
  end
end