# See the loader file for license and author info

require 'sketchup.rb'
module CLF_Extensions_NS
  module CLF_Greeble

  @@first_run = true
  @@min_height = []
  @@max_height = []
  @@min_offset = []
  @@max_offset = []
  @@greeble_face = []
  @@scale = []

    # This method accepts a face and a height to push the face.
    # It returns the Object Reference of the new face that is
    # parallel to the original face.
    def self.push_me(e_face, p_height)
      e_ents = []
      c_ents = []
      n_ents = []
      count = 0
      new_face = 0
      e_ents = e_face.all_connected.to_a
      e_face.pushpull( p_height, true)
      c_ents = e_face.all_connected.to_a
      n_ents = c_ents - e_ents
      n_ents.each do |ent|
        if ent.is_a? Sketchup::Face
          if ent.normal == e_face.normal
            new_face = ent
            break
          end
        end
      end
      new_face
    end
    
    # This method accepts a face and a float number.  It then performs unfirom scale
    # on the face using the faces centerpoint and the float as the percentage to scale.
    def self.scale_on_center( e_face, s_percent )
      cp = Geom::Point3d.new( e_face.bounds.center )
      scale_t = Geom::Transformation.scaling( cp, s_percent.to_f )
      #UI.messagebox "1"
      e_face.parent.entities.transform_entities( scale_t, e_face )
      #UI.messagebox "2"
    end
    
    # This is the main method that is called from the Plugin menu.  It executes most of the script
    # and does the bulk of the work.
    def self.greeble
      model = Sketchup.active_model
      clselection = model.selection
      retry_menu = 0
      
      if @@first_run
        values = ["1'", "5'", "-1'", "-2'", "0", "100"]
        @@first_run = false
      else
        values = [ @@min_height, @@max_height, @@min_offset,	@@max_offset, @@greeble_face, @@scale]
      end

      result = Sketchup.set_status_text( "Happy Greeble-ing! Chris", SB_PROMPT )
      group_greeble_top = 0
       
      # This checks to see if the user has anything selected.  If the selection is empty, a warning is issued.
      if clselection.empty?
        UI.messagebox "You have nothing selected.  This will Greeble all faces that are not grouped."
        entities = model.active_entities.to_a
      else
        entities = model.selection.to_a
      end
       
      # This is the first menu.  It is set to run while the variable retry_menu is set to 0.  So once the menu is complete,
      # the retry_menu is set to 1.  But if there was an incorrect value entered, then retry_menu gets set back to zero and the
      # menu is re-run.
      while retry_menu == 0
        prompts = ["Min Height", "Max Height", "Min Offset Distance (negative is inward)", "Max offset Distance (negative is inward)",
        "Greeble the offset face(0) or original face(1)?", "Scale the Greeble? (In percent)"]
        results = inputbox prompts, values, "Box Dimensions"
        
        @@min_height = results[0]
        @@max_height = results[1]
        @@min_offset = results[2]
        @@max_offset = results[3]
        @@greeble_face = results[4]
        @@scale = results[5]
        
        
        retry_menu = 1
        if @@min_height.to_l > @@max_height.to_l
          UI.messagebox "The Minimum height needs to be equal to or smaller than the maximum height."
          retry_menu = 0
          @@min_height = @@max_height
        end
        if @@greeble_face.to_i == 0 or @@greeble_face.to_i == 1
        else
          UI.messagebox "Greeble the offset(0) or original(1) face can only be set to 0 or 1."
          retry_menu = 0
          @@greeble_face = 0
        end
        if	(@@min_offset.to_l >= 0 and @@max_offset.to_l >= 0) or (@@min_offset.to_l <= 0 and @@max_offset.to_l <= 0)
          if @@max_offset.to_l.abs - @@min_offset.to_l.abs < 0
            retry_menu = 0
            @@min_offset = @@max_offset
            UI.messagebox "Max offset must be a larger number than min offset"
          end
        else
          UI.messagebox "Offset Min/Max values need to be either both positive or both negative"
          retry_menu = 0
        end	
        if @@scale.to_i <= 0
          UI.messagebox "The scale needs to be a positive number and must be larger than 0."
          retry_menu = 0
          @@scale = 100
        end
        
        values = [@@min_height, @@max_height, @@min_offset, @@max_offset, @@greeble_face, @@scale]
      end
      
      # Asks the user if they want to group the top faces separately.  Useful in some applications of the script.
      group_greeble_top = UI.messagebox( "Would you like to group separately all the topmost Greeble faces?", MB_YESNOCANCEL, "Menu" )
      if group_greeble_top == 2
        return nil
      end
      
      clent = entities[0]
      #clcount = 0
      clface = []
      greebletops = []
      
      a = Sketchup.version
      b = a[0,1].to_i
      if b > 6
        model.start_operation("Greeble", true)
      else
        model.start_operation("Greeble")
      end
      
      # This leaves me with an array of just the faces.
      #pb = ProgressBar.new(entities.length,"Finding Faces from #{entities.length} total entities")
      entities.each do |e|
        #pb.update(clcount)
        clface << e if e.is_a? Sketchup::Face
        #clcount = clcount + 1
      end

      clrandheightdif = @@max_height.to_l - @@min_height.to_l
      clrandoffsetdif = @@max_offset.to_l - @@min_offset.to_l
      cloffset = clrandoffsetdif + @@min_offset.to_i + @@max_offset.to_i
      clrandoffsetneg = 1        # Arbitrarily sets the value to 1 which is Positive.
      if @@max_offset.to_l < 0     # If the offset values are negative, then it changes the value to 0.
        clrandoffsetneg = 0
      end
      #pb2 = ProgressBar.new(clface.length,"Making #{clface.length} Greebles")
      #clcount = 0
      clface.each do |e|
        #pb2.update(clcount)
        if e.valid?
          if @@min_offset.to_i == 0 and @@max_offset.to_i == 0
            if clrandheightdif == 0
              new_face = push_me( e, @@min_height.to_l )
            else
              clpp = rand(clrandheightdif) + @@min_height.to_l + 1
              new_face = push_me( e, clpp )
            end
          else
            clpp = rand(clrandheightdif) + @@min_height.to_l + 1
            if clrandoffsetdif == 0
              cl_newface = offset( @@min_offset.to_l, e )
            else
              if clrandoffsetneg == 1 #decides if the randomoffset difference is negative. 0 is negative.
                clrandoffset = rand(clrandoffsetdif) + @@min_offset.to_l + 1
                cl_newface = offset( clrandoffset, e )
              else
                clrandoffset = -(rand(clrandoffsetdif.abs) + @@min_offset.to_l.abs + 1)
                cl_newface = offset( clrandoffset, e )
              end 
            end
            
            # Tests the direction of the new face to the original face.  If they don't face the same direction,
            # the new face gets reversed to match the original face.
            orig_vector = e.normal
            new_vector = cl_newface.normal
            if orig_vector != new_vector
              cl_newface.reverse!
            end
            if clrandheightdif == 0
              if @@greeble_face.to_i == 0
                new_face = push_me( cl_newface, @@min_height.to_l )
              else
                new_face = push_me( e, @@min_height.to_l )
              end
            else
              if @@greeble_face.to_i == 0
                new_face = push_me( cl_newface, clpp )
              else
                new_face = push_me( e, clpp )
              end
            end
          end
          greebletops.push new_face
          scale_percent = (@@scale.to_f / 100.0)
          scale_on_center(new_face, scale_percent) unless @@scale.to_i == 100
        end
        #clcount = clcount + 1
      end
      if group_greeble_top == 6 # Groups greeble tops if the users said "yes" earlier.
        entities = model.active_entities
        group = entities.add_group( greebletops )
      end
    model.commit_operation
    end
    
    def self.offset(dist, face)
      #begin
        pi = Math::PI
        if (not ((dist.class==Fixnum || dist.class==Float || dist.class==Length) && dist!=0))
          return nil
        end
        verts=face.outer_loop.vertices
        pts = []
        
        # CREATE ARRAY pts OF OFFSET POINTS FROM FACE
        
        0.upto(verts.length-1) do |a|
          vec1 = (verts[a].position-verts[a-(verts.length-1)].position).normalize
          vec2 = (verts[a].position-verts[a-1].position).normalize
          vec3 = (vec1+vec2).normalize
          if vec3.valid?
            ang = vec1.angle_between(vec2)/2
            ang = pi/2 if vec1.parallel?(vec2)
            vec3.length = dist/Math::sin(ang)
            t = Geom::Transformation.new(vec3)
            if pts.length > 0
              vec4 = pts.last.vector_to(verts[a].position.transform(t))
              if vec4.valid?
                unless (vec2.parallel?(vec4))
                  t = Geom::Transformation.new(vec3.reverse)
                end
              end
            end
            
            pts.push(verts[a].position.transform(t))
          end
        end

        # CHECK FOR DUPLICATE POINTS IN pts ARRAY

        duplicates = []
        pts.each_index do |a|
          pts.each_index do |b|
            next if b==a
            duplicates<<b if pts[a]===pts[b]
          end
          break if a==pts.length-1
        end
        duplicates.reverse.each{|a| pts.delete(pts[a])}

        # CREATE FACE FROM POINTS IN pts ARRAY

        (pts.length > 2) ? (Sketchup.active_model.entities.add_face(pts)) : (return nil)

      #rescue
      #  puts "#{face} did not offset: #{pts}"
      #  return nil
      #end

    end

  end
end