module Prawn::SVG::Calculators
  class DocumentSizing
    DEFAULT_ASPECT_RATIO = 'xMidYMid meet'.freeze

    attr_writer :document_width, :document_height
    attr_writer :view_box, :preserve_aspect_ratio

    attr_reader :bounds
    attr_reader :x_offset, :y_offset, :x_scale, :y_scale
    attr_reader :viewport_width, :viewport_height, :viewport_diagonal, :output_width, :output_height

    def initialize(bounds, attributes = nil)
      @bounds = bounds
      set_from_attributes(attributes) if attributes
    end

    def set_from_attributes(attributes)
      @document_width = attributes['width']
      @document_height = attributes['height']
      @view_box = attributes['viewBox']
      @preserve_aspect_ratio = attributes['preserveAspectRatio'] || DEFAULT_ASPECT_RATIO
    end

    def calculate
      @x_offset = @y_offset = 0
      @x_scale = @y_scale = 1

      container_width = @requested_width || @bounds[0]
      container_height = @requested_height || @bounds[1]

      @output_width = Pixels::Measurement.to_pixels(@document_width || @requested_width, container_width)
      @output_height = Pixels::Measurement.to_pixels(@document_height || @requested_height, container_height)

      if @view_box
        values = @view_box.strip.split(Prawn::SVG::Elements::COMMA_WSP_REGEXP)
        @x_offset, @y_offset, @viewport_width, @viewport_height = values.map(&:to_f)

        if @viewport_width.positive? && @viewport_height.positive?
          # If neither the width nor height was specified, use the entire width and the viewbox ratio
          # to determine the height.
          @output_width = container_width if @output_width.nil? && @output_height.nil?

          # If one of the output dimensions is missing, calculate it from the other one
          # using the ratio of the viewport width to height.
          @output_width ||= @output_height * @viewport_width / @viewport_height
          @output_height ||= @output_width * @viewport_height / @viewport_width

          aspect = AspectRatio.new(@preserve_aspect_ratio, [@output_width, @output_height],
            [@viewport_width, @viewport_height])
          @x_scale = aspect.width / @viewport_width
          @y_scale = aspect.height / @viewport_height
          @x_offset -= aspect.x / @x_scale
          @y_offset -= aspect.y / @y_scale
        end
      else
        @output_width ||= container_width
        @output_height ||= container_height

        @viewport_width = @output_width
        @viewport_height = @output_height
      end

      return if invalid?

      # SVG 1.1 section 7.10
      @viewport_diagonal = Math.sqrt((@viewport_width**2) + (@viewport_height**2)) / Math.sqrt(2)

      if @requested_width
        scale = @requested_width / @output_width
        @output_width = @requested_width
        @output_height *= scale
        @x_scale *= scale
        @y_scale *= scale

      elsif @requested_height
        scale = @requested_height / @output_height
        @output_height = @requested_height
        @output_width *= scale
        @x_scale *= scale
        @y_scale *= scale
      end

      self
    end

    def invalid?
      @viewport_width <= 0 ||
        @viewport_height <= 0 ||
        @output_width <= 0 ||
        @output_height <= 0 ||
        (@requested_width && @requested_width <= 0) ||
        (@requested_height && @requested_height <= 0)
    end

    def requested_width=(value)
      @requested_width = value&.to_f
    end

    def requested_height=(value)
      @requested_height = value&.to_f
    end
  end
end
