module JamRuby
  class Recording < ActiveRecord::Base

    @@log = Logging.logger[Recording]

    attr_accessible :owner, :owner_id, :band, :band_id, :recorded_tracks_attributes, :mixes_attributes, :claimed_recordings_attributes, :name, :description, :genre, :is_public, :duration, :jam_track_id, as: :admin

    has_many :users, :through => :recorded_tracks, :class_name => "JamRuby::User"
    has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
    has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy
    has_many :quick_mixes, :class_name => "JamRuby::QuickMix", :foreign_key => :recording_id, :dependent => :destroy
    has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id, :dependent => :destroy
    has_many :recorded_videos, :class_name => "JamRuby::RecordedVideo", :foreign_key => :recording_id, :dependent => :destroy
    has_many :recorded_backing_tracks, :class_name => "JamRuby::RecordedBackingTrack", :foreign_key => :recording_id, :dependent => :destroy
    has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :recording_id, :dependent => :destroy
    has_many :comments, :class_name => "JamRuby::RecordingComment", :foreign_key => "recording_id", :dependent => :destroy
    has_many :likes, :class_name => "JamRuby::RecordingLiker", :foreign_key => "recording_id", :dependent => :destroy
    has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
    has_one :feed, :class_name => "JamRuby::Feed", :inverse_of => :recording, :foreign_key => 'recording_id', :dependent => :destroy

    belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings, :foreign_key => 'owner_id'
    belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
    belongs_to :music_session, :class_name => "JamRuby::ActiveMusicSession", :inverse_of => :recordings, foreign_key: :music_session_id
    belongs_to :non_active_music_session, :class_name => "JamRuby::MusicSession", foreign_key: :music_session_id    
    belongs_to :jam_track, :class_name => "JamRuby::JamTrack", :inverse_of => :recordings, :foreign_key => 'jam_track_id'
    belongs_to :jam_track_initiator, :class_name => "JamRuby::User", :inverse_of => :initiated_jam_track_recordings, :foreign_key => 'jam_track_initiator_id'

    accepts_nested_attributes_for :recorded_tracks, :mixes, :claimed_recordings, allow_destroy: true

    has_one :mobile_recording,
            class_name: 'JamRuby::MobileRecording',
            foreign_key: :foreign_key1_id,
            dependent: :destroy

    validate :not_already_recording, :on => :create
    validate :not_still_finalizing_previous, :on => :create
    validate :not_playback_recording, :on => :create
    validate :already_stopped_recording
    validate :only_one_mix

    before_save     :sanitize_active_admin
    before_create   :add_to_feed

    def add_to_feed
      feed = Feed.new
      feed.recording = self
      feed.is_platform_instructor = self.is_platform_instructor
      feed.school_id = self.school_id
    end

    def sanitize_active_admin
      self.owner_id = nil if self.owner_id == ''
      self.band_id = nil if self.band_id == ''
    end

    def comment_count
      self.comments.size
    end

    def is_jamtrack_recording?
      !jam_track_id.nil? && parsed_timeline['jam_track_isplaying']
    end

    def parsed_timeline
      timeline ? JSON.parse(timeline) : {}
    end

    def high_quality_mix?
      has_final_mix
    end

    def has_mix?
      # this is used by the UI to know whether it can show a play button.  We prefer a real mix, but a stream mix will do
      return true if high_quality_mix?
      has_stream_mix
    end

    # this should be a has-one relationship.  until this, this is easiest way to get from recording > mix
    def mix
      self.mixes[0] if self.mixes.length > 0
    end

    def mix_state
      mix.state if mix
    end

    def mix_error
      mix.error if mix
    end

    def stream_mix
      quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
    end

    def mix_state
      mix.state if mix
    end

    def mix_error
      mix.error if mix
    end

    def stream_mix
      quick_mixes.find{|quick_mix| quick_mix.completed && !quick_mix.cleaned }
    end

    # this can probably be done more efficiently, but David needs this asap for a video
    def grouped_tracks
      tracks = []
      sorted_tracks = self.recorded_tracks.sort { |a,b| a.user.id <=> b.user.id }

      t = Track.new
      t.instrument_ids = []
      sorted_tracks.each_with_index do |track, index|
        if index > 0
          if sorted_tracks[index-1].user.id != sorted_tracks[index].user.id
            t = Track.new
            t.instrument_ids = []
            t.instrument_ids << track.instrument.id
            t.musician = track.user
            tracks << t
          else
            if !t.instrument_ids.include? track.instrument.id
              t.instrument_ids << track.instrument.id
            end
          end
        else
          t.musician = track.user
          t.instrument_ids << track.instrument.id
          tracks << t
        end
      end
      tracks
    end

    def not_already_recording
      if music_session && music_session.is_recording?
        errors.add(:music_session, ValidationMessages::ALREADY_BEING_RECORDED)
      end
    end


    # this should be used to cleanup a recording that we detect is no longer running
    def abort
      recording.music_session.claimed_recording_id = nil
      recording.music_session.claimed_recording_initiator_id = nil

      # double check that there are no claims to this recording before destroying it
      unless claimed_recordings.length > 0
        destroy
      end
    end

    def not_still_finalizing_previous
      # after a recording is done, users need to keep or discard it.
      # this checks if the previous recording is still being finalized

      unless !music_session || music_session.is_recording?
        previous_recording = music_session.most_recent_recording
        if previous_recording
          previous_recording.recorded_tracks.each do |recorded_track|
            # if at least one user hasn't taken any action yet...
            if recorded_track.discard.nil?
              # and they are still around and in this music session still...
              connection = Connection.find_by_client_id(recorded_track.client_id)
              if !connection.nil? && connection.music_session == music_session
                errors.add(:music_session, ValidationMessages::PREVIOUS_RECORDING_STILL_BEING_FINALIZED)
                break
              end
            end
          end
        end
      end
    end

    def not_playback_recording
      if music_session && music_session.is_playing_recording?
        errors.add(:music_session, ValidationMessages::ALREADY_PLAYBACK_RECORDING)
      end
    end

    def already_stopped_recording
      if is_done && is_done_was
        errors.add(:music_session, ValidationMessages::NO_LONGER_RECORDING)
      end
    end


    def only_one_mix
      # we leave mixes as has_many because VRFS-1089 was very hard to do with has_one + cocoon add/remove
      if mixes.length > 1
        errors.add(:mixes, ValidationMessages::ONLY_ONE_MIX)
      end
    end

    def recorded_tracks_for_user(user)
      unless self.users.exists?(user.id)
        # THIS CHECK SHOULD PROBABLY COME BACK
        # HOWEVER, THIS WAS KILLING WEBSOCKET-GATEWAY ON SOME DROPPED SCENARIOS
        # PROBABLY WEBSOCKET_GATEWAY SHOULD PASS SECOND ARG THAT RELAXES THE BELOW CHECK
        #raise JamPermissionError, "user was not in this session"
      end
      recorded_tracks.where(:user_id => user.id)
    end

    def recorded_backing_tracks_for_user(user)
      unless self.users.exists?(user.id)
        raise JamPermissionError, "user was not in this session"
      end
      recorded_backing_tracks.where(:user_id => user.id)
    end


    def has_access?(user)
      return false if user.nil?

      users.exists?(user.id) || attached_with_lesson(user) #|| plays.where("player_id=?", user).count != 0
    end

    def attached_with_lesson(user)

      ChatMessage.joins(:claimed_recording => [:recording]).where('recordings.id = ?', self.id).where('chat_messages.user_id = ?', user.id).count > 0 ||
          ChatMessage.joins(:claimed_recording => [:recording]).where('recordings.id = ?', self.id).where('chat_messages.target_user_id = ?', user.id).count > 0
    end

    # creates a recording, and then claims it in one shot.
    def self.create_immediately(owner, params)
      recording = Recording.new
      recording.music_session = nil
      recording.owner = owner
      recording.school_id = owner.school_id
      recording.is_platform_instructor = owner.is_platform_instructor
      recording.band = nil
      recording.immediate = true # immediate in practice means 'the ios app uploaded this'
      recording.video = params[:record_video]

      if recording.save
        QuickMix.create(recording, owner)

        recording.recorded_tracks << RecordedTrack.create_for_immediate(owner, recording)

        recording.save
      end

      if recording.errors.any?
        return recording
      end

      recording.stop

      if recording.errors.any?
        return recording
      end

      recording.reload
      claim = recording.claim(owner, params[:name], params[:description], Genre.find_by_id(params[:genre]), params[:is_public], upload_to_youtube = params[:upload_to_youtube])

      if claim.errors.any?
        return claim
      end

      recording
    end


    # Start recording a session.
    def self.start(music_session, owner, record_video: false)
      recording = nil
      # Use a transaction and lock to avoid races.
      music_session.with_lock do
        recording = Recording.new
        recording.music_session = music_session
        recording.owner = owner
        recording.band = music_session.band
        recording.school_id = owner.school_id
        recording.is_platform_instructor = owner.is_platform_instructor
        recording.video = record_video == true || record_video == 'true'

        if recording.save
          #GoogleAnalyticsEvent.report_band_recording(recording.band)

          # make quick mixes *before* the audio/video tracks, because this will give them precedence in list_uploads
          music_session.users.uniq.each do |user|
            QuickMix.create(recording, user)
          end

          music_session.connections.each do |connection|
            connection.tracks.each do |track|
              if connection.client_role != 'child'
                recording.recorded_tracks << RecordedTrack.create_from_track(track, recording)
              end
            end

            connection.video_sources.each do |video|
              recording.recorded_videos << RecordedVideo.create_from_video_source(video, recording)
            end

            connection.backing_tracks.each do |backing_track|
              recording.recorded_backing_tracks << RecordedBackingTrack.create_from_backing_track(backing_track, recording)
            end
          end

          if music_session.jam_track
            music_session.jam_track.jam_track_tracks.each do |jam_track_track|
              recording.recorded_jam_track_tracks << RecordedJamTrackTrack.create_from_jam_track_track(jam_track_track, recording, owner) if jam_track_track.track_type == 'Track'
            end
            recording.jam_track = music_session.jam_track
            recording.jam_track_initiator = music_session.jam_track_initiator
          end

          recording.save
        end
      end

      recording
    end

    # Stop recording a session
    def stop
      # Use a transaction and lock to avoid races.
      music_session = MusicSession.find_by_id(music_session_id)
      locker = music_session.nil? ? self : music_session
      locker.with_lock do
        self.duration = Time.now - created_at
        self.is_done = true
        self.save
      end

      self
    end

    # Called when a user wants to "claim" a recording. To do this, the user must have been one of the tracks in the recording.
    def claim(user, name, description, genre, is_public, upload_to_youtube=false)
      upload_to_youtube = !!upload_to_youtube # Correct where nil is borking save
      if !self.users.exists?(user.id)
        raise JamPermissionError, "user was not in this session"
      end

      claimed_recording = ClaimedRecording.new 
      claimed_recording.user = user
      claimed_recording.recording = self
      claimed_recording.name = name
      claimed_recording.description = description
      claimed_recording.genre = genre
      claimed_recording.is_public = is_public
      claimed_recording.upload_to_youtube = upload_to_youtube
      self.claimed_recordings << claimed_recording

      if claimed_recording.save
        keep(user)
      end

      claimed_recording
    end

    # the user votes to keep their tracks for this recording
    def keep(user)
      recorded_tracks_for_user(user).update_all(:discard => false)

      Recording.where(:id => id).update_all(:updated_at => Time.now) # updated updated_at for benefit of RecordingsCleaner

      User.where(:id => user.id).update_all(:first_recording_at => Time.now ) unless user.first_recording_at
    end


    # the user votes to discard their tracks for this recording
    def discard(user)
      recorded_tracks_for_user(user).update_all(:discard => true)

      Recording.where(:id => id).update_all(:updated_at => Time.now) # updated updated_at for benefit of RecordingsCleaner

      # check if all recorded_tracks for this recording are discarded
      if recorded_tracks.where('discard = false or discard is NULL').length == 0
        self.all_discarded = true # the feed won't pick this up; also background cleanup will find these and whack them later
        self.save(:validate => false)
      end
    end

    # only discard if the user has previously taken no action
    def discard_if_no_action(user)
      track = recorded_tracks_for_user(user).first
      if track && track.discard.nil?
        discard(user )
      end
    end
  
    # Find out if all the tracks for this recording have been uploaded
    def uploaded?
      self.recorded_tracks.each do |recorded_track|
        return false unless recorded_track.fully_uploaded
      end
      return true
    end

    def self.list_downloads(user, limit = 100, since = 0)
      since = 0 unless since || since == '' # guard against nil
      downloads = []

      # That second join is important. It's saying join off of recordings, NOT user. If you take out the
      # ":recordings =>" part, you'll just get the recorded_tracks that I played. Very different!

      # we also only allow you to be told about downloads if you have claimed the recording
      #User.joins(:recordings).joins(:recordings => :recorded_tracks).joins(:recordings => :claimed_recordings)
      RecordedTrack.joins(:recording).joins(:recording => :claimed_recordings)
      .order('recorded_tracks.id')
      .where('recorded_tracks.fully_uploaded = TRUE')
      .where('recorded_tracks.id > ?', since)
      .where('all_discarded = false')
      .where('deleted = false')
      .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_track|
          downloads.push(
              {
                  :type => "recorded_track",
                  :id => recorded_track.client_track_id,
                  :recording_id => recorded_track.recording_id,
                  :length => recorded_track.length,
                  :md5 => recorded_track.md5,
                  :url => recorded_track[:url],
                  :next => recorded_track.id
              }
          )
      end
      latest_recorded_track = (downloads.length > 0) ? downloads[-1][:next] : 0

      Mix.joins(:recording).joins(:recording => :claimed_recordings)
      .order('mixes.id')
      .where('mixes.completed_at IS NOT NULL')
      .where('mixes.id > ?', since)
      .where('all_discarded = false')
      .where('deleted = false')
      .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user)
      .limit(limit).each do |mix|
        downloads.push(
            {
                :type => "mix",
                :id => mix.id.to_s,
                :recording_id => mix.recording_id,
                :length => mix.ogg_length,
                :md5 => mix.ogg_md5,
                :url => mix.ogg_url,
                :created_at => mix.created_at,
                :next => mix.id
            }
        )
      end
      latest_mix = (downloads.length > 0) ? downloads[-1][:next] : 0

      RecordedBackingTrack.joins(:recording).joins(:recording => :claimed_recordings)
      .order('recorded_backing_tracks.id')
      .where('recorded_backing_tracks.fully_uploaded = TRUE')
      .where('recorded_backing_tracks.id > ?', since)
      .where('recorded_backing_tracks.user_id = ?', user.id) # only the person who opened the backing track can have it back
      .where('all_discarded = false')
      .where('deleted = false')
      .where('claimed_recordings.user_id = ? AND claimed_recordings.discarded = FALSE', user).limit(limit).each do |recorded_backing_track|
          downloads.push(
              {
                  :type => "recorded_backing_track",
                  :id => recorded_backing_track.client_track_id,
                  :recording_id => recorded_backing_track.recording_id,
                  :length => recorded_backing_track.length,
                  :md5 => recorded_backing_track.md5,
                  :url => recorded_backing_track[:url],
                  :next => recorded_backing_track.id
              }
          )
      end
      latest_recorded_backing_track = (downloads.length > 0) ? downloads[-1][:next] : 0

      next_date = [latest_mix, latest_recorded_track, latest_recorded_backing_track].max

      if next_date.nil?
        next_date = since # echo back to the client the same value they passed in, if there are no results
      end

      {
          'downloads' => downloads,
          'next' => next_date.to_s
      }
    end

    def self.list_uploads(user, limit = 100, since = 0)
      since = 0 unless since || since == '' # guard against nil

      uploads = []

      # Uploads now include videos in addition to the tracks.
      # This is accomplished using a SQL UNION query via arel, as follows:

      # Select fields from track.  Note the reorder, which removes
      # the default scope sort as it b0rks the union.  Also note the
      # alias so that we can differentiate tracks and videos when 
      # processing the results:
      track_arel = RecordedTrack.select([
        :id, 
        :recording_id, 
        :user_id,
        :url, 
        :fully_uploaded, 
        :upload_failures, 
        :client_track_id, 
        Arel::Nodes::As.new(Arel::Nodes.build_quoted('track'), Arel.sql('item_type'))
      ]).reorder("")
      
      # Select fields for video. Note that it must include 
      # the same number of fields as the track in order for
      # the union to work:
      vid_arel = RecordedVideo.select([
        :id, 
        :recording_id, 
        :user_id,
        :url, 
        :fully_uploaded, 
        :upload_failures, 
        :client_video_source_id, 
        Arel::Nodes::As.new(Arel::Nodes.build_quoted('video'), Arel.sql('item_type'))
      ]).reorder("")

      # Select fields for quick mix. Note that it must include
      # the same number of fields as the track or video in order for
      # the union to work:
      quick_mix_arel = QuickMix.select([
          :id,
          :recording_id,
          :user_id,
          :ogg_url,
          :fully_uploaded,
          :upload_failures,
          Arel::Nodes::As.new(Arel::Nodes.build_quoted(''), Arel.sql('quick_mix_track_id')),
          Arel::Nodes::As.new(Arel::Nodes.build_quoted('stream_mix'), Arel.sql('item_type'))
      ]).reorder("")

      # Select fields for quick mix. Note that it must include
      # the same number of fields as the track or video in order for
      # the union to work:
      backing_track_arel = RecordedBackingTrack.select([
          :id,
          :recording_id,
          :user_id,
          :url,
          :fully_uploaded,
          :upload_failures,
          :client_track_id,
          Arel::Nodes::As.new(Arel::Nodes.build_quoted('backing_track'), Arel.sql('item_type'))
      ]).reorder("")

      # Glue them together:
      union = track_arel.union(vid_arel)

      utable = Arel::Nodes::TableAlias.new(union, :recorded_items)
      arel = track_arel.from(utable)
      arel = arel.except(:select)

      # Remove the implicit select created by .from.  It
      # contains an ambigious "id" field:
      arel = arel.except(:select)
      arel = arel.select([
                             "recorded_items.id",
                             :recording_id,
                             :user_id,
                             :url,
                             :fully_uploaded,
                             :upload_failures,
                             :client_track_id,
                             :item_type
                         ])

      # And repeat:
      union_quick = arel.union(quick_mix_arel)
      utable_quick = Arel::Nodes::TableAlias.new(union_quick, :recorded_items_quick)
      arel = arel.from(utable_quick)

      arel = arel.except(:select)
      arel = arel.select([
                             "recorded_items_quick.id",
                             :recording_id,
                             :user_id,
                             :url,
                             :fully_uploaded,
                             :upload_failures,
                             :client_track_id,
                             :item_type
                         ])


      # And repeat for backing track:
      union_all = arel.union(backing_track_arel)
      utable_all = Arel::Nodes::TableAlias.new(union_all, :recorded_items_all)
      arel = arel.from(utable_all)

      arel = arel.except(:select)
      arel = arel.select([
                             "recorded_items_all.id",
                             :recording_id,
                             :user_id,
                             :url,
                             :fully_uploaded,
                             :upload_failures,
                             :client_track_id,
                             :item_type
                         ])

      # Further joining and criteria for the unioned object:
      arel = arel.joins("INNER JOIN recordings ON recordings.id=recorded_items_all.recording_id") \
        .where('recorded_items_all.user_id' => user.id) \
        .where('recorded_items_all.fully_uploaded = ?', false) \
        .where('recorded_items_all.id > ?', since) \
        .where("upload_failures <= #{APP_CONFIG.max_track_upload_failures}") \
        .where("duration IS NOT NULL") \
        .where('all_discarded = false') \
        .where('deleted = false') \
        .order('recorded_items_all.id') \
        .limit(limit)

      # Load into array:
      arel.each do |recorded_item|
        if recorded_item.item_type=='video'
          # A video:
          uploads << ({
            :type => "recorded_video",
            :client_video_source_id => recorded_item.client_track_id,
            :recording_id => recorded_item.recording_id,
            :next => recorded_item.id
          })
        elsif recorded_item.item_type == 'track'
          # A track:  
          uploads << ({
            :type => "recorded_track",
            :client_track_id => recorded_item.client_track_id,
            :recording_id => recorded_item.recording_id,
            :next => recorded_item.id
          })
        elsif recorded_item.item_type == 'stream_mix'
          uploads << ({
            :type => "stream_mix",
            :recording_id => recorded_item.recording_id,
            :next => recorded_item.id
          })
        elsif recorded_item.item_type == 'backing_track'
          uploads << ({
            :type => "recorded_backing_track",
            :recording_id => recorded_item.recording_id,
            :client_track_id => recorded_item.client_track_id,
            :next => recorded_item.id
          })
        else

        end

      end

      next_value = uploads.length > 0 ? uploads[-1][:next].to_s : nil
      if next_value.nil?
        next_value = since # echo back to the client the same value they passed in, if there are no results
      end

      {
        "uploads" => uploads,
        "next" => next_value.to_s
      }
    end

    def preconditions_for_mix?
      recorded_tracks.each do |recorded_track|
        return false unless recorded_track.fully_uploaded
      end

      recorded_backing_tracks.each do |recorded_backing_track|
        return false unless recorded_backing_track.fully_uploaded
      end

      true
    end

    # Check to see if all files have been uploaded. If so, kick off a mix.
    def upload_complete
      # Don't allow multiple mixes for now.
      return if self.mixes.length > 0

      # FIXME: There's a possible race condition here. If two users complete
      # uploads at the same time, we'll schedule 2 mixes.

      self.mixes << Mix.schedule(self) if preconditions_for_mix?

      save
    end

    def is_public?
      claimed_recordings.where(is_public: true).length > 0
    end

    # meant to be used as a way to 'pluck' a claimed_recording appropriate for user.
    def candidate_claimed_recording
      #claimed_recordings.where(is_public: true).first
      claimed_recordings.first
    end

    # returns a ClaimedRecording that the user did not discard
    def claim_for_user(user, ignore_discarded = false)
      return nil unless user
      claim = claimed_recordings.find{|claimed_recording| claimed_recording.user == user }
      if ignore_discarded
        claim
      else
        claim unless claim && claim.discarded
      end
    end

    def self.when_will_be_discarded?

      recorded_track_votes = recorded_tracks.map(&:discard)

      discarded = 0
      recorded_track_votes.each do |discard_vote|
        if discard_vote == nil || discard_vote == true
          discarded = discarded + 1
        end
      end

      if recorded_track_votes.length == discarded
        # all tracks are discarded, figure out due time for deletion
        # 3 days in seconds - amount of seconds since last updated
        ((APP_CONFIG.recordings_stale_time * 3600 * 24) - (Time.now - updated_at).to_i).seconds.from_now
      else
        return nil
      end
    end

    # finds all discarded recordings that are sufficiently stale (i.e., abandoned by all those involved, and hasn't been mucked with in a while)
    def self.discarded_and_stale

      # we count up all tracks for the Recording, and count up all discarded/not-voted-on tracks
      # if they are equal,  and if the recording is stale, let's return it.
      Recording
      .joins("INNER JOIN recorded_tracks ON recordings.id = recorded_tracks.recording_id")
      .joins(%Q{
        LEFT OUTER JOIN
          (SELECT id
            FROM recorded_tracks  WHERE discard IS NULL OR discard = TRUE) AS discard_info
          ON recorded_tracks.id = discard_info.id
      })
      .group("recordings.id")
      .having('COUNT(recorded_tracks.id) = COUNT(discard_info.id)')
      .where("NOW()  - recordings.updated_at > '#{APP_CONFIG.recordings_stale_time} day'::INTERVAL")
      .limit(1000)
      .readonly(false)
    end

    def mark_delete

      mixes.each do |mix|
        mix.delete_s3_files
      end

      quick_mixes.each do |quick_mix|
        quick_mix.delete_s3_files
      end

      recorded_tracks.each do |recorded_track|
        recorded_track.delete_s3_files
      end

      self.deleted = true
      self.save(:validate => false)
    end

    def add_video_data(user, data)
      Recording.where(id: self.id).update_all(external_video_id: data[:video_id])

      if music_session.music_session.is_lesson?
        music_session.lesson_session.video_uploaded(user, self, data)
      end
    end

    def add_timeline(timeline)
      global = timeline["global"]
      raise JamArgumentError, "global must be specified" unless global

      tracks = timeline["tracks"]
      raise JamArgumentError, "tracks must be specified" unless tracks

      Recording.where(id: self.id).update_all(timeline: global)

      jam_tracks = tracks.select {|track| track["type"] == "jam_track"}
      jam_tracks.each do |client_jam_track|
        RecordedJamTrackTrack.where(recording_id: id, jam_track_track_id: client_jam_track["id"]).update_all(timeline: client_jam_track["timeline"])
      end
    end

    def self.popular_recordings(limit = 100)
      Recording.select('recordings.id').joins('inner join claimed_recordings ON claimed_recordings.recording_id = recordings.id AND claimed_recordings.is_public = TRUE').where(all_discarded: false).where(is_done: true).where(deleted: false).order('play_count DESC').limit(limit).group('recordings.id')
    end

    private
    def self.validate_user_is_band_member(user, band)
      unless band.users.exists? user.id
        raise JamPermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
      end
    end

    def self.validate_user_is_creator(user, creator)
      unless user.id == creator.id
        raise JamPermissionError, ValidationMessages::PERMISSION_VALIDATION_ERROR
      end
    end

    def self.validate_user_is_musician(user)
      unless user.musician?
        raise JamPermissionError, ValidationMessages::USER_NOT_MUSICIAN_VALIDATION_ERROR
      end
    end

 end
end