module AE



class Console



class DrawEntity

  def initialize(model=nil)
    @model = (model.is_a?(Sketchup::Model)) ? model : Sketchup.active_model
    @entity = nil
    @transformations = []
    # Color for elements in the active path
    @color = @model.rendering_options["HighlightColor"]
    @color_t = Sketchup::Color.new(@color) # transparent for polygons
    @color_t.alpha = 0.5
    # Color for elements in sibling paths
    @color_rest = Sketchup::Color.new(@color)
    @color_rest.alpha = 0.25
    @color_rest_t = Sketchup::Color.new(@color_rest) # transparent for polygons
    @color_rest_t.alpha = 0.05
  end

  def select(entity)
    return @entity = nil unless entity.is_a?(Sketchup::Drawingelement) && entity.model || entity.is_a?(Sketchup::Vertex) || entity.is_a?(Geom::Point3d) || entity.is_a?(Geom::BoundingBox)
    @entity = entity
    # Find all occurences of the entity (in instances) and collect their transformations.
    isCGI = (@entity.is_a?(Sketchup::ComponentInstance) || @entity.is_a?(Sketchup::Group) || @entity.is_a?(Sketchup::Image))
    @transformations = []
    @transformations_rest = []
    # This temporary hash collects for each occurence of @entity an array of the
    # nesting path (all parent entities) and assigns their brutto transformation.
    paths_transformations = { [@entity] => (isCGI) ? @entity.transformation : Geom::Transformation.new }
    if @entity.is_a?(Sketchup::Drawingelement) || @entity.is_a?(Sketchup::Vertex)
      until paths_transformations.empty?
        # Since Ruby 2, we cannot add a new key into hash during iteration, so we collect them in a temporary hash and add them afterwards.
        add_paths_transformations = {}
        paths_transformations.each{ |path, transformation|
          outermost = path.first
          # If the outermost is already the model, end the search.
          if outermost.parent.is_a?(Sketchup::Model) || outermost.parent.nil?
            # Sibling path
            if @model.active_path && !(@model.active_path - path - [@entity]).empty?
              @transformations_rest << transformation
            # Active path
            else
              @transformations << transformation
            end
            paths_transformations.delete(path)
          # Otherwise look if it has siblings, ie. the parent has instances with the same entity.
          else
            instances = (outermost.is_a?(Sketchup::ComponentDefinition)) ?
                            outermost.instances :
                        (outermost.respond_to?(:parent) && outermost.parent.respond_to?(:instances)) ?
                            outermost.parent.instances : []
            instances.each{ |instance|
              add_paths_transformations[ [instance].concat(path) ] = instance.transformation * paths_transformations[path]
            }
            paths_transformations.delete(path)
          end
        }
        paths_transformations.merge!(add_paths_transformations)
      end
    else
      @transformations << @model.edit_transform
    end
    @model.active_view.invalidate
  end

  def deactivate(view)
    view.invalidate
  end

  def draw(view)
    # Drawing settings
    view.drawing_color = color = @color
    color_t = @color_t
    view.line_width = 5

    # Point3d / Vertex
    if @entity.is_a?(Geom::Point3d) || @entity.is_a?(Sketchup::Vertex)
      block = Proc.new{ |t|
        p = (@entity.is_a?(Sketchup::Vertex)) ? @entity.position : @entity
        p = view.screen_coords(p.transform(t))
        p.x = p.x.to_i; p.y = p.y.to_i; p.z = p.z.to_i
        r = 3
        view.draw2d(GL_LINES, p + [r,r,0], p + [-r,-r,0], p + [r,-r,0], p + [-r,r,0])
      }
      view.line_width = 3
      view.drawing_color = @color
      @transformations.each(&block)
      view.drawing_color = @color_rest
      @transformations_rest.each(&block)

    # Edge
    elsif @entity.is_a?(Sketchup::Edge) || @entity.is_a?(Sketchup::Curve) || @entity.is_a?(Sketchup::ArcCurve)
      block = Proc.new{ |t|
        view.draw2d(GL_LINE_STRIP, @entity.vertices.map{ |v| view.screen_coords(v.position.transform(t)) })
      }
      view.drawing_color = @color
      @transformations.each(&block)
      view.drawing_color = @color_rest
      @transformations_rest.each(&block)

    # Face
    elsif @entity.is_a?(Sketchup::Face)
      block = Proc.new{ |t|
        view.drawing_color = color
        ps = @entity.outer_loop.vertices.map{ |v| view.screen_coords(v.position.transform!(t)) }
        view.draw2d(GL_LINE_LOOP, ps)
        view.drawing_color = color_t
        # TODO: This fails for concave faces, because then the OpenGL polygon overlaps itself.
        view.draw2d(GL_POLYGON, ps) if Sketchup.version.to_i >= 8 # support of transparent color
        # Test this solution:
        # ps = @entity.mesh.points.map{ |p| p.transform!(t) }
        # view.draw(GL_TRIANGLE_STRIP, ps) if Sketchup.version.to_i >= 8 # support of transparent color
        # ps.each_with_index{ |p,i| view.draw_text(view.screen_coords(p), i.to_s) }
      }
      color = @color
      color_t = @color_t
      @transformations.each(&block)
      color = @color_rest
      color_t = @color_rest_t
      @transformations_rest.each(&block)

    # Group / Component / Image
    elsif @entity.is_a?(Sketchup::Group) || @entity.is_a?(Sketchup::ComponentInstance) || @entity.is_a?(Sketchup::Image) || @entity.is_a?(Sketchup::ComponentDefinition) || @entity.is_a?(Geom::BoundingBox)
      bounds = case @entity
        when Sketchup::Group then @entity.entities.parent.bounds
        when Sketchup::ComponentDefinition then @entity.bounds
        when Geom::BoundingBox then @entity
        else @entity.definition.bounds
      end
      block = Proc.new{ |t|
        view.drawing_color = color
        ps = (0..7).map{ |i| bounds.corner(i).transform!(t) }
        # A quad strip around the bounding box
        ps1 = [ ps[0],ps[1],  ps[2],ps[3],  ps[6],ps[7],  ps[4],ps[5], ps[0],ps[1] ]
        # Two quads not covered by the quad strip
        ps2 = [ ps[0],ps[2],  ps[6],ps[4],  ps[1],ps[3],  ps[7],ps[5] ]
        # Quad strips ps1, ps2 can be interpreted as lines, but these are missing:
        ps3 = [ ps[0],ps[4],  ps[1],ps[5],  ps[2],ps[6],  ps[3],ps[7] ]
        # Draw lines
        view.draw2d(GL_LINES, [ps1, ps2, ps3].flatten.map{ |p| view.screen_coords(p) })
        # Draw polygons
        if Sketchup.version.to_i >= 8 # support of transparent color
          view.drawing_color = color_t
          view.draw(GL_QUAD_STRIP, ps1)
          view.draw(GL_QUADS, ps2)
        end
      }
      color = @color
      color_t = @color_t
      @transformations.each(&block)
      color = @color_rest
      color_t = @color_rest_t
      @transformations_rest.each(&block)

    # Other. For entities with undefined shape, draw a circle around them.
    elsif @entity.is_a?(Sketchup::Drawingelement) && !(@entity.is_a?(Sketchup::Text) && !@entity.has_leader?)
      cp = @entity.bounds.center
      # Diameter; consider a minimum for Drawingelements that have no diameter
      d = [@entity.bounds.diagonal/2.0, view.pixels_to_model(5, cp)].max
      e = view.camera.eye.vector_to(view.camera.target)
      vec = view.camera.up
      vec.length = d
      t_circle = Geom::Transformation.new(cp, e, 10.degrees)
      block = Proc.new{ |t|
        circle = [cp + vec]
        (1..36).each{ |i| circle << circle.last.transform(t_circle).transform(t) }
        # Convert to screen space (so that it won't be covered by other geometry).
        circle.map!{ |p| view.screen_coords(p) }
        view.draw2d(GL_LINE_STRIP, circle)
      }
      view.drawing_color = @color
      @transformations.each(&block)
      view.drawing_color = @color_rest
      @transformations_rest.each(&block)
    end
  end

