require 'rubygems'
require 'nokogiri'
require 'mechanize'

Mechanize.html_parser = Nokogiri::HTML

# TODO: Tests (mock out expected DropBox output)
# TODO: Directory object, File object

class DropBox
	#before_filter :login_filter, :except => [:login]
	
	def initialize(email, password, folder_namespace = "")
		@email = email
		@password = password
		@agent = Mechanize.new
		@folder_namespace = folder_namespace.gsub(/^\//,"")
		@logged_in = false
	end
	
	def agent
	  @agent
  end
	
	# Lists all the files and folders in a given directory
	def index(path = "/")
 		login_filter
		path = namespace_path(path)
		
		list = @agent.post("/browse2#{path}?ajax=yes", {"d"=> 1, "t" => @token })
		
		listing = list.search('div.browse-file-box-details').collect do |file|
			details = {}
			details['name'] = file.at('div.details-filename a').content.strip
			details['url']  = file.at('div.details-filename a')["href"]
			#details['size'] = file.at('div.details-size a').try(:content).try(:strip)
			#details['modified'] = file.at('div.details-modified a').try(:content).try(:strip)
			
			if match_data = details['url'].match(/^\/browse_plain(.*)$/)
				details['directory'] = true
				details['path'] = normalize_namespace(match_data[1])
			elsif match_data = details['url'].match(%r{^https?://[^/]*/get(.*)$})
				details['directory'] = false
				details['path'] = normalize_namespace(match_data[1])
      elsif match_data = details['url'].match(%r{^https?://[^/]*/u/\d*/(.*)$})
          details['directory'] = false
          details['path'] = "Public/#{match_data[1]}"
			else
				raise "could not parse path from Dropbox URL: #{details['url'] }"
			end
			
			details
		end
		
		return listing
	end
	
	alias :list :index
	
	# Lists the full history for a file on DropBox
	def list_history(path)
		login_filter
		
		path = namespace_path(path)

		history = @agent.get("/revisions#{path}")
		listing = history.search("table.filebrowser > tr").select{|r| r.search("td").count > 1 }.collect do |r|
			
			# warning, this is very brittle!
			details = {}
			details["version"] = r.search("td a").first.content.strip
			details["url"] = r.search("td a").first["href"]
			details["size"] = r.search("td").last.content.strip
			details["modified"] = r.search("td")[2].content.strip
			details["version_id"] = details["url"].match(/^.*sjid=([\d]*)$/)[1]
			details['path'] = normalize_namespace(details['url'][33..-1])
			
			details
		end
		
		return listing
	end
	
	# Downloads the specified file from DropBox
	def show(path)
	  require 'pathname'
		# change to before filter
		login_filter
		
		# round about way of getting the secure url we need
    # path = namespace_path(path)
		pathname = Pathname.new(path)
		url = self.list(pathname.dirname.to_s).detect{ |f| f["name"] == pathname.basename.to_s }["url"]
		
		#https://dl-web.dropbox.com/get/testing.txt?w=0ff80d5d&sjid=125987568
		@agent.get(url).content
	end
	
	alias :get :show
	
	# Creates a directory
	def create_directory(new_path, destination = "/" )
		# change to before filter
		login unless @logged_in
		destination = namespace_path(destination)
		@agent.post("/cmd/new#{destination}",{"to_path"=>new_path, "folder"=>"yes", "t" => @token }).code == "200"
	end
	
	# Uploads a file to DropBox under the given filename
	def create(file, destination = "/")
		# change to before filter
		if @logged_in
			home_page = @agent.get('https://www.dropbox.com/home')
		else
			home_page = login
		end
		
		upload_form = home_page.forms.detect{ |f| f.action == "https://dl-web.dropbox.com/upload" }
		upload_form.dest = namespace_path(destination)
		upload_form.file_uploads.first.file_name = file if file
		
		@agent.submit(upload_form).code == "200"
	end
	
  alias :update :create
	
	# Renames a file or folder in the DropBox
	def rename(file, destination)
		login_filter
		file = namespace_path(file)
		destination = namespace_path(destination)
		@agent.post("/cmd/rename#{file}", {"to_path"=> destination, "t" => @token }).code == "200"
	end
	
	# Deletes a file/folder from the DropBox (accepts string path or an array of string paths)
	def destroy(paths)
		login_filter
		paths = [paths].flatten
		paths = paths.collect { |path| namespace_path(path) }
		@agent.post("/cmd/delete", {"files"=> paths, "t" => @token }).code == "200"
	end

  # Permanently deletes a file from the DropBox (no history!) accepts arrays, as #destroy does
	def purge(paths)
		login_filter
		paths = [paths].flatten
		paths = paths.collect { |path| namespace_path(path) }
		@agent.post("/cmd/purge", {"files"=> paths, "t" => @token }).code == "200"
	end
	
	# Will give a hash of the amount of space left on the DropBox, the amound used, the calculated amount free (all as a 1 d.p. rounded GB value) and the percentage used (scraped)
	def usage_stats
	  login_filter
    
	  stats_page = @agent.get("/account")
	  
	  stats = stats_page.at('#usage-percent').content.scan(/(\d+(?:\.\d+)?)%\ used\ \((\d+(?:\.\d+)?)([MG])B of (\d+(?:\.\d+)?)GB\)/).collect{ |d|
	    { :used => d[1].to_f * ((d[2] == "G") ? 1024 : 1),
	      :total => d[3].to_f * 1024,
	      :free => (d[3].to_f * 1024 - d[1].to_f * ((d[2] == "G") ? 1024 : 1)),
	      :percent => Percentage.new(d[0].to_f/100)
	    }
	  }[0]
	  
	  regular_data = stats_page.at('span.bar-graph-legend.bar-graph-normal').next.content.scan(/\((\d+(?:\.\d+)?)([MG])B/)[0]
	  stats[:regular_used] = regular_data[0].to_f * ((regular_data[1] == "G") ? 1024 : 1) unless regular_data.nil?

	  shared_data = stats_page.at('span.bar-graph-legend.bar-graph-shared').next.content.scan(/\((\d+(?:\.\d+)?)([MG])B/)[0]
	  stats[:shared_used] = shared_data[0].to_f * ((shared_data[1] == "G") ? 1024 : 1) unless shared_data.nil?

    return stats
  end

	private
	def namespace_path(path)
		# remove the start slash if we have one
		path.gsub(/^\//,"")
		new_path = if @folder_namespace.empty?
			"/#{path}"
		else
			"/#{@folder_namespace}/#{path}"
		end
		new_path.gsub("//","/")
	end
	
	def normalize_namespace(file)
		file.gsub(/^\/#{@folder_namespace}/,"")
	end

  def login
		page = @agent.get('https://www.dropbox.com/login')
		login_form = page.forms.detect { |f| f.action == "/login" }
		login_form.login_email = @email
		login_form.login_password = @password
		
		home_page = @agent.submit(login_form)
		# todo check if we are logged in! (ie search for email and "Log out"
		@logged_in = true
		@token = home_page.at('//script[contains(text(), "TOKEN")]').content.match("TOKEN: '(.*)',$")[1]
		
		# check if we have our namespace
		
		home_page
	end
	
	def login_filter
		login unless @logged_in
	end
end

# this file doesn't use the standard ruby indent settings, so the following
# modeline will make sure that the whitespace stays consistent.
# vim:noexpandtab tabstop=4 shiftwidth=4
