#
# Prawn::SVG::Interface makes a Prawn::SVG::Document instance, uses that object to parse the supplied
# SVG into Prawn-compatible method calls, and then calls the Prawn methods.
#
module Prawn
  module SVG
    class Interface
      VALID_OPTIONS = [:at, :position, :vposition, :width, :height, :cache_images, :enable_web_requests, :enable_file_requests_with_root, :fallback_font_name]

      attr_reader :data, :prawn, :document, :options

      #
      # Creates a Prawn::SVG object.
      #
      # +data+ is the SVG data to convert.  +prawn+ is your Prawn::Document object.
      #
      # See README.md for the options that can be passed to this method.
      #
      def initialize(data, prawn, options, &block)
        Prawn.verify_options VALID_OPTIONS, options

        @data = data
        @prawn = prawn
        @options = options

        font_registry = Prawn::SVG::FontRegistry.new(prawn.font_families)

        @document = Document.new(data, [prawn.bounds.width, prawn.bounds.height], options, font_registry: font_registry, &block)
      end

      #
      # Draws the SVG to the Prawn::Document object.
      #
      def draw
        if @document.sizing.invalid?
          @document.warnings << "Zero or negative sizing data means this SVG cannot be rendered"
          return
        end

        @document.warnings.clear

        prawn.save_font do
          prawn.bounding_box(position, :width => @document.sizing.output_width, :height => @document.sizing.output_height) do
            prawn.save_graphics_state do
              clip_rectangle 0, 0, @document.sizing.output_width, @document.sizing.output_height

              calls = []
              root_element = Prawn::SVG::Elements::Root.new(@document, @document.root, calls)
              root_element.process

              proc_creator(prawn, calls).call
            end
          end
        end
      end

      def sizing
        document.sizing
      end

      def resize(width: nil, height: nil)
        document.calculate_sizing(requested_width: width, requested_height: height)
      end

      def position
        @options[:at] || [x_based_on_requested_alignment, y_based_on_requested_alignment]
      end

      def self.font_path # backwards support for when the font_path used to be stored on this class
        Prawn::SVG::FontRegistry.font_path
      end

      private

      def x_based_on_requested_alignment
        case options[:position]
        when :left, nil
          0
        when :center, :centre
          (@document.sizing.bounds[0] - @document.sizing.output_width) / 2.0
        when :right
          @document.sizing.bounds[0] - @document.sizing.output_width
        when Numeric
          options[:position]
        else
          raise ArgumentError, "options[:position] must be one of nil, :left, :right, :center or a number"
        end
      end

      def y_based_on_requested_alignment
        case options[:vposition]
        when nil
          prawn.cursor
        when :top
          @document.sizing.bounds[1]
        when :center, :centre
          @document.sizing.bounds[1] - (@document.sizing.bounds[1] - @document.sizing.output_height) / 2.0
        when :bottom
          @document.sizing.output_height
        when Numeric
          @document.sizing.bounds[1] - options[:vposition]
        else
          raise ArgumentError, "options[:vposition] must be one of nil, :top, :right, :bottom or a number"
        end
      end

      def proc_creator(prawn, calls)
        Proc.new {issue_prawn_command(prawn, calls)}
      end

      def issue_prawn_command(prawn, calls)
        calls.each do |call, arguments, children|
          skip = false

          rewrite_call_arguments(prawn, call, arguments) do
            issue_prawn_command(prawn, children) if children.any?
            skip = true
          end

          if skip
            # the call has been overridden
          elsif children.empty?
            prawn.send(call, *arguments)
          else
            prawn.send(call, *arguments, &proc_creator(prawn, children))
          end
        end
      end

      def rewrite_call_arguments(prawn, call, arguments)
        case call
        when 'text_group'
          @cursor = [0, document.sizing.output_height]
          yield

        when 'draw_text'
          text, options = arguments

          at = options.fetch(:at)

          at[0] = @cursor[0] if at[0] == :relative
          at[1] = @cursor[1] if at[1] == :relative

          if offset = options.delete(:offset)
            at[0] += offset[0]
            at[1] -= offset[1]
          end

          width = prawn.width_of(text, options.merge(kerning: true))

          if stretch_to_width = options.delete(:stretch_to_width)
            factor = stretch_to_width.to_f * 100 / width.to_f
            prawn.add_content "#{factor} Tz"
            width = stretch_to_width.to_f
          end

          if pad_to_width = options.delete(:pad_to_width)
            padding_required = pad_to_width.to_f - width.to_f
            padding_per_character = padding_required / text.length.to_f
            prawn.add_content "#{padding_per_character} Tc"
            width = pad_to_width.to_f
          end

          case options.delete(:text_anchor)
          when 'middle'
            at[0] -= width / 2
            @cursor = [at[0] + width / 2, at[1]]
          when 'end'
            at[0] -= width
            @cursor = at.dup
          else
            @cursor = [at[0] + width, at[1]]
          end

        when 'transformation_matrix'
          left = prawn.bounds.absolute_left
          top = prawn.bounds.absolute_top
          arguments[4] += left - (left * arguments[0] + top * arguments[2])
          arguments[5] += top - (left * arguments[1] + top * arguments[3])

        when 'clip'
          prawn.add_content "W n" # clip to path
          yield

        when 'save'
          prawn.save_graphics_state
          yield

        when 'restore'
          prawn.restore_graphics_state
          yield

        when "end_path"
          yield
          prawn.add_content "n" # end path

        when 'fill_and_stroke'
          yield
          # prawn (as at 2.0.1 anyway) uses 'b' for its fill_and_stroke.  'b' is 'h' (closepath) + 'B', and we
          # never want closepath to be automatically run as it stuffs up many drawing operations, such as dashes
          # and line caps, and makes paths close that we didn't ask to be closed when fill is specified.
          even_odd = arguments[0].is_a?(Hash) && arguments[0][:fill_rule] == :even_odd
          content  = even_odd ? 'B*' : 'B'
          prawn.add_content content

        when 'noop'
          yield
        end
      end

      def clip_rectangle(x, y, width, height)
        prawn.move_to x, y
        prawn.line_to x + width, y
        prawn.line_to x + width, y + height
        prawn.line_to x, y + height
        prawn.close_path
        prawn.add_content "W n" # clip to path
      end
    end
  end
end
