module Mittsu
  class Line < Object3D
    attr_accessor :geometry, :material, :mode, :type, :morph_target_base

    def initialize(geometry = nil, material = nil, mode = nil)
      super()

      @type = 'Line'

      @geometry = geometry || Geometry.new
      @material = material || LineBasicMaterial.new(color: (rand * 0xffffff).to_i)

      @mode = mode || LineStrip

      @_inverse_matrix = Matrix4.new
      @_ray = Ray.new
      @_sphere = Sphere.new
    end

    def raycast(raycaster, intersects)
      precision = raycaster.line_precision
      precision_sq = precision * precision

      @geometry.compute_bounding_sphere if @geometry.bounding_sphere.nil?

      # Checking bounding_sphere distance to ray

      @_sphere.copy(geometry.bounding_sphere)
      @_sphere.apply_matrix4(@matrix_world)

      return unless raycaster.ray.intersetion_sphere?(sphere)

      @_inverse_matrix.get_inverse(@matrix_world)
      @_ray.copy(raycaster.ray).apply_matrix4(@_inverse_matrix)

      v_start = Vector3.new
      v_end = Vector3.new
      inter_segment = Vector3.new
      inter_ray = Vector3.new
      step = @mode == LineStrip ? 1 : 2

      if geometry.is_a?(BufferGeometry)
        attributes = @geometry.attributes

        if !attributes.index.nil?
          indices = attributes.index.array
          positions = attributes.position.array
          offsets = geometry.offsets

          if offsets.empty?
            offsets = [{ start: 0, count: indices.length, index: 0 }]
          end

          offsets.each_with_index do |offset, oi|
            start = offset[:start]
            count = offset[:count]

            (start...(count-1)).step(step).each do |i|
              v_start.from_array(positions, a * 3)
              v_end.from_array(positions, b * 3)

              dist_sq = @_ray.distance_sq_to_segment(v_start, v_end, inter_ray, inter_segment)

              next if dist_sq > precision_sq

              distance = @_ray.origin.distance_to(inter_ray)

              next if distance < raycaster.near || distance > raycaster.far

              intersects << {
                distance: distance,
                # What to we want? intersection point on the ray or on the segment??
                # point: raycaster.ray.at(distance),
                point: inter_segment.clone.apply_matrix4(@matrix_matrix),
                index: i,
                offset_index: oi,
                face: nil,
                face_index: nil,
                object: self
              }
            end
          end
        else
          positions = attributes.position.array

          (0...(positions.length / 3 - 1)).step(step) do |i|
            v_start.from_array(positions, 3 * i)
            v_end.from_array(positions, 3 * i + 3)

            dist_sq = @_ray.distance_sq_to_segment(v_start, v_end, inter_ray, inter_segment)

            next if dist_sq > precision_sq

            distance = @_ray.origin.distance_to(inter_ray)

            next if distance < raycaster.near || distance > raycaster.far

            intersects << {
              distance: distance,
              # What do we want? intersection point on the ray or on the segment??
              # point: raycaster.ray.at(distance),
              point: inter_segment.clone.apply_matrix4(@matrix_world),
              index: i,
              face: nil,
              face_index: nil,
              object: self
            }
          end
        end
      elsif geometry.is_a?(Geometry)
        vertices = @geometry.vertices
        nb_vertices = vertices.length

        (0...(nb_vertices - 1)).step(step).each do |i|
          dist_sq = @_ray.distance_sq_to_segment(vertices[i], vertices[i + 1], inter_ray, inter_segment)

          next if dist_sq > precision_sq

          distance = @_ray.origin.distance_to(inter_ray)

          next if distance < raycaster.near || distance > raycaster.far

          intersects << {
            distance: distance,
            # What do we want? intersection point on the ray or on the segment??
            # point: raycaster.ray.at(distance),
            point: inter_segment.clone.apply_matrix4(@matrix_world),
            index: i,
            face: nil,
            face_index: nil,
            object: self
          }
        end
      end
    end

    def clone(object = Line.new(@geometry, @material, @mode))
      super(object)
      object
    end

    protected

    def jsonify
      data = super
      data[:object][:mode] = self.mode
      data
    end
  end
end
