require 'addressable/uri'

#
# Load a file from disk.
#
# WINDOWS
# =======
# Windows is supported, but must use URLs in the modern structure like:
#   file:///x:/path/to/the/file.png
# or as a relative path:
#   directory/file.png
# or as an absolute path from the current drive:
#   /path/to/the/file.png
#
# Ruby's URI parser does not like backslashes, nor can it handle filenames as URLs starting
# with a drive letter as it thinks you're giving it a scheme.
#
# URL ENCODING
# ============
# This module assumes the URL that is passed in has been URL-encoded.  If for some reason
# you're passing in a filename that hasn't been taken from an XML document's attribute,
# you will want to URL encode it before you pass it in.
#
# FILES READ AS BINARY
# ====================
# At the moment, prawn-svg uses this class only to load graphical files, which are binary.
# This class therefore uses File.binread to read file data.  If it is ever used in the future
# to load text files, it will have to be taught about what kind of file it's expecting to
# read, and adjust the file read function accordingly.
#
module Prawn::SVG::Loaders
  class File
    attr_reader :root_path

    def initialize(root_path)
      if root_path.empty?
        raise ArgumentError,
          "An empty string is not a valid root path.  Use '.' if you want the current working directory."
      end

      @root_path = ::File.expand_path(root_path)

      raise ArgumentError, "#{root_path} is not a directory" unless Dir.exist?(@root_path)
    end

    def from_url(url)
      uri = build_uri(url)

      if uri && uri.scheme.nil? && uri.path
        load_file(uri.path)

      elsif uri && uri.scheme == 'file'
        assert_valid_file_uri!(uri)
        path = windows? ? fix_windows_path(uri.path) : uri.path
        load_file(path)
      end
    end

    private

    def load_file(path)
      path = Addressable::URI.unencode(path)
      path = build_absolute_and_expand_path(path)
      assert_valid_path!(path)
      assert_file_exists!(path)
      ::File.binread(path)
    end

    def build_uri(url)
      URI(url)
    rescue URI::InvalidURIError
    end

    def assert_valid_path!(path)
      # TODO : case sensitive comparison, but it's going to be a bit of a headache
      # making it dependent on whether the file system is case sensitive or not.
      # Leaving it like this until it's a problem for someone.

      unless path.start_with?("#{root_path}#{::File::SEPARATOR}")
        raise Prawn::SVG::UrlLoader::Error, "file path is not inside the root path of #{root_path}"
      end
    end

    def build_absolute_and_expand_path(path)
      ::File.expand_path(path, root_path)
    end

    def assert_valid_file_uri!(uri)
      unless uri.host.nil? || uri.host.empty?
        raise Prawn::SVG::UrlLoader::Error,
          "prawn-svg does not suport file: URLs with a host. Your URL probably doesn't start with three slashes, and it should."
      end
    end

    def assert_file_exists!(path)
      raise Prawn::SVG::UrlLoader::Error, "File #{path} does not exist" unless ::File.exist?(path)
    end

    def fix_windows_path(path)
      if path.match(%r{\A/[a-z]:/}i)
        path[1..]
      else
        path
      end
    end

    def windows?
      !!(RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/)
    end
  end
end
