module Prawn::SVG::Pathable
  Move = Struct.new(:destination)
  Close = Struct.new(:destination)
  Line = Struct.new(:destination)
  Curve = Struct.new(:destination, :point1, :point2)

  def bounding_box
    points = commands.map { |command| translate(command.destination) }
    x1, x2 = points.map(&:first).minmax
    y2, y1 = points.map(&:last).minmax

    [x1, y1, x2, y2]
  end

  protected

  def apply_commands
    commands.each do |command|
      case command
      when Move
        add_call 'move_to', translate(command.destination)
      when Close
        add_call 'close_path'
      when Line
        add_call 'line_to', translate(command.destination)
      when Curve
        add_call 'curve_to', translate(command.destination), bounds: [translate(command.point1), translate(command.point2)]
      else
        raise NotImplementedError, "Unknown path command type"
      end
    end
  end

  def apply_markers
    if marker = extract_element_from_url_id_reference(properties.marker_start, "marker")
      marker.apply_marker(self, point: commands.first.destination, angle: angles.first)
    end

    if marker = extract_element_from_url_id_reference(properties.marker_mid, "marker")
      (1..commands.length-2).each do |index|
        marker.apply_marker(self, point: commands[index].destination, angle: angles[index])
      end
    end

    if marker = extract_element_from_url_id_reference(properties.marker_end, "marker")
      marker.apply_marker(self, point: commands.last.destination, angle: angles.last)
    end
  end

  def angles
    return @angles if @angles

    last_point = nil

    destination_angles = commands.map do |command|
      angles = case command
      when Move
        [nil, nil]
      when Close, Line
        angle = Math.atan2(command.destination[1] - last_point[1], command.destination[0] - last_point[0]) * 180.0 / Math::PI
        [angle, angle]
      when Curve
        point = select_non_equal_point(last_point, command.point1, command.point2, command.destination)
        start = Math.atan2(point[1] - last_point[1], point[0] - last_point[0]) * 180.0 / Math::PI

        point = select_non_equal_point(command.destination, command.point2, command.point1, last_point)
        stop = Math.atan2(command.destination[1] - point[1], command.destination[0] - point[0]) * 180.0 / Math::PI

        [start, stop]
      else
        raise NotImplementedError, "Unknown path command type"
      end

      last_point = command.destination
      angles
    end

    angles = destination_angles.each_cons(2).map do |first_angles, second_angles|
      if first_angles.first.nil?
        second_angles.first || 0
      elsif second_angles.first.nil?
        first_angles.last
      else
        first = first_angles.last
        second = second_angles.last
        bisect = (first + second) / 2.0

        if (first - second).abs > 180
          bisect >= 0 ? bisect - 180 : bisect + 180
        else
          bisect
        end
      end
    end

    if commands.last.is_a?(Close)
      first = destination_angles.last.last || 0
      second = angles.first
      bisect = (first + second) / 2.0

      angles << if (first - second).abs > 180
        bisect >= 0 ? bisect - 180 : bisect + 180
      else
        bisect
      end
    else
      angles << destination_angles.last.last
    end

    @angles = angles
  end

  def parse_points(points_string)
    values = points_string.
      to_s.
      strip.
      gsub(/(\d)-(\d)/, '\1 -\2').
      split(Prawn::SVG::Elements::COMMA_WSP_REGEXP).
      map(&:to_f)

    if values.length % 2 == 1
      document.warnings << "points attribute has an odd number of points; ignoring the last one"
      values.pop
    end

    raise Prawn::SVG::Elements::Base::SkipElementQuietly if values.length == 0

    values.each_slice(2).to_a
  end

  def translate(point)
    [point[0].to_f, document.sizing.output_height - point[1].to_f]
  end

  private

  def select_non_equal_point(base, point_a, point_b, point_c)
    if point_a != base
      point_a
    elsif point_b != base
      point_b
    else
      point_c
    end
  end
end