require "dragonfly/hash_with_css_style_keys"
require "dragonfly/image_magick/commands"
require "dragonfly/param_validators"

module Dragonfly
  module ImageMagick
    module Generators
      class Text
        include ParamValidators

        FONT_STYLES = {
          "normal" => "normal",
          "italic" => "italic",
          "oblique" => "oblique",
        }

        FONT_STRETCHES = {
          "normal" => "normal",
          "semi-condensed" => "semi-condensed",
          "condensed" => "condensed",
          "extra-condensed" => "extra-condensed",
          "ultra-condensed" => "ultra-condensed",
          "semi-expanded" => "semi-expanded",
          "expanded" => "expanded",
          "extra-expanded" => "extra-expanded",
          "ultra-expanded" => "ultra-expanded",
        }

        FONT_WEIGHTS = {
          "normal" => "normal",
          "bold" => "bold",
          "bolder" => "bolder",
          "lighter" => "lighter",
          "100" => 100,
          "200" => 200,
          "300" => 300,
          "400" => 400,
          "500" => 500,
          "600" => 600,
          "700" => 700,
          "800" => 800,
          "900" => 900,
        }

        IS_COLOUR = ->(param) {
          /\A(#\w+|rgba?\([\d\.,]+\)|\w+)\z/ === param
        }

        def update_url(url_attributes, string, opts = {})
          url_attributes.name = "text.#{extract_format(opts)}"
        end

        def call(content, string, opts = {})
          validate_all_keys!(opts, %w(font font_family), &is_words)
          validate_all_keys!(opts, %w(color background_color stroke_color), &IS_COLOUR)
          validate!(opts["format"], &is_word)

          opts = HashWithCssStyleKeys[opts]
          args = []
          format = extract_format(opts)
          background = opts["background_color"] || "none"
          font_size = (opts["font_size"] || 12).to_i
          font_family = opts["font_family"] || opts["font"]
          escaped_string = "\"#{string.gsub(/"/, '\"')}\""

          # Settings
          args.push("-gravity NorthWest")
          args.push("-antialias")
          args.push("-pointsize #{font_size}")
          args.push("-family '#{font_family}'") if font_family
          args.push("-fill #{opts["color"]}") if opts["color"]
          args.push("-stroke #{opts["stroke_color"]}") if opts["stroke_color"]
          args.push("-style #{FONT_STYLES[opts["font_style"]]}") if opts["font_style"]
          args.push("-stretch #{FONT_STRETCHES[opts["font_stretch"]]}") if opts["font_stretch"]
          args.push("-weight #{FONT_WEIGHTS[opts["font_weight"]]}") if opts["font_weight"]
          args.push("-background #{background}")
          args.push("label:#{escaped_string}")

          # Padding
          pt, pr, pb, pl = parse_padding_string(opts["padding"]) if opts["padding"]
          padding_top = (opts["padding_top"] || pt).to_i
          padding_right = (opts["padding_right"] || pr).to_i
          padding_bottom = (opts["padding_bottom"] || pb).to_i
          padding_left = (opts["padding_left"] || pl).to_i

          Commands.generate(content, args.join(" "), format)

          if (padding_top || padding_right || padding_bottom || padding_left)
            dimensions = content.analyse(:image_properties)
            text_width = dimensions["width"]
            text_height = dimensions["height"]
            width = padding_left + text_width + padding_right
            height = padding_top + text_height + padding_bottom

            args = args.slice(0, args.length - 2)
            args.push("-size #{width}x#{height}")
            args.push("xc:#{background}")
            args.push("-annotate 0x0+#{padding_left}+#{padding_top} #{escaped_string}")
            Commands.generate(content, args.join(" "), format)
          end

          content.add_meta("format" => format, "name" => "text.#{format}")
        end

        private

        def extract_format(opts)
          opts["format"] || "png"
        end

        # Use css-style padding declaration, i.e.
        # 10        (all sides)
        # 10 5      (top/bottom, left/right)
        # 10 5 10   (top, left/right, bottom)
        # 10 5 10 5 (top, right, bottom, left)
        def parse_padding_string(str)
          padding_parts = str.gsub("px", "").split(/\s+/).map { |px| px.to_i }
          case padding_parts.size
          when 1
            p = padding_parts.first
            [p, p, p, p]
          when 2
            p, q = padding_parts
            [p, q, p, q]
          when 3
            p, q, r = padding_parts
            [p, q, r, q]
          when 4
            padding_parts
          else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
          end
        end
      end
    end
  end
end
