#!/usr/bin/ruby

## This script requires the multipart-post gem

require 'net/http'
require 'net/http/post/multipart'
require 'cgi'
require 'json'

class IBroadcastUploader
  def initialize(email, password)
    if email.nil? || password.nil?
      raise ArgumentError.new "Run this script in the parent directory of your music files.\nusage: ./uploader <email_address> <password>"
    end

    @email = email
    @password = password
  end

  ## performs a login request, gets user id, access token and supported formats
  def login
    ## Inital request, verifies username/password, returns user_id/token and supported file types
    req = {
      'mode' => 'status',
      'email_address' => ARGV[0],
      'password' => ARGV[1],
      'version' => '.1',
      'client' => 'ruby uploader script',
      'supported_types' => '1'
    }

    ## Convert request hash to a json string
    json_out = JSON(req)

    ## Create the client and perform the initial request
    uri = URI("https://json.ibroadcast.com/s/JSON/" + req['mode'])
    client = Net::HTTP.new(uri.host, uri.port)
    client.use_ssl = true

    response = client.post(uri.request_uri, json_out)

    ## Parse the resulting json
    @login_data = JSON.parse(response.body)

    unless @login_data['user'] && @login_data['user']['id']
      raise StandardError.new "Login failed. Please check your email address, password combination"
    end

    @user_id = @login_data['user']['id']
    @token = @login_data['user']['token']
  end

  ## prompts for user action and initiates upload
  def upload
    login unless @login_data

    ## Create an array of supported extensions
    @supported = @login_data['supported'].map { |e| e['extension']}

    ## Get files from cwd
    files = list_files(Dir.pwd)

    if confirm(files)
      upload_files(files)
    end
  end

  private

  ## returns an array of absolute file paths of supported file formats in the given directory
  def list_files(dir)
    files = []

    Dir.foreach(dir) do |entry|
      ## skip hidden
      next if entry =~ /^\./

      ## get full path
      path = File.join(dir, entry)

      if File.directory? path ## if this is a directory
        files += list_files(path) ## recursively add subdirectories
      elsif @supported.include? File.extname(path) ## if file has a supported extension
        files << path
      end
    end

    return files
  end

  ## prompts for input - list files and/or upload
  ## returns true if user gives ok to upload
  def confirm(files)
    puts "Found #{files.count} files. Press 'L' to list, or 'U' to start the upload."
    input = $stdin.gets

    if input.upcase.start_with? 'L'
      ## print list of files found
      puts "\nListing found, supported files"
      files.each { |path| puts " - " + path}

      puts "Press 'U' to start the upload if this looks reasonable."

      input2 = $stdin.gets
      if input2.upcase.start_with? 'U'
        puts "Starting upload"
        return true
      else
        puts "aborted."
        return false
      end

    elsif input.upcase.start_with? 'U'
      puts "Starting upload"
      return true
    else
      puts "aborted."
      return false
    end
  end

  ## returns an array of MD5 sums of present files in hex form
  def get_md5
    ## Create http client and perform the md5 request
    uri = URI("https://sync.ibroadcast.com")
    request = Net::HTTP::Post.new(uri.request_uri)
    request['Content-Type'] = "application/x-www-form-urlencoded"
    request.set_form_data 'user_id' => @user_id, 'token' => @token

    client = Net::HTTP.new(uri.host, uri.port)
    client.use_ssl = true
    response = client.request(request)

    ## Parse and return response
    JSON.parse(response.body)['md5']
  end

  def upload_files(files)
    md5 = get_md5

    files.each do |file|
      digest = Digest::MD5.hexdigest(File.read(file))

      ## create http client
      uri = URI("https://sync.ibroadcast.com")
      client = Net::HTTP.new(uri.host, uri.port)
      client.use_ssl = true

      puts "Uploading: #{file}"
      if !md5.include?(digest) ## check against uploaded md5 list

        ## not in uploaded md5 list, post file to server
        request = Net::HTTP::Post::Multipart.new(uri.request_uri, {
          'method' => 'ruby uploader',
          'user_id' => @user_id,
          'token' => @token,
          'file_path' => file,
          'file' => UploadIO.new(File.new(file), 'audio/mpeg', File.basename(file))
        })

        response = client.request(request)
        if response.code == '200'
          puts " Done!"
        else
          puts " Failed."
        end

      else
        puts " skipping, already uploaded"
      end
    end
  end
end

begin
  uploader = IBroadcastUploader.new(ARGV[0], ARGV[1]) ## create uploader with email and password
  uploader.upload ## start process
rescue Exception => e
  puts e.message
end
