require 'sketchup'

require File.dirname(__FILE__) + '/offset'

module JF

   module Protrude

      UI.menu.add_item("Protrude") { init() }

      def self.init
         @@instance = nil
         @tops = []
         @dlg = UI::WebDialog.new("Protrude #{VERSION}", false, "JF\\Protrude_#{VERSION}", 300, 350, 10, 100, true)
         @sm = JF::Protrude::SelectionManager.new
         #super "Protrude", true, "Protrude"
         @dlg.set_file( File.join(File.dirname(__FILE__), 'ui.html'))
         @dlg.add_action_callback("set_opts") { |d, a| 
            #puts "set_opts1:#{a.inspect}"
            cb, opts = a.split(/--/)
            set_opts(d, opts) 
            send(cb)
         }
         #@dlg.add_action_callback("ruby_callback") do |d, a|
         #    puts "ruby_callback:#{a.inspect}"
         #    m, a = a.split(';') 
         #    send(m, a)
         #end 
         @dlg.set_on_close { onClose }
         @opts = {}
         if RUBY_PLATFORM[/darwin/]
            @dlg.show_modal { }
         else
            @dlg.show { }
         end
      end # initialize

      def self.set_opts(d, a)
         #puts "set_opts2:#{a.inspect}"
         opts = {}
         return if a.nil?
         a.split(/;/).each do |opt|
            k, v = opt.split("=")
            opts[k] ||= []
            opts[k].push(v)
         end
         @opts = opts
      end

      def self.do_over
         Sketchup.send_action "editUndo:"
         @sm.undo 
         UI.start_timer(1) { protrude }
      end

      def self.divideFaces(a=nil)
         #puts "made it to divideFaces."
         begin
            Sketchup.active_model.start_operation "Divide", true
         rescue
            Sketchup.active_model.start_operation "Divide"
         end
         @sm.save
         min = @opts['dmin'][0]
         max = @opts['dmax'][0]
         random_face_divide(min, max)
         Sketchup.active_model.commit_operation
      end

      def self.onClose 
         #print "Closing."
         %w( dmin dmax omin omax hmin hmax tmin tmax ).each do |o|
            @opts[o] && Sketchup.write_default("JFProtrudeDialog", o, @opts[o][0])
         end
      end

      def self.close
         @@instance && @@instance.close
      end
      def self.toggle
         if @@instance == nil
            @@instance = self.new
            @@instance.show { }
         else
            if @@instance.visible?
               @@instance.close
            else
               @@instance.show { }
            end
         end
      end

      def self.protrude
         #puts "Made it to protrude."
         faces = @sm.active_faces
         @sm.save
         @tops = []
         @sides = []
         ofaces = []
         @bots = []
         bases = []
         begin
            Sketchup.active_model.start_operation "Divide", true
         rescue
            Sketchup.active_model.start_operation "Divide"
         end
         faces.each do |face|
            bface = doOffset(face)
            bface = face if bface.nil?
            bases << bface
            if calcHeight > 0
               top, sides = doProtrude(bface)
               @tops << top
               @sides += sides
               doScaling top if calcTaper > 0
            end
         end
         if !@opts['keep_bottoms']
            bases.map {|e| e.erase! unless calcHeight == 0}
         end
         select_tops
         Sketchup.active_model.commit_operation
      end

      def self.doScaling(face)
         tmin = @opts['tmin'][0].to_f if @opts['tmin']
         taper = tmin
         if @opts['tmax']
            tmax = @opts['tmax'][0].to_f if @opts['tmax']
            taper = ((tmax - tmin + 1) * rand) + tmin
         end
         taper = 99 if taper >= 100
         tr = Geom::Transformation.scaling(face.bounds.center, 1.0 - taper/100)
         face.parent.entities.transform_entities tr, face unless taper == 0
         face
      end	

      def self.myrand(min, max)
         (rand * (max -  min) + min)
      end

      def self.doOffset(face)
         offset = calcOffset()
         offset_face(face, -calcOffset)
      end

      def self.calcOffset
         omin = @opts['omin'][0].to_l
         offset = omin
         if @opts['omax']
            omax = @opts['omax'][0].to_l
            offset = ((omax - omin) * rand) + omin  unless omax == 0
         end
         offset
      end

      def self.calcHeight
         min = @opts['hmin'][0].to_l
         if @opts['hmax']
            max = @opts['hmax'][0].to_l 
            h = ((max - min) * rand) + min
         else
            h = min
         end
         h
      end

      def self.calcTaper
         min = @opts['tmin'] ?  @opts['tmin'][0].to_f : 0
         max = @opts['tmax'] ? @opts['tmax'][0].to_f : 0
         if min > max
            max = min
         end
         t = ((max - min) * rand) + min
      end

      def self.doProtrude(face)
         entities = face.parent.entities
         n = face.normal
         h = calcHeight
         n.length = h
         tops = []
         bots = []
         verts = face.outer_loop.vertices
         verts.each {|v|
            b_pt = v.position
            top_pt = b_pt + n
            tops << top_pt
            bots << b_pt
         }
         sides = []
         topface = entities.add_face tops
         for i in 0..verts.length-1
            sides << entities.add_face(bots[i-1], tops[i-1], tops[i], bots[i])
         end
         [topface, sides]
      end

      def self.doRayTest(face)
         fcp =Centroid.go(face)
         Sketchup.active_model.entities.add_cpoint fcp
         rt = Sketchup.active_model.raytest([fcp, face.normal])
         p rt
         if rt.nil?
            rt = Sketchup.active_model.raytest([face.bounds.center, face.normal])
         end
         if rt
            path = rt[1]
            @tops << path.first
            ctr = path.first.bounds.center
            return path.first
         else
            puts "Raytest failed."
            return nil
         end # if rt
      end # doRayTest

      def self.offsetByPercent(face)
         verts = face.vertices
         v0 = verts.shift
         dists = verts.map { |v| v0.position.distance(v.position) }
         dists.sort!
         dists.pop
         dist = (dists.min) * calcOffset() / 100
         face.offset(-1.0 * dist)
      end
      def self.setSelection(c)
         Sketchup.active_model.selection.clear
         Sketchup.active_model.selection.add c
      end
      def self.faceMinMax(face)
         verts = corner_vertices face
         v0 = verts.shift
         verts.map!{ |v| v0.position.distance( v.position ) }
         [verts.min, verts.max]
      end
      def self.offsetFromAngle(a, h)
         offset = h * Math::tan(a.degrees)
         #puts "offset from angle = #{offset}"
      end

      def self.shortestEdge
         faces = @sm.active_faces
         s = nil
         faces.each do |face|
            mm = faceMinMax(face)
            s = mm[0] if s.nil?
            if mm[0] < s
               s = mm[0]
            end
         end
         s
      end

      def self.setById(id, val=nil)
         if val.nil?
            val = ""
         else
            val = val.to_l.to_s
         end
         val.gsub!(%r/'/, %q(\\\'))
         val.gsub!(%r/~ /, '')
         cmd = %/setById('#{id}', '#{val}')/
         @dlg.execute_script(cmd)
      end
      def self.setReasonableValues(args = nil)
         setById('tmax')
         reasonableHeight
         reasonableOffset
      end
      def self.reasonableTaper
         setById('tmin', 0)
         setById('tmax')
      end
      def self.reasonableHeight
         setById('hmin', shortestEdge() *0.5 )
         setById('hmax')
      end
      def self.reasonableOffset
         setById('omin', shortestEdge() * 0.1 )
         setById('omax')
      end
      def self.loadGreebles
         names = %w( n1.skp n2.skp n3.skp n4.skp  )
         @nurnies = []
         names.each do |name|
            path = Sketchup.find_support_file name, "Plugins/protrude/nurnies"
            tdef = nil
            tdef = Sketchup.active_model.definitions[name]
            next if tdef
            cdef = Sketchup.active_model.definitions.load(path)
            @nurnies << cdef
         end
      end
      def self.addGreebles
         begin
            Sketchup.active_model.start_operation "add greebles", true
         rescue
            Sketchup.active_model.start_operation "add greebles"
         end
         @sm.active.each do |face|
            next unless face.typename == "Face"
            area = face.area
            normal = face.normal
            center = faceCenterPoint(face)
            fw = face.bounds.width
            fh = face.bounds.height
            fw, fh = faceMinMax(face)
            trans = Geom::Transformation.new(center, normal)
            ins = Sketchup.active_model.entities.add_instance(@nurnies[rand(@nurnies.length)], trans)
            rr = Geom::Transformation.rotation(ins.transformation.origin, Z_AXIS, [0, 90, 180, 270][rand(4)].degrees)
            ins.transform! rr
            xscale = fw * myrand(0.1, 0.5)
            yscale = fh * myrand(0.1, 0.5)
            zscale = (xscale + yscale / 2.0) * myrand(0.1, 0.2)
            scale = Geom::Transformation.scaling(ins.transformation.origin, xscale, yscale, zscale)
            ins.transform! scale
         end
         Sketchup.active_model.commit_operation
      end
      def self.select_tops
         Sketchup.active_model.selection.clear
         Sketchup.active_model.selection.add @tops
      end
      def self.sides
         Sketchup.active_model.selection.clear
         Sketchup.active_model.selection.add @sides
      end
      def self.guess_tops
         faces = Sketchup.active_model.active_entities.select{|e| e.typename=="Face" and e.normal == Z_AXIS}
         faces = faces.select{|f| f.vertices[0].position.z > 0}
         faces.reject!{|f| f.loops.length > 1}
         Sketchup.active_model.selection.clear
         Sketchup.active_model.selection.add faces
      end
      def self.bevel
         @sm.clear
      end

      def self.random_face_divide(min, max)
         model = Sketchup.active_model
         selection = model.selection
         face = selection[0]
         min = Integer(min)
         max = Integer(max)
         max = 50
         faces = selection.find_all{ |e| e.is_a? Sketchup::Face }
         faces.each do |face|
            r = Integer( rand(max - min) + min )
            verts = corner_vertices(face)
            next if verts.length != 4
            pts =  verts.map { |v| v.position }
            vecs = [pts[0].vector_to(pts[1]), pts[0].vector_to(pts[2]), pts[0].vector_to(pts[3]) ]
            lens = vecs.map {|v| v.length}
            imin = lens.index(lens.min)
            sp1 = pts[0]
            sp2 = pts[0] + vecs[imin]
            lens.delete_at(imin)
            vecs.delete_at(imin)
            imin = lens.index lens.min # new min is long side
            vec = vecs[imin]
            vec.length = vec.length * r / 100.0
            sp3 = sp1 + vec
            sp4 = sp2 + vec
            edge = model.active_entities.add_edges sp3, sp4
            newfaces = edge[0].faces
            selection.add(newfaces)
            selection.remove(selection.find_all{|e| e.is_a? Sketchup::Edge})
         end # faces.each

      end

      def self.corner_vertices(face)
         # Thomthom version
         corners = []
         # We only check the outer loop, ignoring interior lines.
         face.outer_loop.edgeuses.each { |eu|
            # Ignore vertices that's between co-linear edges. When using .same_direction? we must 
            # test the vector in both directions.
            unless eu.edge.line[1].samedirection?(eu.next.edge.line[1]) ||
               eu.edge.line[1].reverse.samedirection?(eu.next.edge.line[1])
               # Find which vertex is shared between the two edges.
               if eu.edge.start.used_by?(eu.next.edge)
                  corners << eu.edge.start
               else
                  corners << eu.edge.end
               end
            end
         }
         return corners
      end




      # Oooh, named selection sets!
      # Selection filters: faces only, edges only, et.
      # Model Observer onTransactionUndo goes here?

      class SelectionManager

         def initialize
            @ss = Sketchup.active_model.selection
            @stack = []
            @ptr = 0
            @namedSets = {}
         end


         def save(name = nil)
            @stack.push(@ss.collect{|e| e.entityID})
            if name
               @namedSets[name] = @stack.last
            end
            @ptr = @stack.length - 1
         end

         def undo?
            @stack.length > 0
         end

         def undo(flag = false)
            if undo?
               if flag
                  Sketchup.undo
                  UI.start_timer(1) { setSelection(@stack.pop) }
               else
                  setSelection(@stack.pop)
               end
            end
         end

         def setSelection(ids)
            things = []
            Sketchup.active_model.active_entities.each {|e|
               st =  ids.include? e.entityID
               things.push e if st
            }
            #puts "things:#{ things.inspect}"
            @ss.clear
            @ss.add things
            #puts self.inspect
         end


         def clear
            @stack.clear
         end

         def active
            @ss.to_a
         end

         def active_faces
            active.select{ |e| e.is_a? Sketchup::Face }
         end

      end

   end # class ProtrudeDialog
end
