require 'repertoire/media/exceptions/unknown_media'
require 'repertoire/media/exceptions/repository_exists'
require 'repertoire/media/exceptions/checkout_failed'
require 'repertoire/media/exceptions/update_failed'
require 'repertoire/extensions/uri'

require 'fileutils'

module Repertoire
  module Media
    #
    # Returns the +Hash+ of all registered Media types.
    #
    def Media.types
      @@media_types ||= {}
    end

    #
    # Returns +true+ if there is a Media type registered with the matching 
    # _name_.
    #
    def Media.supports?(name)
      Media.types.has_key?(name.to_sym)
    end

    #
    # Returns the Media type with the matching _name_.
    #
    def Media.get(name)
      name = name.to_sym

      unless Media.supports?(name)
        raise(UnknownMedia,"media type #{name.dump} is not registered",caller)
      end

      return Media.types[name]
    end

    #
    # Returns the +Hash+ of all registered Media types and their associated
    # URI schemes.
    #
    def Media.schemes
      @@media_schemes ||= {}
    end

    #
    # Returns an +Array+ of all registered URI schemes.
    #
    def Media.registered_schemes
      Media.schemes.keys
    end

    #
    # Returns +true+ if a Media type was registered for the specified
    # _scheme_, returns +false+ otherwise.
    #
    def Media.supports_scheme?(scheme)
      Media.schemes.has_key?(scheme.to_s)
    end

    #
    # Returns the Media type that was registered for the specified _scheme_.
    #
    def Media.supports_scheme(name)
      name = name.to_s

      unless Media.supports_scheme?(name)
        raise(UnknownMedia,"media type for scheme #{name.dump} is not registered",caller)
      end

      return Media.schemes[name]
    end

    #
    # Returns the +Hash+ of all registered Media types and their
    # associated media storage directories.
    #
    def Media.directories
      @@media_directories ||= {}
    end

    #
    # Returns an +Array+ of all registered directories.
    #
    def Media.registered_directories
      Media.directories.keys
    end

    #
    # Returns +true+ if a Media type was registered with the specified
    # _directory_, returns +false+ otherwise.
    #
    def Media.recognizes_directory?(name)
      Media.directories.has_key?(name.to_s)
    end

    #
    # Returns the Media type that was registered for the specified
    # _directory_.
    #
    def Media.recognizes_directory(name)
      name = name.to_s

      unless Media.recognizes_directory?(name)
        raise(UnknownMedia,"media type for directory #{name.dump} is not registered",caller)
      end

      return Media.directories[name]
    end

    #
    # Get the Media type that was registered for the scheme of the
    # specified _uri_.
    #
    def Media.guess_from_uri(uri)
      scheme = URI(uri).scheme

      return [scheme, Media.supports_scheme(scheme)]
    end

    #
    # Attempts to determine the correct Media type for the specified _path_.
    #
    def Media.guess_from_path(path)
      path = File.expand_path(path)

      Media.directories.each do |directory,media|
        if File.directory?(File.join(path,directory))
          return media
        end
      end

      raise(UnknownMedia,"the media type for #{path.dump} is unknown",caller)
    end

    #
    # Checkout the repository at the specified _options_. If a _block_
    # is given, it will be passed the path of the local repository
    # after the repository is successfully checked out.
    #
    # _options_ must contain the following key:
    # <tt>:uri</tt>:: The URI of the repository to checkout.
    #
    # _options_ may also contain the additional keys:
    # <tt>:path</tt>:: Path to checkout the repository to.
    # <tt>:media</tt>:: The media type of the repository. Defaults to the
    #                   value of Media.get_for_uri.
    # <tt>:into</tt>:: Checkout the repository into the given directory.
    #                  Cannot be used with <tt>:path</tt>.
    #
    def Media.checkout(options={},&block)
      uri = options[:uri].to_s
      path = options[:path]
      into = options[:into]
      media = options[:media]

      unless path
        if into
          into = File.expand_path(into)

          unless File.directory?(into)
            FileUtils.mkdir_p(into)
          end

          path = File.join(into,URI.repo_name(uri))
        else
          path = URI.repo_name(uri)
        end
      end

      path = File.expand_path(path)
      if File.exists?(path)
        raise(RepositoryExists,"the repository #{path.dump} already exists",caller)
      end

      if media
        handler = Media.get(media)
      else
        media, handler = Media.guess_from_uri(uri)
      end

      begin
        handler.checkout(uri,path)
      rescue CommandFailed
        raise(CheckoutFailed,"failed to checkout the repository located at #{uri.dump}",caller)
      end

      block.call(path,media,uri) if block
      return {:path => path, :media => media, :uri => uri}
    end

    #
    # Update the repository with the specified _options_. If a _block_
    # is given, it will be passed the path of the local repository
    # after the repository is successfully updated.
    #
    # _options_ must contain the following keys:
    # <tt>:path</tt>:: The path of the repository to update.
    #
    # _options_ may also contain the additional keys:
    # <tt>:uri</tt>:: The URI to update against.
    # <tt>:media</tt>:: The type of the repository. Defaults to
    #                   Media.guess_from_uri if <tt>:uri</tt> is given,
    #                   otherwise Media.guess_from_path.
    #
    def Media.update(options={},&block)
      path = File.expand_path(options[:path])
      uri = options[:uri]
      media = options[:media]

      if media
        handler = Media.get(media)
      elsif uri
        media, handler = Media.guess_from_uri(uri)
      else
        handler = Media.guess_from_path(path)
      end

      begin
        handler.update(path,uri)
      rescue CommandFailed
        raise(UpdateFailed,"failed to update the repository at #{path.dump}",caller)
      end

      block.call(media,path,uri) if block
      return {:media => media, :path => path, :uri => uri}
    end

    #
    # Delete the repository at the specified _path_. If a _block_ is
    # given, it will be passed the _path_ before it is deleted.
    #
    def Media.delete(path,&block)
      path = File.expand_path(path)

      block.call(path) if block
      FileUtils.rm_r(path.to_s,:force => true, :secure => true)
      return nil
    end
  end
end