end # class DrawEntity



# Extension for highlighting SketchUp entities and Point3d
class HighlightEntity < DrawEntity

  # Since SketchUp used to crash when reverting to the previous tool (pop_tool)
  # if there was no previous tool, we keep track of the tool stack length.
  # It seems my current installations of SU2013, SU8, SU7.1 return true/false
  # instead of the last tool (according to API), and do not crash.
  # This tool works only on one model.
  @@tool_stack_length ||= 1
  @@instance ||= nil
  @@model ||= nil

  # Change the tool (for being able to draw at the screen)
  # and highlight the entity with given object_id.
  # @param [String] id_string a string of the object id that inspect returns.
  def self.entity(id)
    e = ObjectSpace._id2ref(id) rescue nil
    if e.is_a?(Sketchup::Entity) && e.valid? || e.is_a?(Geom::BoundingBox)
      @@model = (e.respond_to?(:model)) ? e.model || Sketchup.active_model : Sketchup.active_model
      # Get an instance of this tool and highlight the entity.
      @@model.tools.push_tool(@@instance || @@instance = self.new)
      @@instance.select(e)
      @@tool_stack_length += 1
      return true
    else
      return false
    end
  end

  # Change the tool (for being able to draw at the screen)
  # and highlight the given Point.
  # @param [Numeric] x
  # @param [Numeric] y
  # @param [Numeric] z
  # @returns [Boolean] success
  def self.point(x, y, z)
    point = Geom::Point3d.new(x, y, z) rescue nil
    if point
      @@model = Sketchup.active_model
      @@model.tools.push_tool(@@instance || @@instance = self.new)
      @@instance.select(point)
      @@tool_stack_length += 1
      return true
    else
      return false
    end
  end

  # Change the tool back.
  def self.stop
    if @@tool_stack_length > 1
      # Be careful, this can crash SketchUp when removing the only tool on the stack.
      @@model.tools.pop_tool
      @@tool_stack_length -= 1
    end
  end

  # Instance methods:

  # This is because we want to draw onto the screen only when the cursor hovers the WebDialog. [optional]
  def onMouseMove(flags, x, y, view)
    self.class.stop
  end

