module Runtime
  class << self

    # Return the base paths to the runtime directories.
    #
    # @return [Array] Current runtime paths.
    def paths
      @paths ||= default_paths
    end

    # Possible extensions for fonts.
    #
    # @return [Array<String>]
    #   Possible extensions for font files.
    def font_extensions
      @font_extensions ||= ["otf", "ttf"]
    end

    # Find a runtime file.
    #
    # @param type [Symbol]
    #   Type of runtime file to find. Options:
    #   - :shader
    #   - :font
    # @param name [String]
    #   Name of the runtime file to locate.
    #
    # @return [String, nil]
    #   Path to the runtime file, or +nil+ if not found.
    def find(type, name)
      path = nil
      paths.find do |runtime_path|
        path = find_in(type, name, runtime_path)
      end
      path
    end

    # Find and read a runtime file.
    #
    # @param type [Symbol]
    #   Type of runtime file to find. See {#find} for available types.
    # @param name [String]
    #   Name of the runtime file to locate.
    #
    # @return [String]
    #   Contents of the runtime file, or +nil+ if not found.
    def read(type, name)
      path = find(type, name)
      if path
        File.read(path, mode: "rb")
      end
    end

    private

    # Return the default runtime paths.
    #
    # @return [Array] Default runtime paths.
    def default_paths
      paths = []
      user_path = File.expand_path("~/.jes")
      if File.directory?(user_path)
        paths << user_path
      end
      paths << File.expand_path("../../runtime", $0)
      paths
    end

    # Find a runtime file within a single runtime path.
    #
    # @param type [Symbol]
    #   Type of runtime file to find. Options:
    #   - :shader
    #   - :font
    # @param name [String]
    #   Name of the runtime file to locate.
    # @param runtime_path [String]
    #   The runtime path to look in.
    #
    # @return [String, nil]
    #   Path to the runtime file, or +nil+ if not found.
    def find_in(type, name, runtime_path)
      path =
        case type
        when :shader
          File.join(runtime_path, "shaders", name)
        when :font
          fonts_dir = File.join(runtime_path, "fonts")
          if File.directory?(fonts_dir)
            if path = find_with_ext(fonts_dir, name, font_extensions)
              path
            else
              path = nil
              Dir.entries(fonts_dir).find do |dirent|
                next if dirent.start_with?(".")
                dir = File.join(fonts_dir, dirent)
                next unless File.directory?(dir)
                path = find_with_ext(dir, name, font_extensions)
              end
              path
            end
          end
        else
          raise "Unknown runtime type: #{type.inspect}"
        end
      if path and File.exists?(path)
        path
      else
        nil
      end
    end

    # Look for a runtime file with multiple possible extensions.
    #
    # @param dir [String]
    #   The directory to look in.
    # @param name [String]
    #   The basename of the file to look for.
    # @extensions [Array<String>]
    #   The possible file extensions.
    #
    # @return [String, nil]
    #   Path to the file found, or +nil+ if a file was not found.
    def find_with_ext(dir, name, extensions)
      path = nil
      ([""] + extensions.map {|e| ".#{e}"}).find do |ext|
        p = File.join(dir, name + ext)
        if File.file?(p)
          path = p
        end
      end
      path
    end

  end
end
