require "net/http" 
require "cgi"
require "set"
require "restclient"
require "filezor/file"
require "json"

class Filezor::Client
  class Unauthorized < RuntimeError; end
  class InvalidArgument < RuntimeError; end
  
  attr_reader :options
  
  def self.last_exception
    @e
  end
  
  def self.last_exception=(e)
    @e = e
  end
  
  def initialize(host, port, password, options = {})
    @root = "http://admin:#{password}@#{host}:#{port}"
    @options ||= stringify_keys(options)
    @options.update JSON.parse(post("info", options.to_json, "/"))
  end
  
  # This method acts like an rsync --delete.  It deletes all files in the root,
  # and then puts the new files in.  TODO: make atomic
  def sync(*files)
    options = files.last.is_a?(Hash) ? files.pop : {}    
    files = files.flatten.uniq
    test(files)
    # 
    # files.each do |f|
    #   f.path = File.join(root, f.path)
    # end
    cached_md5s = caching? ? Set.new(cached(files)) : []
    params = files.inject({}) do |memo, file|
      memo[file.path] = cached_md5s.include?(file.md5) ? file.md5 : file.tempfile
      memo
    end
    
    params["--delete"] = options[:delete] if options[:delete]
    params["--root"] = options[:root] if options[:root]
    post("", params)
  end
  
  # Simple file upload
  def put(*files)
    test(files)
    params = files.flatten.inject({}) do |memo, file|
      memo[file.path] = file.tempfile
      memo
    end
    post("", params)
  end
  
  def set_options(options = {})
    @options = JSON.parse(post("info", options.to_json, "/"))
  end
  
  def ping
    get("ping", "/").code == 200
  end
  
  def cached(*files)
    JSON.parse(post("cached", files.flatten.map{|f| f.md5 }.to_json, "/"))
  end

  def get(path, prefix = "/file/")
    _get("#{http_root}#{prefix}#{path}")
  rescue RestClient::Unauthorized
    raise Unauthorized
  end
  
  def post(path, params, prefix = "/file/")
    _post("#{http_root}#{prefix}#{path}", params)
  rescue RestClient::Unauthorized
    raise Unauthorized
  end
  
  def http_root
    "#{@root}#{app_root}"
  end
  
  def app_root
    options["app_root"] || ""
  end
  
  def caching?
    options["caching"]
  end
    
private

  def _get(*args)
    puts args.inspect if ENV["FILEZOR_DEBUG"] 
    RestClient.get(*args)
  rescue => e
    self.class.last_exception = e
    raise
  end
  
  def _post(*args)
    puts args.inspect if ENV["FILEZOR_DEBUG"] 
    RestClient.post(*args)
  rescue => e
    self.class.last_exception = e
    raise
  end

  def test(*files)
    files.flatten.each do |file|
      unless file.is_a?(Filezor::File)
        raise InvalidArgument.new(file.inspect)
      end
    end
  end
  
  def stringify_keys(hash)
    hash.inject({}) {|memo, (k, v)| memo.update k.to_s => v }
  end
end