end # class HighlightEntity



class SelectEntity < DrawEntity

  # We select this tool only temporarily over the current tool and then switch back.
  # Since SketchUp used to crash when reverting to the previous tool (pop_tool)
  # if there was no previous tool, we keep track of the tool stack length.
  # It seems my current installations of SU2013, SU8, SU7.1 return true/false
  # instead of the last tool (according to API), and do not crash.
  # This tool works in a multi-document interface.
  @@tool_stack_length ||= Hash.new(1)
  @@active_instances ||= []
  def self.select_tool(*args, &block)
    model = Sketchup.active_model
    instance = self.new(*args, &block)
    model.tools.push_tool(instance)
    @@tool_stack_length[model] += 1
  end


  def self.deselect_tool
    @@active_instances.each{ |instance| instance.deselect_tool }
  end


  def deselect_tool
    if @@tool_stack_length[@model] > 1
      # Be careful, this can crash SketchUp when removing the only tool on the stack.
      success = @model.tools.pop_tool
      # pop_tool calls already deactivate
    end
  end

  def activate
    @@active_instances << self
    Sketchup.status_text = TRANSLATE["Click to select an entity. Right-click to abort. Press the ctrl key to select points. Press the shift key to use inferencing."]
  end

  def deactivate(view)
    @@active_instances.delete(self)
    @@tool_stack_length[@model] -= 1 if @@tool_stack_length[@model] > 1
    super
  end

  def initialize(name=nil, binding=nil, &block)
    @model = Sketchup.active_model
    @callback = block if block_given?
    @ctrl = false
    @shift = false
    @name = name if name.is_a?(String) && name[/^[^\!\"\'\`\@\$\%\|\&\/\(\)\[\]\{\}\,\;\?\<\>\=\+\-\*\/\#\~\\]+$/]
    @binding = (binding.is_a?(Binding)) ? binding : TOPLEVEL_BINDING
    @cursor = UI.create_cursor(File.join(DIR, "images","cursor_select_entity.png"), 10, 10)
    @ip = Sketchup::InputPoint.new
    super()
  end

  def onSetCursor
    UI.set_cursor(@cursor)
  end

  def onLButtonDown(flags, x, y, view)
    pick_entity_or_point(view, x, y)
    return if @entity.nil?
    # Create a unique name for the reference if no desired name was given.
    if @name.is_a?(String) && @name[/^[^\!\"\'\`\@\$\%\|\&\/\(\)\[\]\{\}\,\;\?\<\>\=\+\-\*\/\#\~\\]+$/]
      name = @name
    else
      name = suggested_name = case @entity
      # Short names: (You can add more)
      when Sketchup::ComponentInstance
        "component"
      when Geom::Point3d
        "p"
      # Or generic name:
      else
        @entity.respond_to?(:typename) ?
          @entity.typename.downcase    :
          @entity.class.to_s[/[^\:]+$/].downcase
      end
      i = 0
      name = "#{suggested_name}#{i+=1}" while AE::Console.unnested_eval("defined?(#{name})", @binding) && @entity != AE::Console.unnested_eval("#{name}", @binding)
    end
    # Assign the entity to that reference and return the reference as a string.
    id = @entity.object_id
    AE::Console.unnested_eval("#{name} = ObjectSpace._id2ref(#{id})", @binding)
    @callback.call(name) if @callback.is_a?(Proc)
    # Deselect this tool.
    deselect_tool
  end

  def getMenu(menu)
    deselect_tool
  end

  def onRButtonDown(flags, x, y, view)
    deselect_tool
  end

  def onMouseMove(flags, x, y, view)
    pick_entity_or_point(view, x, y)
  end

  def onKeyDown(key, repeat, flags, view)
    @ctrl = true if key == COPY_MODIFIER_KEY # VK_CONTROL
    @shift = true if key == CONSTRAIN_MODIFIER_KEY # VK_SHIFT
  end

  def onKeyUp(key, repeat, flags, view)
    @ctrl = false if key == COPY_MODIFIER_KEY # VK_CONTROL
    @shift = false if key == CONSTRAIN_MODIFIER_KEY # VK_SHIFT
  end

  def pick_entity_or_point(view, x, y)
    # Get point/inference under cursor.
    if @ctrl || AE::Console.ctrl?
      # With inferencing: use InputPoint
      if @shift || AE::Console.shift?
        @ip.pick(view, x, y)
        return unless @ip.valid?
        point = @ip.position.transform(@model.edit_transform.inverse)
      # Without inferencing: use a raytest
      else
        ray = view.pickray(x, y)
        result = @model.raytest(ray)
        return unless result
        point = result[0].transform(@model.edit_transform.inverse)
      end
      select(point)

    # Get an entity is under the cursor.
    else
      # With inferencing: use InputPoint
      if @shift || AE::Console.shift?
        @ip.pick(view, x, y)
        return unless @ip.valid?
        entity = @ip.edge || @ip.face
      # Without inferencing: use PickHelper
      else
        pick_helper = view.pick_helper
        pick_helper.do_pick(x, y)
        entity = pick_helper.best_picked
        entity = entity.curve if entity.is_a?(Sketchup::Edge) && entity.curve
      end
      select(entity)
    end
  end

end



end # class Console



end # module AE
