module JamRuby
  class Recording < ActiveRecord::Base

    self.primary_key = 'id'

    has_many :claimed_recordings, :class_name => "JamRuby::ClaimedRecording", :inverse_of => :recording
    has_many :users, :through => :claimed_recordings, :class_name => "JamRuby::User"
    belongs_to :owner, :class_name => "JamRuby::User", :inverse_of => :owned_recordings
    belongs_to :band, :class_name => "JamRuby::Band", :inverse_of => :recordings
    belongs_to :music_session, :class_name => "JamRuby::MusicSession", :inverse_of => :recording
    has_many :mixes, :class_name => "JamRuby::Mix", :inverse_of => :recording
    has_many :recorded_tracks, :class_name => "JamRuby::RecordedTrack", :foreign_key => :recording_id
    
    
    # Start recording a session.
    def self.start(music_session_id, owner)

      recording = nil
      
      # Use a transaction and lock to avoid races.
      ActiveRecord::Base.transaction do
        music_session = MusicSession.find(music_session_id, :lock => true)

        if music_session.nil?
          raise PermissionError, "the session has ended"
        end

        unless music_session.recording.nil?
          raise PermissionError, "the session is already being recorded"
        end
      
        recording = Recording.new
        recording.music_session = music_session
        recording.owner = owner
        
        music_session.connections.each do |connection|
          # Note that we do NOT connect the recording to any users at this point.
          # That ONLY happens if a user clicks 'save'
          # recording.users << connection.user
          connection.tracks.each do |track|
            RecordedTrack.create_from_track(track, recording)
          end
        end

        # Note that I believe this can be nil.
        recording.band = music_session.band
        recording.save

        music_session.recording = recording
        music_session.save
      end
      

      # FIXME:
      # NEED TO SEND NOTIFICATION TO ALL USERS IN THE SESSION THAT RECORDING HAS STARTED HERE.
      # I'LL STUB IT A BIT. NOTE THAT I REDO THE FIND HERE BECAUSE I DON'T WANT TO SEND THESE
      # NOTIFICATIONS WHILE THE DB ROW IS LOCKED
      music_session = MusicSession.find(music_session_id)
      music_session.connections.each do |connection|
        # connection.notify_recording_has_started
      end
      
      recording
    end

    # Stop recording a session
    def stop
      # Use a transaction and lock to avoid races.
      ActiveRecord::Base.transaction do
        music_session = MusicSession.find(self.music_session_id, :lock => true)
        if music_session.nil?
          raise PermissionError, "the session has ended"
        end
        unless music_session.recording
          raise PermissionError, "the session is not currently being recorded"
        end
        music_session.recording = nil
        music_session.save
      end

      self.duration = Time.now - created_at
      save
    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, genre, is_public, is_downloadable)
      if self.users.include?(user)
        raise PermissionError, "user already claimed this recording"
      end

      unless self.recorded_tracks.find { |recorded_track| recorded_track.user == user }
        raise PermissionError, "user was not in this session"
      end  

      unless self.music_session.nil?
        raise PermissionError, "recording cannot be claimed while it is being recorded"
      end

      if name.nil? || genre.nil? || is_public.nil? || is_downloadable.nil?
        raise PermissionError, "recording must have name, genre and flags"
      end
     
      claimed_recording = ClaimedRecording.new 
      claimed_recording.user = user
      claimed_recording.recording = self
      claimed_recording.name = name
      claimed_recording.genre = genre
      claimed_recording.is_public = is_public
      claimed_recording.is_downloadable = is_downloadable
      self.claimed_recordings << claimed_recording
      save

      claimed_recording
    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

    # Discards this recording and schedules deletion of all files associated with it.
    def discard
      self.destroy
    end

    # Returns the list of files the user needs to upload. This will only ever be recordings
    def self.upload_file_list(user)
      files = []
      User.joins(:recordings).joins(:recordings => :recorded_tracks)
        .where(%Q{ recordings.duration IS NOT NULL })
        .where("recorded_tracks.user_id = '#{user.id}'")
        .where(%Q{ recorded_tracks.fully_uploaded = FALSE }).each do |user|
          user.recordings.each.do |recording|
            recording.recorded_tracks.each do |recorded_track|
              files.push(
                {
                  :type => "recorded_track",
                  :id => recorded_track.id,
                  :url => recorded_track.url # FIXME IS THIS RIGHT?
                }
              )
            end
          end
      files
    end

    # Returns the list of files this user should have synced to his computer, along with md5s and lengths
    def self.list(user)
      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!
      User.joins(:recordings).joins(:recordings => :recorded_tracks)
        .order(%Q{ recordings.created_at DESC })
        .where(%Q{ recorded_tracks.fully_uploaded = TRUE })
        .where(:id => user.id).each do |theuser|
          theuser.recordings.each do |recording|
            recording.recorded_tracks.each do |recorded_track|
              recorded_track = user.claimed_recordings.first.recording.recorded_tracks.first
              downloads.push(
                {
                  :type => "recorded_track",
                  :id => recorded_track.id,
                  :length => recorded_track.length,
                  :md5 => recorded_track.md5,
                  :url => recorded_track.url
                }
              )
            end
          end
        end

      User.joins(:recordings).joins(:recordings => :mixes)
        .order(%Q{ recordings.created_at DESC })
        .where(%Q{ mixes.completed_at IS NOT NULL }).each do |theuser|
          theuser.recordings.each do |recording|
            recording.mixes.each do |mix|
              downloads.push(
                {
                  :type => "mix",
                  :id => mix.id,
                  :length => mix.length,
                  :md5 => mix.md5,
                  :url => mix.url
                }
              )
            end
          end
        end


      uploads = []
      RecordedTrack
        .joins(:recording)
        .where(:user_id => user.id)
        .where(:fully_uploaded => false)
        .where("duration IS NOT NULL").each do |recorded_track| 
          uploads.push(recorded_track.filename)
        end

      {
        "downloads" => downloads,
        "uploads" => uploads
      }
    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.
      raise JamRuby::JamArgumentError unless 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.
      recorded_tracks.each do |recorded_track|
        return unless recorded_track.fully_uploaded
      end

      self.mixes << Mix.schedule(self, base_mix_manifest.to_json)

      save
    end

=begin 
# This is no longer remotely right.
    def self.search(query, options = { :limit => 10 })

      # only issue search if at least 2 characters are specified
      if query.nil? || query.length < 2
        return []
      end

      # create 'anded' statement
      query = Search.create_tsquery(query)

      if query.nil? || query.length == 0
        return []
      end

      return Recording.where("description_tsv @@ to_tsquery('jamenglish', ?)", query).limit(options[:limit])
    end
=end

    def base_mix_manifest
      manifest = { "files" => [], "timeline" => [] }
      mix_params = []
      recorded_tracks.each do |recorded_track|
        return nil unless recorded_track.fully_uploaded
        manifest["files"] << { "url" => recorded_track.url, "codec" => "vorbis", "offset" => 0 }
        mix_params << { "level" => 100, "balance" => 0 }
      end

      manifest["timeline"] << { "timestamp" => 0, "mix" => mix_params }
      manifest["timeline"] << { "timestamp" => duration, "end" => true }
      manifest
    end
    
  private
    def self.validate_user_is_band_member(user, band)
      unless band.users.exists? user
        raise PermissionError, ValidationMessages::USER_NOT_BAND_MEMBER_VALIDATION_ERROR
      end
    end

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

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

 end
end
