# coding: utf-8
# frozen_string_literal: true

module Google
  module Webfonts
    # Used to simplify creating <link> tags for Google's Webfonts CDN.
    #
    # @example
    #   tag = Google::Webfonts::LinkTag.new(:droid_sans => %w[400 500i])
    #   html = tag.to_s
    class LinkTag
      attr_reader :options

      # Creates a new {Google::Webfonts::LinkTag} instance.
      #
      # @return [Google::Webfonts::LinkTag] The new {LinkTag} instance.
      def initialize(*options)
        @options = parse_options(options).freeze
      end

      # When making headers or display texts on your website, you'll often
      # want to stylize your text in a decorative way. To simplify your work,
      # Google has provided a collection of font effects that you can use
      # with minimal effort to produce beautiful display text.
      #
      # To use feature, simply add an +effect:+ option when initializing the
      # link tag. Then add the corresponding class name to the HTML element(s)
      # that you want to affect.
      #
      # At the time that version 0.3.0 of this gem was released, this was a
      # beta feature provided by Google.
      #
      # @see https://developers.google.com/fonts/docs/getting_started
      # @return [Array<String>] Any effects to applied to the font.
      def effect
        options[:effect]
      end

      # Parses the options provided to the initializer into a list of font
      # families.
      #
      # @return [String] The font family formatted for Google Webfont's API.
      def family
        @family ||= family_options.map do |font_name, font_options|
          name = parse_font_name(font_name)

          options = font_options.sort.uniq

          options.any? ? "#{name}:#{options.join(',')}" : name
        end.join('|')
      end

      # Creates the hypertext reference (URL) to the font.
      #
      # @return [URI] URL to the font on Google's CDN.
      def href
        # Return the cached value when present.
        return @href unless @href.nil?

        # Create a URI to Google's CDN.
        uri = URI.join(Google::Webfonts::CDN_HOST, Google::Webfonts::CDN_PATH)

        uri.query = "family=#{family}"

        uri.query += "&text=#{CGI.escape(text.join)}" unless text.nil?

        uri.query += "&effect=#{CGI.escape(effect.join(','))}" unless effect.nil?

        @href = uri
      end

      # Oftentimes, when you want to use a web font on your website or
      # application, you know in advance which letters you'll need. This
      # often occurs when you're using a web font in a logo or heading.
      #
      # In these cases, you should consider specifying a text= value in your
      # font request URL. This allows Google to return a font file that's
      # optimized for your request. In some cases, this can reduce the size
      # of the font file by up to 90%.
      #
      # At the time that version 0.3.0 of this gem was released, this was a
      # beta feature provided by Google.
      #
      # @see https://developers.google.com/fonts/docs/getting_started
      # @return [Array<String>] Which letters to include in the font.
      def text
        options[:text]
      end

      # Converts the link tag to HTML.
      #
      # @return [String] HTML for the link tag.
      def to_s
        to_html
      end

      protected

      # This is used by #to_s to create a <link> tag so that #to_s can be
      # overridden depending on the web framework being used - e.g. with Rails.
      #
      # @return [String] HTML for the link tag.
      def to_html
        %(<link href="#{href}" rel="stylesheet">)
      end

      private

      # Extracts the options for the font families.
      #
      # @return [Hash{String, Symbol => Array<Integer, String>}] The families.
      def family_options
        options.reject {|k,_| %i[effect text].include?(k) }
      end

      # Converts the Ruby-friendly font name provided to the initializer to a
      # URL-encoded font name that Google's CDN will recognize.
      #
      # @param [String, Symbol] font_name The given font name.
      # @return [String] A URI-encoded key name.
      def parse_font_name(font_name)
        # Strings only get URI-encoded.
        return CGI.escape(font_name) if font_name.is_a?(String)

        # Convert the font name into an array of its words.
        words = "#{font_name}".split('_')

        # Capitalize each word in the font name.
        words.map!(&:capitalize)

        # Convert back to a String, joined by spaces instead of underscores.
        font_name = words.join(' ')

        # URI-encode the font name.
        CGI.escape(font_name)
      end

      # Parses the options given to the initializer into a Hash.
      #
      # @return [Hash{String, Symbol => Array}] Options for the LinkTag.
      def parse_options(options)
        {}.tap do |parsed|
          options.each { |opt| parse_option(opt, parsed) }
        end
      end

      # @param [Hash, String, Symbol] opt
      # @param [Hash] parsed
      # @return [Array<String, Array>]
      # @raise [ArgumentError] If an option cannot be parsed.
      def parse_option(opt, parsed)
        # Font names without options are converted to names with empty options.
        return parsed[opt] = [] if opt.is_a?(String) || opt.is_a?(Symbol)

        # Add any option in a Hash, ensuring that its value is an Array.
        return opt.each { |k, v| parsed[k] = Array(v) } if opt.is_a?(Hash)

        # Reject unknown arguments.
        fail ArgumentError, "invalid argument: #{opt.inspect}", caller
      end
    end
  end
end