=begin
###
Copyright 2015, TIG (c)
All rights reserved.
This software is provided "as is" and without any express or implied 
warranties, including, without limitation, the implied warranties of 
merchantability and fitness for a particular purpose.
###
Main Code.
###
=end
###
module TIG
  module ShadowProjector
	###
	def self.options()
	  ###
	  @model=Sketchup.active_model
	  ### dialog
	  @type = @model.get_attribute("ShadowProjector", "type", nil)
	  @type = LINES unless @type ### change default initial 'type' here
	  @type = LINES unless [ LINES, FACES, FACES_EDGED, FACES_PC, FACES_PCA ].include?(@type)
	  @model.set_attribute("ShadowProjector", "type", @type)
	  ###
	  prompts=["#{TYPE}: ", "#{TXT_HEIGHT}: "]
	  values=[@type, @txth]
	  pops=["#{LINES}|#{FACES}|#{FACES_EDGED}|#{FACES_PC}|#{FACES_PCA}", ""]
	  title=OPTIONS#NAME
	  ###
	  Sketchup.status_text="#{NAME}: #{TYPE}"
	  ###
	  UI.beep
	  results = inputbox(prompts, values, pops, title)
	  return nil unless results
	  @type, @txth = results
	  @model.set_attribute("ShadowProjector", "type", @type)
	  @model.set_attribute("ShadowProjector", "txth", @txth)
	  ###
	  return true
	  ###
	end
	###
	def self.report(to_file=false)
		###
		@model=Sketchup.active_model
		###
		rep= "#{SREP}:\n"
		groups=@model.selection.grep(Sketchup::Group)
		unless groups[0]
			gs=@model.definitions.select{|d| d.group? }
			groups=[]
			gs.each{|g| groups << g.instances }
			groups.flatten!
			groups.compact!
			groups.uniq!
		end
		groups.each{|g|
			next unless attd = g.attribute_dictionary("ShadowProjector")
			next unless attd["id"] == true
			arr=[]
			attd.each_pair{|k,v|
				v="" unless v
				arr << "#{k} = #{v.to_s.split("\n")[0]}" unless k=="id"
			}
			arr.sort!
			rep<< "\n"
			rep<< "#{GROUP} = #{g.name}\n"
			rep<< arr.join("\n")
			rep<< "\n"
		}
		###
		if to_file
			unless @model.path.empty?
				dir = File.dirname(@model.path)
				fin = File.basename(@model.path, ".*")+"_#{SREP}.txt"
			else
				dir = File.join(ENV["HOME"], "Desktop")
				fin = "#{UNTITLED}_#{SREP}.txt"
			end
			if file=UI.savepanel("#{SREP}:", dir, fin)
				fi=File.open(file, "w")
				fi.puts(rep)
				fi.close
				UI.openURL("file:///#{file}")
			end
			return
		else
			puts rep
			return rep
		end
	end
	###
	def self.help()
		file=File.join(HELPS, "#{FNAME}-#{Sketchup.get_locale.upcase}.doc")
		if File.exist?(file)
			help=file
		else #TIG-shadowProjector-EN-US.doc
			file=File.join(HELPS, "#{FNAME}-EN-US.doc")
			if File.exist?(file)
				help=file
			else
				UI.messagebox("#{FNAME}:\n\n#{HELP_MISSING}\n#{file}")
				return nil
			end
		end
		UI.openURL("file:///#{help}")
	end
	###
	def self.new()
	  ###
	  @model=Sketchup.active_model
	  ###
	  ss=@model.selection
	  sfaces = ss.grep(Sketchup::Face)
	  unless sfaces[0]
		Sketchup.status_text="#{NAME}: #{SEL_FACE}"
		UI.messagebox("#{NAME}:\n\n#{SEL_FACE}")
		return nil
	  end
	  ###
	  sfaces = sfaces.select{|e| e.receives_shadows? == true }
	  unless sfaces[0]
		Sketchup.status_text="#{NAME}: #{SEL_FACE2}"
		UI.messagebox("#{NAME}:\n\n#{SEL_FACE2}")
		return nil
	  end
	  receives=true
	  @model.active_path.each{|e|
		unless e.receives_shadows?
			receives=false
			break
		end
	  } if @model.active_path
	  unless receives
		Sketchup.status_text="#{NAME}: #{SEL_FACE3}"
		UI.messagebox("#{NAME}:\n\n#{SEL_FACE3}")
		return nil
	  end
	  ###
	  units=@model.options["UnitsOptions"]["LengthUnit"]
	  @txth=@model.get_attribute("ShadowProjector", "txth", nil)
	  if units < 2
	   @txth=2.0.inch unless @txth #inches
	   afact=0.006944444444444444 # sq" >> sq'
	  else
	   @txth=50.mm unless @txth #metric
	   afact=0.000645160000000000 # sq" >> sqm
	  end
	  @model.set_attribute("ShadowProjector", "txth", @txth)
	  ###
	  return nil unless self.options() ### dialog
	  ###
	  ss.clear
	  ###
	  ### get sun vector
	  si=@model.shadow_info
	  time=si["ShadowTime"].to_s.split(' ')[1..3].join(' ').split(':')[0..1].join(':').tr(" ","_")
	  txt="#{SHADOWS}@"+time+"#"
	  @vect=si["SunDirection"].reverse
	  ###
	  @ents=@model.entities
	  ### get max model bounds
	  bb=@model.bounds
	  plmin=[bb.min, Z_AXIS]
	  plmax=[bb.max, Z_AXIS]
	  @line=[bb.min, @vect]
	  pmin=Geom::intersect_line_plane(@line, plmin)
	  pmax=Geom::intersect_line_plane(@line, plmax)
	  dmax=pmin.distance(pmax)*1.1 ### make sure shadows span whole model !
	  @vect.length=dmax
	  ###
	  @view=@model.active_view
	  ###
	  begin
		@model.start_operation(NAME, true)
	  rescue
		@model.start_operation(NAME)
	  end
	  ###
	  #GC.start()
	  ### material
	    mats=@model.materials
		unless @mat=mats[SHADOWS]
		  @mat=mats.add(SHADOWS) 
		  @mat.color=[100, 100, 100] ### gray
		  @mat.alpha=0.50
		end
	  ###
	  @face=nil
	  @bb=nil
	  @group=nil
	  @gents=nil
	  @tgroup=nil
	  @tents=nil
	  ###
	  num="0"
	  tot=sfaces.length
	  xpts=[]
	  ###
	sfaces.each{|face|
	  ###
	  @face=nil
	  next unless face && face.valid?
	  @face=face
	  @plane=@face.plane
	  @trr=@model.edit_transform
	  #@edges=@face.edges
	  ###
	  num.next!
	  @msg="#{NAME}: #{PROCESSING} #{num}/#{tot}"
	  Sketchup.status_text=@msg
	  ###
	  ### make group for shadows edges etc
	  facen = @face.to_s.split("::")[1].split(':')[1].split(">")[0]
	  @group=@ents.add_group() ### make group in model ents !
	  @group.name=txt+facen
	  @gents=@group.entities
	  @gents.add_cpoint(@face.bounds.center)
	  @group.set_attribute("ShadowProjector", "id", true)
	  tt=@group.transformation
	  ###
	  ### check for self-shaded face
	  if @vect.reverse.angle_between(@face.normal) < 90.degrees
		  ###
		  ### make container for shadow extrusion
		  @tgroup=@gents.add_group() ### add into @group!
		  @tents=@tgroup.entities
		  cpoint=@tents.add_cpoint(ORIGIN)
		  ttt=@group.transformation
		  ###
		  @bb=@face.bounds
		  max=@bb.max.offset(@vect.reverse)
		  @bb.add(max)
		  min=@bb.min.offset(@vect.reverse)
		  @bb.add(min, max)
		  ###
		  @ctr=0
		  ###
		  self.process_entities(@ents)
		  ###
		  #return
		  @tents.grep(Sketchup::Edge).each{|e| e.find_faces unless e.faces[1] }
		  #return
		  @tents.intersect_with( true, tt, @tents, tt, false, @ents.to_a )
		  #return
		  ### add in face's edges
		  oloop=@face.outer_loop
		  loops=@face.loops-[oloop]
		  perims=[]
		  loops.each{|loop|perims << @tents.add_edges(loop.vertices+[loop.vertices[0]])}
		  perims << @tents.add_edges(oloop.vertices+[oloop.vertices[0]])
		  perims.flatten!
		  #return
		  @tents.intersect_with( true, tt, @tents, tt, false, @tents.to_a )
		  #return
		  @tgroup.explode
		  ###
		  #return
		  @msg=@msg+" ."
		  ### test for shadedness
		  faces=@gents.grep(Sketchup::Face)
		  togos=[]
		  ctr=0
		  vrr=@vect.reverse
		  di=0.0025
		  ###
		  faces.each{|f|
			### show progress every 10
			ctr+=1
			if ctr%10 == 0
				@msg=@msg+"."
				Sketchup.status_text=@msg
			end
		    ### 1st check if f within bounds of @face, if not we'll delete it !
			outside=false
			verts=f.outer_loop.vertices
			verts.each_with_index{|v, i|
			  pt=v.position
			  i= -1 if i==verts.length-1
			  vt=pt.vector_to(verts[i+1].position)
			  vb=pt.vector_to(verts[i-1].position)
			  an=vt.angle_between(vb)/2.0
			  pr=pt.offset(vt, di)
			  tr=Geom::Transformation.rotation(pt, f.normal, an)
			  pr.transform!(tr)
			  if f.classify_point(pr) == Sketchup::Face::PointOutside
				#@ents.add_cpoint(pr)
				pr.offset!(pr.vector_to(pt), di*2.0)
				if f.classify_point(pr) == Sketchup::Face::PointOutside
					next
				end
			  end
			  if @face.classify_point(pr) == Sketchup::Face::PointOutside
				togos << f
				#f.material='Red'
				outside=true
				break
			  end
			}
			next if outside
			### now check that in shade etc
			f.reverse! if f.normal != @face.normal
			rays=[]
			verts=f.outer_loop.vertices
			verts.each_with_index{|v, i|
			 begin
			  pt=v.position
			  #@gents.add_cpoint(pt)
			  i= -1 if i==verts.length-1
			  vt=pt.vector_to(verts[i+1].position)
			  vb=pt.vector_to(verts[i-1].position)
			  an=vt.angle_between(vb)/2.0
			  pr=pt.offset(vt, di)
			  tr=Geom::Transformation.rotation(pt, f.normal, an)
			  pr.transform!(tr)
			  #@ents.add_cpoint(pr)
			  #puts
			  #p f.classify_point(pr)
			  if f.classify_point(pr) == Sketchup::Face::PointOutside
				#@ents.add_cpoint(pr)
				pr.offset!(pr.vector_to(pt), di*2.0)
				if f.classify_point(pr) == Sketchup::Face::PointOutside
					#@ents.add_cpoint(pr)
					next
				end
			  end
			  ###
			  #pr.transform!(@trr)
			  ray = @model.raytest([pr, vrr])
			  #@ents.add_cline(pr, pr.offset(vrr))
			  ### check for transparency
			  if ray
				#ray[1][-1].material.alpha if ray[1][-1].material
				#f.material='Pink'
				ray = self.casts?(ray, vrr)
			  end
			  ###
			  unless ray
				#f.material='Yellow'
				togos << f
				break
			  end
			 rescue Exception => ex
			  puts ex
			 end
			}
		  }
		  #togos.each{|e| e.material='Red' }
		  #return
		  @gents.erase_entities(togos) if togos[0]
		  #return
		  ### remove unfaced edges
		  togos=[]
		  @gents.grep(Sketchup::Edge).each{|e|
			togos << e unless e.faces[0]
		  }
		  ###
		  #return
		  @gents.erase_entities(togos) if togos[0]
		  #return
		  ### remove coplanar edges
		  4.times{
			  togos=[]
			  @gents.grep(Sketchup::Edge).each{|e|
				togos << e if e.faces[1]
			  }
			  @gents.erase_entities(togos) if togos[0]
			  @gents.grep(Sketchup::Edge).each{|e|
				e.find_faces unless e.faces[0]
			  }
			  @gents.intersect_with( true, tt, @gents, tt, false, @gents.to_a )
		  }
		  ###
		  #return
		  ### remove unfaced edges
		  togos=[]
		  @gents.grep(Sketchup::Edge).each{|e|
			togos << e unless e.faces[0]
		  }
		  #return
		  #p togos
		  @gents.erase_entities(togos) if togos[0]
		  #return
		  ### tiny holes
		  togos=[]
		  @gents.grep(Sketchup::Face).select{|e| e.loops[1] }.each{|f|
			loops=f.loops-[f.outer_loop]
			loops.each{|loop|
			  len=0
			  loop.edges.each{|e|len+=e.length}
			  togos << loop.edges if len <= 3.mm
			}
		  }
		  togos.flatten!
		  #p togos
		  @gents.erase_entities(togos) if togos[0]
		  ###
		  #return
	  else # self-shading
		  ### clone whole face
		  Sketchup.status_text=@msg+" ."
		  oloop=@face.outer_loop
		  loops=@face.loops-[oloop]
		  ifaces=[]
		  loops.each{|loop|ifaces << @gents.add_face(loop.vertices)}
		  @gents.add_face(oloop.vertices)
		  @gents.erase_entities(ifaces) if ifaces[0]
		  ###
	  end#if
	  ###
	  ### hidden edges ?
	  if @type==FACES ### NO edges
		@gents.grep(Sketchup::Edge).each{|e| e.hidden=true }
	  else ### show edges
	    @gents.grep(Sketchup::Edge).each{|e| e.hidden=false }
	  end
	  ###
	  @msg=@msg+" ."
	  Sketchup.status_text=@msg
	  ###
		### remove temp cpoints
		togos=@gents.grep(Sketchup::ConstructionPoint)
		@gents.erase_entities(togos) if togos[0]
		###
	  ### add text if % etc
	  #if @type==FACES_PC || @type==FACES_PCA
		if units < 2
			un="ft²"
		else
			un="m²"
		end
		pt=@group.bounds.center
		farea=face.area
		sarea=0.0
		@gents.grep(Sketchup::Face).each{|e| sarea+=e.area }
		sa=sarea*afact
		fa=farea*afact		
		per=100.0*(sarea/farea)
		if per==0.0
		  per="0%"
		elsif per==100.0
		  per="100%"
		elsif per > 1
		  per=sprintf("%.1f%", per)
		else #<1
		  per=sprintf("%.3f%", per)
		end
		###
		if @type==FACES_PCA
		  if units < 2 #sq'
			if sa < 1
			  sa=sprintf("%.2f", sa)
			else
			  sa=sprintf("%.1f", sa)
			end
			if fa < 1
			  fa=sprintf("%.2f", fa)
			else
			  fa=sprintf("%.1f", fa)
			end
		  else #sqm
			if sa < 1
			  sa=sprintf("%.2f", sa)
			else
			  sa=sprintf("%.1f", sa)
			end
			if fa < 1
			  fa=sprintf("%.2f", fa)
			else
			  fa=sprintf("%.1f", fa)
			end
		  end
		  sa="0" if per=="0%"
		  pert=per+"\n"+sa+":"+fa+un
		else
		  pert=per
		end
		###
	  if @type==FACES_PC || @type==FACES_PCA
	    ###
		@msg=@msg+"."
		Sketchup.status_text=@msg
		### Add text
		tgroup=@gents.add_group()
		tgroup.material="Black"
		tgroup.casts_shadows=false
		tgroup.receives_shadows=false
		tgroup.name=@group.name.split("#")[0]+"="+(pert.tr("\n",""))
		tgroup.layer=@model.layers.add(TEXT_LAYER)
		tents=tgroup.entities
		begin
			tents.add_3d_text(pert, TextAlignCenter, 'Arial', false, false, @txth, 0, 0, true, 0)
			tents.to_a.each{|e|
			  e.reverse! if e.is_a?(Sketchup::Face) && e.normal != @face.normal
			  e.hidden=true if e.is_a?(Sketchup::Edge)
			}###up
			### put on face
			if sarea==0
				#@ents.add_cline(tgroup.bounds.center, @face.bounds.center)
				#@ents.add_cline(tgroup.bounds.center, tgroup.bounds.center.offset(Z_AXIS, 22))
				vr=tgroup.bounds.center.vector_to(@face.bounds.center)
				trn=Geom::Transformation.translation(vr)
				tgroup.transform!(trn)
			else
				vr=tgroup.bounds.center.vector_to(pt)
				trn=Geom::Transformation.translation(vr)
				tgroup.transform!(trn)
			end
			###
		rescue
			###
		end
		### orient onto face etc
		ve=@face.normal
		ve.length=0.1.mm
		tr=Geom::Transformation.translation(ve)
		###
		if ve.normalize.z != 1
		  trn=Geom::Transformation.rotation(pt, X_AXIS, ve.angle_between(Z_AXIS))
		  tgroup.transform!(trn)
		  trn=Geom::Transformation.rotation(pt, tgroup.transformation.yaxis, 90.degrees)
		  vv=ve.transform!(trn)
		  trn=Geom::Transformation.rotation(pt, tgroup.transformation.yaxis, -vv.angle_between(X_AXIS))
		  tgroup.transform!(trn)
		  ### ensure right-way round/up
		  if tgroup.transformation.zaxis != @face.normal
			trn=Geom::Transformation.rotation(pt, tgroup.transformation.xaxis, 180.degrees)
			tgroup.transform!(trn)
		  end
		  if tgroup.transformation.yaxis == Z_AXIS.reverse
			trn=Geom::Transformation.rotation(pt, tgroup.transformation.zaxis, 180.degrees)
			tgroup.transform!(trn)
		  end
		  ###
		end
		if sarea==0
			vr=tgroup.bounds.center.vector_to(@face.bounds.center)
			trn=Geom::Transformation.translation(vr)
			tgroup.transform!(trn)
		end
		@gents.transform_entities(tr, [tgroup]) ### 
		@gents.transform_entities(tr, [tgroup]) ### move off twice
	  end
	  ###
	  ### resolve display etc
	  if @type==LINES ### i.e. NO faces
		togos=@gents.grep(Sketchup::Face)
		@gents.erase_entities(togos) if togos[0]
	  else # it is faced
	    @gents.grep(Sketchup::Face).each{|e|
			e.reverse! unless e.normal == @face.normal
			e.material = @mat
		}
		### relocate slightly up
		@gents.transform_entities(tr, @gents.to_a)
	  end
	  ###
		### Move group into same context as face, unless already in model.entities
		pents = @face.parent.entities
		unless pents == @ents
			trg = @group.transformation
			gp = pents.add_instance(@group.entities.parent, trg)
			gp.name=@group.name
			@group.erase!
			### replace
			@group=gp
			@gents=@group.entities
			@group.set_attribute("ShadowProjector", "id", true)
		end
		###
		@msg=@msg+"."
		Sketchup.status_text=@msg
		### layer, attribs etc
		### ensure some geometry, if there is no text group
		unless @gents[0]
			e0=@face.edges[0]
			li=e0.line
			p0=li[0]
			vc=li[1]
			vc.length=1.mm
			p1=p0.offset(vc)
			ed=@gents.add_line(p0, p1)
			ed.hidden=true
		end
		### layer
		@group.layer=@model.layers.add(LAYER)
		@group.casts_shadows=false
		#@group.receives_shadows=false
		### attributes
		@group.set_attribute("ShadowProjector", "face_area", fa)
		@group.set_attribute("ShadowProjector", "shadow_area", sa)
		@group.set_attribute("ShadowProjector", "percentage", per)
		@group.set_attribute("ShadowProjector", "units", un)
		###
	  ###
	  begin
		@view.refresh
	  end
	  ###
	}#sfaces
	  ###
	  UI.beep
	  ###
	  @model.commit_operation
	  ###
	  Sketchup.status_text=""
	  Sketchup.send_action("selectSelectionTool:")
	  ###
	end#def new
	###
	def self.casts?(ray, vrr)
		###
		return nil unless ray
		###
		prr=ray[0]
		rray=ray[1].reverse
		fac=rray[0]
		cos=rray[1..-1]
		return true unless fac.is_a?(Sketchup::Face) ### hits edge ?
		fmat=fac.material
		bmat=fac.back_material
		norm=fac.normal
		###
		#@ents.add_cline(prr, prr.offset(@vect, 111))
		#### check if facing sun, if so consider fmat
		if vrr.angle_between(norm) < 90.degrees
			if fmat && fmat.alpha >= 0.7 ### opaque
				return true
			elsif fmat && fmat.alpha < 0.7 ### transparent
				### check for solids behind it
				ray2 = @model.raytest([prr, vrr])
				return self.casts?(ray2, vrr)
			elsif fmat==nil ### might inherit its containers' transparent material
				### check all containers for some transparency
				transp=false
				cos.each{|e|
					if e.material && e.material.alpha < 0.7 ### transparent
						transp=true
						break
					end
				}
				return true unless transp ### might be some transparency inherited
				ray2 = @model.raytest([prr, vrr]) ### check for solids behind face
				return self.casts?(ray2, vrr)
			end
		else ### it faces away so consider bmat
			if bmat && bmat.alpha >= 0.7 ### opaque
				return true
			elsif bmat && bmat.alpha < 0.7 ### transparent
				### check for solids behind it
				ray2 = @model.raytest([prr, vrr])
				return self.casts?(ray2, vrr)
			elsif bmat==nil ### might inherit its containers' transparent material
				### check all containers for some transparency
				transp=false
				cos.each{|e|
					if e.material && e.material.alpha < 0.7 ### transparent
						transp=true
						break
					end
				}
				return true unless transp ### might be some transparency inherited
				ray2 = @model.raytest([prr, vrr]) ### check for solids behind face
				return self.casts?(ray2, vrr)
			end
		end
		###
	end
	###
	def self.process_entities(ents=[], tr=Geom::Transformation.new() )
		ents.grep(Sketchup::Face).each{|f|
			self.process_face(f, tr)
		}
		ents.grep(Sketchup::Group).each{|g|
			next unless g.layer.visible?
			next if g.hidden?
			next unless g.casts_shadows?
			next if g == @group
			next if g == @tgroup
			self.process_entities(g.entities, tr*g.transformation)
		}
		ents.grep(Sketchup::ComponentInstance).each{|i|
			next unless i.layer.visible?
			next if i.hidden?
			next unless i.casts_shadows?
			self.process_entities(i.definition.entities, tr*i.transformation)
		}
	end
	###
	def self.process_face(fac, tr)
		###
		return nil unless fac.layer.visible?
		return nil if fac.hidden?
		return nil unless fac.casts_shadows?
		return if fac == @face
		#if @vect.transform(tr).angle_between(fac.normal.transform(tr)) == 90.degrees
			#p fac
			#fac.material='Red'
			#return nil
		#end ### it's at 90° the sun !!!
		  @ctr+=1
		  if @ctr%10 == 0
			@msg=@msg+"."
			Sketchup.status_text=@msg
		  end
		  ### make edge clone extrusions...
		  ###
		  ### check that fac is within 'bounds' of @face
		  bb=Geom::BoundingBox.new
		  bb.add(fac.bounds.min.transform(tr))
		  bb.add(fac.bounds.max.transform(tr))
		  bb.add(fac.bounds.center.transform(tr))
		  within=true
		  within=false if @bb.intersect(bb).empty?
		  #p within
		  #fac.material='Red' if within
		  #fac.back_material='Red' if within
		  return unless within
		  #@gents.add_text('!', fac.bounds.center.transform(tr), [1,1,1])
		  ### clone fac's edges
		  tgp=@tents.add_group()
		  tents=tgp.entities
		  fac.loops.each{|loop|
			loop.edges.each{|e|
				#next if @edges.include?(e)
				#@edges << e if e.parent==@model
				p0=e.start.position.transform(tr)
				p1=e.end.position.transform(tr)
				tents.add_line(p0, p1)
			}
		  }
		  ### transform vertices onto @plane
		  verts=tents.grep(Sketchup::Edge).collect{|e| e.vertices }.flatten.uniq
		  vects=[]
		  verts.each{|v|
			pt=v.position
		    line=[pt, @vect]
			ptt=Geom::intersect_line_plane(line, @plane)
			if ptt
				vect=pt.vector_to(ptt)#.transform(tr) 
			else
				vect=[0,0,0]
			end
			vects << vect
		  }
		  ###
		  #tgp.copy
		  ###
		  tents.transform_by_vectors(verts, vects)
		  ###
		  #return
		  tents.intersect_with( true, tr, tents, tr, false, tents.to_a )
		  @tents.intersect_with( true, tr, @tents, tr, false, tents.to_a )
		  #return
		  tgp.explode
		  ###
		end
		###
  end#module
end#module
