module JamRuby
  class ActiveMusicSession < ActiveRecord::Base

    @@log = Logging.logger[ActiveMusicSession]

    self.primary_key = 'id'

    self.table_name = 'active_music_sessions'

    attr_accessor :legal_terms, :max_score, :opening_jam_track, :opening_recording, :opening_backing_track, :opening_metronome, :jam_track_id

    belongs_to :claimed_recording, :class_name => "JamRuby::ClaimedRecording", :foreign_key => "claimed_recording_id", :inverse_of => :playing_sessions
    belongs_to :claimed_recording_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_claimed_recordings, :foreign_key => "claimed_recording_initiator_id"

    belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :foreign_key => "jam_track_id", :inverse_of => :playing_sessions
    belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "jam_track_initiator_id"

    belongs_to :backing_track_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "backing_track_initiator_id"
    belongs_to :metronome_initiator, :class_name => "JamRuby::User", :inverse_of => :playing_jam_tracks, :foreign_key => "metronome_initiator_id"

    has_one :music_session, :class_name => "JamRuby::MusicSession", :foreign_key => 'music_session_id'
    has_one :mount, :class_name => "JamRuby::IcecastMount", :inverse_of => :music_session, :foreign_key => 'music_session_id'
    belongs_to :creator, :class_name => 'JamRuby::User', :foreign_key => :user_id

    has_many :connections, :class_name => "JamRuby::Connection", foreign_key: :music_session_id
    has_many :users, :through => :connections, :class_name => "JamRuby::User"
    has_many :recordings, :class_name => "JamRuby::Recording", :inverse_of => :music_session, foreign_key: :music_session_id
    has_many :chats, :class_name => "JamRuby::ChatMessages", :foreign_key => "session_id"
    validates :creator, :presence => true
    validate :creator_is_musician
    validate :validate_opening_recording, :if => :opening_recording
    validate :validate_opening_jam_track, :if => :opening_jam_track
    validate :validate_opening_backing_track, :if => :opening_backing_track

    # not sure if this is helpful since if one opens, it always stays open
    validate :validate_opening_metronome, :if => :opening_metronome

    after_create :started_session

    after_destroy do |obj|
      JamRuby::MusicSession.removed_music_session(obj.id)
    end

    #default_scope :select => "*, 0 as score"

    def attributes
      super.merge('max_score' => self.max_score)
    end

    def max_score
      nil unless has_attribute?(:max_score)
      read_attribute(:max_score).to_i
    end

    before_create :create_uuid
    def create_uuid
       #self.id = SecureRandom.uuid
    end

    def before_destroy
      feed = Feed.find_by_music_session_id(self.id)
      unless feed.nil?
        feed.active = false
        feed.save
      end
      self.mount.destroy if self.mount
    end

    def creator_is_musician
      unless creator.musician?
        errors.add(:creator, ValidationMessages::MUST_BE_A_MUSICIAN)
      end
    end

    def validate_opening_recording
      unless claimed_recording_id_was.nil?
        errors.add(:claimed_recording, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
      end

      if is_jam_track_open?
        errors.add(:claimed_recording, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
      end

      if is_backing_track_open?
        errors.add(:claimed_recording, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
      end

      if is_metronome_open?
        errors.add(:claimed_recording, ValidationMessages::METRONOME_ALREADY_OPEN)
      end
    end

    def validate_opening_jam_track
      validate_other_audio(:jam_track)      
    end

    def validate_opening_backing_track
      validate_other_audio(:backing_track)
    end

    def validate_opening_metronome
      validate_other_audio(:metronome)
    end

    def validate_other_audio(error_key)
      # validate that there is no backing track already open in this session
      if backing_track_path_was.present?
        errors.add(error_key, ValidationMessages::BACKING_TRACK_ALREADY_OPEN)
      end

      # validate that there is no jam track already open in this session
      if jam_track_id_was.present?
        errors.add(error_key, ValidationMessages::JAM_TRACK_ALREADY_OPEN)
      end

      # validate that there is no recording being made
      if is_recording?
        errors.add(error_key, ValidationMessages::RECORDING_ALREADY_IN_PROGRESS)
      end

      # validate that there is no recording being played back to the session
      if is_playing_recording?
        errors.add(error_key, ValidationMessages::CLAIMED_RECORDING_ALREADY_IN_PROGRESS)
      end
    end

    # returns an array of client_id's that are in this session
    # if as_musician is nil, all connections in the session ,regardless if it's a musician or not or not
    # you can also exclude a client_id from the returned set by setting exclude_client_id
    def get_connection_ids(options = {})
      as_musician = options[:as_musician]
      exclude_client_id = options[:exclude_client_id]

      where = { :music_session_id => self.id }
      where[:as_musician] = as_musician unless as_musician.nil?

      exclude = "client_id != '#{exclude_client_id}'"unless exclude_client_id.nil?

      Connection.select(:client_id).where(where).where(exclude).map(&:client_id)
    end

    # This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true
    # If so, then it's an OR condition. If both are false, you can get sessions with anyone.
    def self.index(current_user, options = {})
      participants = options[:participants]
      genres = options[:genres]
      keyword = options[:keyword]
      friends_only = options[:friends_only].nil? ? false : options[:friends_only]
      my_bands_only = options[:my_bands_only].nil? ? false : options[:my_bands_only]
      as_musician = options[:as_musician].nil? ? true :  options[:as_musician]

      query = ActiveMusicSession
      .joins(
          %Q{
            INNER JOIN
              music_sessions
            ON
              active_music_sessions.id = music_sessions.id
          }
      )
      .joins(
          %Q{
            INNER JOIN
              connections
            ON
              active_music_sessions.id = connections.music_session_id
          }
      )
      .joins(
          %Q{
            LEFT OUTER JOIN 
              friendships 
            ON 
              connections.user_id = friendships.user_id 
            AND 
              friendships.friend_id = '#{current_user.id}'
          }
      )
      .joins(
          %Q{
            LEFT OUTER JOIN
              invitations
            ON
              invitations.music_session_id = active_music_sessions.id
            AND
              invitations.receiver_id = '#{current_user.id}'
          }
      )
      .group(
          %Q{
            active_music_sessions.id
          }
      )
      .order(
          %Q{
            SUM(CASE WHEN invitations.id IS NULL THEN 0 ELSE 1 END) DESC, 
            SUM(CASE WHEN friendships.user_id IS NULL THEN 0 ELSE 1 END) DESC, 
            active_music_sessions.created_at DESC
          }
      )

      query = Search.scope_schools_together_sessions(query, current_user)

      if as_musician
        query = query.where(
            %Q{
                musician_access = true
              OR
                invitations.id IS NOT NULL
            }
        )
      else
        # if you are trying to join the session as a fan/listener,
        # we have to have a mount, fan_access has to be true, and we have to allow for the reload of icecast to have taken effect
        query = query.joins('INNER JOIN icecast_mounts ON icecast_mounts.music_session_id = active_music_sessions.id INNER JOIN icecast_servers ON icecast_mounts.icecast_server_id = icecast_servers.id')
        query = query.where('music_sessions.fan_access = true')
        query = query.where("(active_music_sessions.created_at < icecast_servers.config_updated_at)")
      end

      query = query.where("music_sessions.description like '%#{keyword}%'") unless keyword.nil?
      query = query.where("connections.user_id" => participants.split(',')) unless participants.nil?
      query = query.where("music_sessions.genre_id in (?)", genres) unless genres.nil?

      if my_bands_only
        query = query.joins(
            %Q{
            LEFT OUTER JOIN
              bands_musicians
            ON
              bands_musicians.user_id = '#{current_user.id}'
          }
        )
      end

      if my_bands_only || friends_only
        query = query.where(
            %Q{
            #{friends_only ? "friendships.user_id IS NOT NULL" : "false"}
            OR
              #{my_bands_only ? "bands_musicians.band_id = music_sessions.band_id" : "false"}
            }
        )
      end

      return query
    end

    # all sessions that are private and active, yet I can see
    def self.friend_active_index(user, options)

      session_id = options[:session_id]
      genre = options[:genre]
      lang = options[:lang]
      keyword = options[:keyword]
      offset = options[:offset]
      limit = options[:limit]

      query = MusicSession.select('music_sessions.*')

      query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id")

      # one flaw in the join below is that we only consider if the creator of the session has asked you to be your friend, but you have not accepted.  While this means you may always see a session by someone you haven't friended, the goal is to match up new users more quickly
      query = query.joins(
          %Q{

            LEFT OUTER JOIN
              rsvp_requests
            ON rsvp_requests.music_session_id = active_music_sessions.id and rsvp_requests.user_id = '#{user.id}' AND rsvp_requests.chosen = true

            LEFT OUTER JOIN
              invitations
            ON
              active_music_sessions.id = invitations.music_session_id AND invitations.receiver_id = '#{user.id}'
            LEFT OUTER JOIN
              friendships
            ON
              active_music_sessions.user_id = friendships.user_id AND friendships.friend_id = '#{user.id}'
            LEFT OUTER JOIN
              friendships as friendships_2
            ON
              active_music_sessions.user_id = friendships_2.friend_id AND friendships_2.user_id = '#{user.id}'
          }
      )

      # keep only rsvp/invitation/friend results.  Nice tailored active list now!
      query = query.where("rsvp_requests.id IS NOT NULL OR invitations.id IS NOT NULL or active_music_sessions.user_id = '#{user.id}' OR (friendships.id IS NOT NULL AND friendships_2.id IS NOT NULL)")

      # if not specified, default offset to 0
      offset ||= 0
      offset = offset.to_i
      # if not specified, default limit to 20
      limit ||= 20
      limit = limit.to_i

      query = query.offset(offset)
      query = query.limit(limit)
      query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
      query = query.where('music_sessions.language = ?', lang) unless lang.blank?
      query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
      query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))",  ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
      query = query.group("music_sessions.id, active_music_sessions.created_at")
      query = query.order("active_music_sessions.created_at DESC")
      query
    end

    # all sessions that are private and active, yet I can see
      def self.public_index(user, options)

      session_id = options[:session_id]
      genre = options[:genre]
      lang = options[:lang]
      keyword = options[:keyword]
      offset = options[:offset]
      limit = options[:limit]

      query = MusicSession.select('music_sessions.*')

      query = query.joins("INNER JOIN active_music_sessions ON music_sessions.id = active_music_sessions.id")

      query = query.where("musician_access = TRUE")

      query = Search.scope_schools_together_sessions(query, user)

      # if not specified, default offset to 0
      offset ||= 0
      offset = offset.to_i
      # if not specified, default limit to 20
      limit ||= 20
      limit = limit.to_i

      query = query.offset(offset)
      query = query.limit(limit)
      query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
      query = query.where('music_sessions.language = ?', lang) unless lang.blank?
      query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
      query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))",  ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?
      query = query.group("music_sessions.id, active_music_sessions.created_at")
      query = query.order("active_music_sessions.created_at DESC")
      query
    end


    # This is a little confusing. You can specify *BOTH* friends_only and my_bands_only to be true
    # If so, then it's an OR condition. If both are false, you can get sessions with anyone.
    # note, this is mostly the same as above but includes paging through the result and and scores.
    # thus it needs the client_id...
    def self.nindex(current_user, options = {})
      client_id = options[:client_id]
      participants = options[:participants]
      genres = options[:genres]
      keyword = options[:keyword]
      friends_only = options[:friends_only].nil? ? false : options[:friends_only]
      my_bands_only = options[:my_bands_only].nil? ? false : options[:my_bands_only]
      as_musician = options[:as_musician].nil? ? true :  options[:as_musician]
      offset = options[:offset]
      limit = options[:limit]

      connection = Connection.where(client_id: client_id).first!
      locidispid = connection.locidispid

      query = ActiveMusicSession
      .select("active_music_sessions.*, max(coalesce(current_scores.score, 1000)) as max_score")  # 1000 is higher than the allowed max of 999
      .joins(
          %Q{
            INNER JOIN
              music_sessions
            ON
              active_music_sessions.id = music_sessions.id
          }
      )
      .joins(
          %Q{
            INNER JOIN
              connections
            ON
              active_music_sessions.id = connections.music_session_id
          }
      )
      .joins(
          %Q{
            LEFT OUTER JOIN
              current_scores
            ON
              current_scores.alocidispid = connections.locidispid
            AND
              current_scores.blocidispid = #{locidispid}
          }
      )
      .joins(
          %Q{
            LEFT OUTER JOIN
              friendships
            ON
              connections.user_id = friendships.user_id
            AND
              friendships.friend_id = '#{current_user.id}'
          }
      )
      .joins(
          %Q{
            LEFT OUTER JOIN
              invitations
            ON
              invitations.music_session_id = active_music_sessions.id
            AND
              invitations.receiver_id = '#{current_user.id}'
          }
      )
      .group(
          %Q{
            active_music_sessions.id
          }
      )
      .order(
          %Q{
            SUM(CASE WHEN invitations.id IS NULL THEN 0 ELSE 1 END) DESC,
            SUM(CASE WHEN friendships.user_id IS NULL THEN 0 ELSE 1 END) DESC,
            active_music_sessions.created_at DESC
          }
      )

      query = Search.scope_schools_together_sessions(query, current_user)

      query = query.offset(offset) if offset
      query = query.limit(limit) if limit

      if as_musician
        query = query.where(
            %Q{
                musician_access = true
              OR
                music_sessions.user_id = '#{current_user.id}'
              OR
                invitations.id IS NOT NULL
            }
        )
      else
        # if you are trying to join the session as a fan/listener,
        # we have to have a mount, fan_access has to be true, and we have to allow for the reload of icecast to have taken effect
        query = query.joins('INNER JOIN icecast_mounts ON icecast_mounts.music_session_id = active_music_sessions.id INNER JOIN icecast_servers ON icecast_mounts.icecast_server_id = icecast_servers.id')
        query = query.where('music_sessions.fan_access = true')
        query = query.where("(active_music_sessions.created_at < icecast_servers.config_updated_at)")
      end

      query = query.where("music_sessions.description like '%#{keyword}%'") unless keyword.nil?
      query = query.where("connections.user_id" => participants.split(',')) unless participants.nil?
      query = query.where("music_sessions.genre_id in (?)", genres) unless genres.nil?

      if my_bands_only
        query = query.joins(
            %Q{
            LEFT OUTER JOIN
              bands_musicians
            ON
              bands_musicians.user_id = '#{current_user.id}'
          }
        )
      end

      if my_bands_only || friends_only
        query = query.where(
            %Q{
            #{friends_only ? "friendships.user_id IS NOT NULL" : "false"}
            OR
              #{my_bands_only ? "bands_musicians.band_id = music_sessions.band_id" : "false"}
            }
        )
      end

      return query
    end

    # initialize the two temporary tables we use to drive ams_index and ams_users
    def self.ams_init(current_user, options = {})
      my_locidispid = current_user.last_jam_locidispid
      # 13 is an average audio gear value we use if they have not qualified any gear
      my_audio_latency = current_user.last_jam_audio_latency || 13
      locidispid_expr = my_locidispid ? "#{my_locidispid}::bigint" : '0::bigint'

      self.connection.execute("select ams_index('#{current_user.id}'::varchar, #{locidispid_expr}, #{my_audio_latency}::integer)").check
    end

    # Generate a list of music sessions (that are active) filtered by genre, language, keyword, and sorted
    # (and tagged) by rsvp'd (1st), invited (2nd), and musician can join (3rd). within a group tagged the
    # same, sorted by score. date seems irrelevant as these are active sessions. ams_init must be called
    # first.
    def self.ams_query(current_user, options = {})
      session_id = options[:session_id]
      client_id = options[:client_id]
      genre = options[:genre]
      lang = options[:lang]
      keyword = options[:keyword]
      offset = options[:offset]
      limit = options[:limit]
      day = options[:day]
      timezone_offset = options[:timezone_offset]

      query = MusicSession
        .select('music_sessions.*')

      # this is not really needed when ams_music_session_tmp is joined
      # unless there is something specific we need out of active_music_sessions
      # query = query.joins(
      #   %Q{
      #     INNER JOIN
      #       active_music_sessions
      #     ON
      #       active_music_sessions.id = music_sessions.id
      #   }
      # )
      # .select('1::integer as tag, 15::integer as latency')

      # integrate ams_music_session_tmp into the processing
      # then we can join ams_music_session_tmp and not join active_music_sessions
      query = query.joins(
        %Q{
          INNER JOIN
            ams_music_session_tmp
          ON
            ams_music_session_tmp.music_session_id = music_sessions.id
        }
      )
      .select('ams_music_session_tmp.tag, ams_music_session_tmp.latency')

      query = query.order(
        %Q{
          tag, latency, music_sessions.id
        }
      )
      .group(
        %Q{
          tag, latency, music_sessions.id
        }
      )

      # if not specified, default offset to 0
      offset ||= 0
      offset = offset.to_i
      # if not specified, default limit to 20
      limit ||= 20
      limit = limit.to_i

      query = query.offset(offset)
      query = query.limit(limit)
      query = query.where("music_sessions.genre_id = ?", genre) unless genre.blank?
      query = query.where('music_sessions.language = ?', lang) unless lang.blank?
      query = query.where('music_sessions.id = ?', session_id) unless session_id.blank?
      query = query.where("(description_tsv @@ to_tsquery('jamenglish', ?))",  ActiveRecord::Base.connection.quote(keyword) + ':*') unless keyword.blank?

      if !day.blank? && !timezone_offset.blank?
        begin
          day = Date.parse(day)
          next_day = day + 1
          timezone_offset = timezone_offset.to_i
          if timezone_offset > 0
            timezone_offset = "+#{timezone_offset}"
          end
          query = query.where("scheduled_start BETWEEN TIMESTAMP WITH TIME ZONE '#{day} 00:00:00#{timezone_offset}'
                    AND TIMESTAMP WITH TIME ZONE '#{next_day} 00:00:00#{timezone_offset}'")
        rescue Exception => e
          # do nothing. bad date probably
          @@log.warn("unable to parse  day=#{day}, timezone_offset=#{timezone_offset}, e=#{e}")
        end
      end

      return query
    end

    # returns the set of users in a music_sessions and the music_session they are in and their latency.
    # ams_init must be called first.
    # user.audio_latency / 2 , + other_user.audio_latency of them / 2, + network latency /2
    def self.ams_users
      return User.select('users.*, ams_users_tmp.music_session_id, ams_users_tmp.full_score, ams_users_tmp.audio_latency, ams_users_tmp.internet_score')
      .joins(
        %Q{
          INNER JOIN
            ams_users_tmp
          ON
            ams_users_tmp.user_id = users.id
        }
      )
      .order('ams_users_tmp.music_session_id, ams_users_tmp.user_id')
    end

    # NOTE: unused anymore!
    #
    # wrap me in a transaction!
    # note that these queries must be actualized before the end of the transaction
    # else the temporary tables created by sms_init will be gone.
    def self.ams_index(current_user, params)
      ActiveMusicSession.ams_init(current_user, params)

      music_sessions = ActiveMusicSession.ams_query(current_user, params).all

      music_session_users = ActiveMusicSession.ams_users.all

      user_scores = {}
      music_session_users.each do |user|
        user_scores[user.id] = {full_score: user.full_score,  audio_latency: user.audio_latency, internet_score: user.internet_score}
      end

      [music_sessions, user_scores]
    end

    def self.participant_create(user, music_session_id, client_id, as_musician, tracks, audio_latency, client_role = nil, parent_client_id = nil, video_sources=nil)
      music_session = MusicSession.find(music_session_id)

      # USERS ARE ALREADY IN SESSION
      if music_session.active_music_session
        connection = nil
        active_music_session = music_session.active_music_session
        
        ActiveRecord::Base.transaction do

          active_music_session.with_lock do # VRFS-1297
            active_music_session.tick_track_changes
            Notification.send_tracks_changed(active_music_session)
            # VRFS-3986
            connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources)

            if connection.errors.any?
              # rollback the transaction to make sure nothing is disturbed in the database
              raise ActiveRecord::Rollback
            end
          end
        end

        unless connection.errors.any?
          user.update_progression_field(:first_music_session_at)
          MusicSessionUserHistory.save(music_session_id, user.id, client_id, tracks)

          if as_musician
            # send to session participants
            Notification.send_session_join(active_music_session, connection, user)

            # send "musician joined session" notification only if it's not a band session since there will be a "band joined session" notification
            if music_session.band.nil?
              Notification.send_musician_session_join(music_session, user)
            end
          end
        end

        connection

      # FIRST USER TO JOIN SESSION
      else
        return_value = nil

        time = Benchmark.realtime do
          ActiveRecord::Base.transaction do

            # we need to lock the icecast server in this transaction for writing, to make sure thath IcecastConfigWriter
            # doesn't dumpXML as we are changing the server's configuraion
            icecast_server = IcecastServer.find_best_server_for_user(user) if music_session.fan_access
            icecast_server.lock! if icecast_server

            music_session.running_recordings.each do |recording|
              recording.stop
            end

            # check if we are connected to rabbitmq
            active_music_session = ActiveMusicSession.new
            active_music_session.id = music_session.id # copy the .id from music_session to active_music_session
            active_music_session.creator = user
            active_music_session.school_id = user.school_id
            active_music_session.is_platform_instructor = user.is_platform_instructor

            if music_session.fan_access
              # create an icecast mount since regular users can listen in to the broadcast
              active_music_session.mount = IcecastMount.build_session_mount(music_session, active_music_session, icecast_server)
            end

            active_music_session.save

            unless active_music_session.errors.any?
              music_session.started_at = active_music_session.created_at
              music_session.save(:validate => false)

              # save session parameters for next session
              User.save_session_settings(user, music_session)

              # auto-join this user into the newly created session
              as_musician = true
              connection = ConnectionManager.new.join_music_session(user, client_id, active_music_session, as_musician, tracks, audio_latency, client_role, parent_client_id, video_sources)

              unless connection.errors.any?
                user.update_progression_field(:first_music_session_at)
                MusicSessionUserHistory.save(active_music_session.id, user.id, client_id, tracks)

                Notification.send_session_join(active_music_session, connection, user)

                # only send this notification if it's a band session
                unless music_session.band.nil?
                  Notification.send_band_session_join(music_session, music_session.band)
                else
                  Notification.send_musician_session_join(music_session, user)
                end

                return_value = connection
              else
                return_value = connection
                # rollback the transaction to make sure nothing is disturbed in the database
                raise ActiveRecord::Rollback
              end
            else
              return_value = active_music_session
              # rollback the transaction to make sure nothing is disturbed in the database
              raise ActiveRecord::Rollback
            end
          end
        end

        if time > 2
          Logging.logger[self].warn "creating a music session took #{time*1000} milliseconds"
        end

        return_value
      end
    end

    # Verifies that the specified user can delete this music session
    def can_delete? user
      # the creator can delete
      self.creator == user
    end

    def access? user
      music_session.part_of_session? user
    end

    def most_recent_recording
      recordings.where(:music_session_id => self.id).order('created_at desc').limit(1).first
    end

    def is_jam_track_open?
      !self.jam_track.nil?
    end

    def is_backing_track_open?
      self.backing_track_path.present?
    end

    def is_metronome_open?
      self.metronome_active.present?
    end

    # is this music session currently recording?
    def is_recording?
      recordings.where(:duration => nil).count > 0
    end

    def is_playing_recording?
      !self.claimed_recording.nil?
    end

    def recording
      recordings.where(:duration => nil).first
    end

    # stops any active recording
    def stop_recording
      current_recording = self.recording
      current_recording.stop unless current_recording.nil?
    end

    def claimed_recording_start(owner, claimed_recording)
      self.claimed_recording = claimed_recording
      self.claimed_recording_initiator = owner
      self.opening_recording = true
      self.save
      self.opening_recording = false
    end

    def claimed_recording_stop
      self.claimed_recording = nil
      self.claimed_recording_initiator = nil
      self.save
    end

    def friends_can_join
      music_session.friends_can_join
    end

    def invitations
      music_session.invitations
    end

    def invited_musicians
      music_session.invited_musicians
    end

    def join_requests
      music_session.join_requests
    end

    def fan_invitations
      music_session.fan_invitations
    end

    def to_s
      description
    end

    def is_lesson_member?(user)
      music_session.is_lesson_member?(user)
    end

    def musician_access
      music_session.musician_access
    end

    def fan_access
      music_session.fan_access
    end

    def description
      music_session.description
    end

    def name
      music_session.name
    end

    def genre
      music_session.genre
    end

    def fan_chat
      music_session.fan_chat
    end

    def band
      music_session.band
    end

    def approval_required
      music_session.approval_required
    end

    def music_notations
      music_session.music_notations
    end

    def music_session_id_int
      music_session.music_session_id_int
    end
    # Verifies that the specified user can join this music session
    def can_join? user, as_musician
      music_session.can_join? user, as_musician
    end

    # Verifies that the specified user can see this music session
    def can_see? user
      music_session.can_see? user
    end

    def tick_track_changes
      self.track_changes_counter += 1
      self.save!(:validate => false)
    end

    def connected_participant_count
      Connection.where(:music_session_id => self.id, 
                       :aasm_state => Connection::CONNECT_STATE.to_s,
                       :as_musician => true)
        .count
    end

    def started_session
      raise "active_music_sessions.id must be set by caller" unless self.id
      # associate this active_music_session with the music_session formally
      session = MusicSession.find(self.id)
      session.active_music_session = self
      self.music_session = session # needed because of the Google Analytics below in some test cases
      session.scheduled_start = self.created_at unless session.scheduled_start
      session.save!

      feed = Feed.find_by_music_session_id(self.id)

      # this should never be hit since the feed entry is created when the music_session record is created
      if feed.nil?
        feed = Feed.new
        feed.music_session_id = self.id
      end

      feed.school_id = self.school_id
      feed.is_platform_instructor = self.is_platform_instructor
      feed.active = true
      feed.save

      #GoogleAnalyticsEvent.track_session_duration(self)
      #GoogleAnalyticsEvent.track_band_real_session(self)
    end

    def open_jam_track(user, jam_track)
      self.jam_track = jam_track
      self.jam_track_initiator = user
      self.opening_jam_track = true
      self.save
      self.opening_jam_track = false

      JamTrackSession.create_session(jam_track, user, self.music_session) if jam_track && user
      #self.tick_track_changes
    end

    def close_jam_track
      self.jam_track = nil
      self.jam_track_initiator = nil
      self.save
    end

    # @param backing_track_path is a relative path:
    def open_backing_track(user, backing_track_path)
      self.backing_track_path = backing_track_path
      self.backing_track_initiator = user
      self.opening_backing_track = true
      self.save
      self.opening_backing_track = false
    end

    def close_backing_track
      self.backing_track_path = nil
      self.backing_track_initiator = nil
      self.save
    end

    def open_metronome(user)
      self.metronome_active = true
      self.metronome_initiator = user
      self.opening_metronome = true
      self.save
      self.opening_metronome = false
    end

    def close_metronome
      self.metronome_active = false
      self.metronome_initiator = nil
      self.save
    end

    def play_time_remaining(user)
      rules = SubscriptionDefinitions.rules(user.subscription_plan_code)
      play_time_per_session = rules[:play_time_per_session]
      if play_time_per_session.nil?
        nil
      else
        (play_time_per_session * 3600) - MusicSessionUserHistory.where(music_session_id: self.id).where(user_id: user.id).sum("extract('epoch' from (COALESCE(session_removed_at, NOW()) - created_at))")
      end
    end

    def self.sync(session_history)
      music_session = MusicSession.find_by_id(session_history.id)

      if music_session.nil?
        music_session = MusicSession.new
        music_session.id = session_history.id
      end

      music_session.user_id = session_history.creator.id
      music_session.band_id = session_history.band.id unless session_history.band.nil?
      session_history.save!
    end

    def self.stats
      stats = {}

      result = ActiveMusicSession.select('count(distinct(id)) AS total, count(distinct(jam_track_initiator_id)) as jam_track_count, count(distinct(backing_track_initiator_id)) as backing_track_count, count(distinct(metronome_initiator_id)) as metronome_count, count(distinct(claimed_recording_initiator_id)) as recording_count')[0]

      stats['count'] = result['total'].to_i
      stats['jam_track_count'] = result['jam_track_count'].to_i
      stats['backing_track_count'] = result['backing_track_count'].to_i
      stats['metronome_count'] = result['metronome_count'].to_i
      stats['recording_count'] = result['recording_count'].to_i

      stats
    end

    def lesson_session
      music_session.lesson_session
    end
  end
end