module JamRuby
  # BackingTrack analog to JamRuby::RecordedTrack
  class RecordedBackingTrack < ActiveRecord::Base

    include JamRuby::S3ManagerMixin

    attr_accessor   :marking_complete
    attr_writer :current_user

    belongs_to :user, :class_name => "JamRuby::User", :inverse_of => :recorded_backing_tracks
    belongs_to :recording, :class_name => "JamRuby::Recording", :inverse_of => :recorded_backing_tracks
    validates :filename, :presence => true

    validates :client_id, :presence => true  # not a connection relation on purpose
    validates :backing_track_id, :presence => true # not a track relation on purpose
    validates :client_track_id, :presence => true
    validates :md5, :presence => true, :if => :upload_starting?
    validates :length, length: {minimum: 1, maximum: 1024 * 1024 * 256 }, if: :upload_starting?  # 256 megs max. is this reasonable? surely...
    validates :user, presence: true
    validates :download_count, presence: true

    before_destroy :delete_s3_files
    validate :validate_fully_uploaded
    validate :validate_part_complete
    validate :validate_too_many_upload_failures
    validate :verify_download_count

    def self.create_from_backing_track(backing_track, recording) 
      recorded_backing_track = self.new
      recorded_backing_track.recording = recording
      recorded_backing_track.client_id = backing_track.connection.client_id
      recorded_backing_track.backing_track_id = backing_track.id
      recorded_backing_track.client_track_id = "R" + backing_track.client_track_id # Matches behavior in RecordingManager.cpp#getWavComment
      recorded_backing_track.user = backing_track.connection.user
      recorded_backing_track.filename = backing_track.filename
      recorded_backing_track.next_part_to_upload = 0
      recorded_backing_track.file_offset = 0
      recorded_backing_track[:url] = construct_filename(recording.created_at, recording.id, backing_track.client_track_id)
      recorded_backing_track.save
      recorded_backing_track
    end

    def sign_url(expiration_time = 120, secure = true)
      s3_manager.sign_url(self[:url], {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => secure})
    end

    def can_download?(some_user)
      claimed_recording = recording.claimed_recordings.find{|claimed_recording| claimed_recording.user == some_user }

      if claimed_recording
        !claimed_recording.discarded
      else
        false
      end
    end

    def too_many_upload_failures?
      upload_failures >= APP_CONFIG.max_track_upload_failures
    end

    def too_many_downloads?
      (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
    end

    def upload_starting?
      next_part_to_upload_was == 0 && next_part_to_upload == 1
    end

    def validate_too_many_upload_failures
      if upload_failures >= APP_CONFIG.max_track_upload_failures
        errors.add(:upload_failures, ValidationMessages::UPLOAD_FAILURES_EXCEEDED)
      end
    end

    def validate_fully_uploaded
      if marking_complete && fully_uploaded && fully_uploaded_was
        errors.add(:fully_uploaded, ValidationMessages::ALREADY_UPLOADED)
      end
    end

    def validate_part_complete

      # if we see a transition from is_part_uploading from true to false, we validate
      if is_part_uploading_was && !is_part_uploading
        if next_part_to_upload_was + 1 != next_part_to_upload
          errors.add(:next_part_to_upload, ValidationMessages::INVALID_PART_NUMBER_SPECIFIED)
        end

        if file_offset > length
          errors.add(:file_offset, ValidationMessages::FILE_OFFSET_EXCEEDS_LENGTH)
        end
      elsif next_part_to_upload_was + 1 == next_part_to_upload
        # this makes sure we are only catching 'upload_part_complete' transitions, and not upload_start
        if next_part_to_upload_was != 0
          # we see that the part number was ticked--but was is_part_upload set to true before this transition?
          if !is_part_uploading_was && !is_part_uploading
            errors.add(:next_part_to_upload, ValidationMessages::PART_NOT_STARTED)
          end
        end
      end
    end

    def verify_download_count
      if (self.download_count < 0 || self.download_count > APP_CONFIG.max_audio_downloads) && !@current_user.admin
        errors.add(:download_count, "must be less than or equal to 100")
      end
    end


    def upload_start(length, md5)
      #self.upload_id set by the observer
      self.next_part_to_upload = 1
      self.length = length
      self.md5 = md5
      save
    end

    # if for some reason the server thinks the client can't carry on with the upload,
    # this resets everything to the initial state
    def reset_upload
      self.upload_failures = self.upload_failures + 1
      self.part_failures = 0
      self.file_offset = 0
      self.next_part_to_upload = 0
      self.upload_id = nil
      self.md5 = nil
      self.length = 0
      self.fully_uploaded = false
      self.is_part_uploading = false
      save :validate => false # skip validation because we need this to always work
    end

    def upload_next_part(length, md5)
      self.marking_complete = true
      if next_part_to_upload == 0
        upload_start(length, md5)
      end
      self.is_part_uploading = true
      save
    end

    def upload_sign(content_md5)
      s3_manager.upload_sign(self[:url], content_md5, next_part_to_upload, upload_id)
    end

    def upload_part_complete(part, offset)
      # validated by :validate_part_complete
      self.marking_complete = true
      self.is_part_uploading = false
      self.next_part_to_upload = self.next_part_to_upload + 1
      self.file_offset = offset.to_i
      self.part_failures = 0
      save
    end

    def upload_complete
      # validate from happening twice by :validate_fully_uploaded
      self.fully_uploaded = true
      self.marking_complete = true
      save
    end

    def increment_part_failures(part_failure_before_error)
      self.part_failures = part_failure_before_error + 1
      RecordedBackingTrack.update_all({"part_failures" => self.part_failures, "id" => self.id })
    end

    def stored_filename
      # construct a path from s3
      RecordedBacknigTrack.construct_filename(recording.created_at, self.recording.id, self.client_track_id)
    end

    def update_download_count(count=1)
      self.download_count = self.download_count + count
      self.last_downloaded_at = Time.now
    end

    def delete_s3_files
      s3_manager.delete(self[:url]) if self[:url] && s3_manager.exists?(self[:url])
    end

    def mark_silent
      destroy

      # check if we have all the files we need, now that the recorded_backing_track is out of the way
      recording.preconditions_for_mix?
    end

    private

    def self.construct_filename(created_at, recording_id, client_track_id)
      raise "unknown ID" unless client_track_id
      "recordings/#{created_at.strftime('%m-%d-%Y')}/#{recording_id}/backing-track-#{client_track_id}.ogg"
    end
  end  
end
