=begin
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
# Designed February 2013 by Fredo6

# Permission to use this software for any purpose and without fee is hereby granted
# Distribution of this software for commercial purpose is subject to:
#  - the expressed, written consent of the author
#  - the inclusion of the present copyright notice in all copies.

# 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.
#-----------------------------------------------------------------------------
# Name			:   TopoShaper_Cleanser.rb
# Original Date	:   18 Feb 2013
# Description	:   TopoShaper Cleansing process
#-------------------------------------------------------------------------------------------------------------------------------------------------
#*************************************************************************************************
=end

module F6_TopoShaper

T6[:TIP_JunctionAccept] = "Click to Accept Junction"
T6[:TIP_JunctionIgnore] = "Click to Ignore Junction"
T6[:TIP_HookAccept] = "Click to Accept small Hook"
T6[:TIP_HookIgnore] = "Click to Ignore small Hook"
T6[:TIP_DragExtremity] = "Click and Drag to connect to another contour extremity"
T6[:TIP_MoveExtremity] = "Move cursor to another matching extremity"
T6[:TIP_ReleaseExtremity] = "Release to register junction"
T6[:TIP_DownExtremity] = "Click to register junction"
T6[:MNU_SelectContour] = "Select Contour"
T6[:MNU_UnSelectContour] = "Unselect Contour"
T6[:MNU_IgnoreContour] = "Exclude Contour(s) from terrain"
T6[:MNU_IncludeContour] = "Include Contour(s) in terrain"
T6[:MNU_SimplifyContour] = "Simplify Contours(s)"
T6[:MNU_UnSimplifyContour] = "Restore selected contours (not simplified)"
T6[:MNU_JunctionAccept] = "Accept Junction"
T6[:MNU_JunctionReject] = "Reject Junction"

T6[:TIT_AltitudeAskCurrent] = "Setting Current Altitude"
T6[:PROMPT_AltitudeAskCurrent] = "New Current Altitude (in model unit)"
T6[:TIT_AltitudeAskSpecial] = "Assigning a Special Altitude"
T6[:PROMPT_AltitudeAskSpecial] = "Altitude (in model unit)"

T6[:TIP_PictureMatchMove] = "TRANSLATION:"
T6[:TIP_PictureMatchAdjust] = "ROTATION / SCALING:"
T6[:TIP_PictureMatchMsg1] = "Pick a point in the Image and drag it"
T6[:TIP_PictureMatchMsg2] = "to the corresponding point in the contour map"

#=============================================================================================
#=============================================================================================
# Class TopoShaperCleanser: Perfrom cleansing of original contours
#=============================================================================================
#=============================================================================================

class TopoShaperCleanser

#Include the mixin for common operations on contours
include TopoShaperContour_mixin

#Structures for Cleansing
CleansingRepair = Struct.new :inum, :type, :pts, :ptsxy, :specs, :ignored

#Structure for BackMap
BackMap = Struct.new :filename, :su_img

def initialize(tool, lst_pts, *hargs)
	@myclass = :cleanser
	@tool = tool
	@lst_pts = lst_pts
	
	@model = Sketchup.active_model
	@view = Sketchup.active_model.active_view
	@ph = @view.pick_helper
	
	@tol_z = 1.0
	
	@simplify_angle = 7.5
	
	parse_args hargs

	#Other initializations
	colors_init	
	text_init
	cursor_init

	
	#Init the VCB
	init_VCB
end

def parse_args(hargs)
	hargs.each do |hsh|
		next unless hsh.class == Hash
		hsh.each do |key, val|
			case key
			when :palette
				@palette = val
			end	
		end
	end	
end

#Reset the altitude and picture match editors
def reset_editors
	@editing_altitude = false
	@picture_matching = false
end

def cursor_init
	@id_cursor_picture_match_1 = Traductor.create_cursor "Cursor_Cible_Blue", 16, 16		
	@id_cursor_picture_match_2 = Traductor.create_cursor "Cursor_Cible_Green", 16, 16		
end

def text_init
	@tip_junction_accept = T6[:TIP_JunctionAccept]
	@tip_junction_ignore = T6[:TIP_JunctionIgnore]
	@tip_hook_accept = T6[:TIP_HookAccept]
	@tip_hook_ignore = T6[:TIP_HookIgnore]
	@tip_drag_extremity = T6[:TIP_DragExtremity]
	@tip_move_extremity = T6[:TIP_MoveExtremity]
	@tip_release_extremity = T6[:TIP_ReleaseExtremity]
	@tip_down_extremity = T6[:TIP_DownExtremity]
	
	tx = ' ' + T6[:TIP_PictureMatchMsg1] + "\n" + T6[:TIP_PictureMatchMsg2]
	@tip_picture_match_move = T6[:TIP_PictureMatchMove] + tx
	@tip_picture_match_adjust = T6[:TIP_PictureMatchAdjust] + tx	
end

def top_processing(nocompute=false)
	@no_compute = nocompute
	@editing_altitude = false
	
	#Starting the visual progress bar
	vbar_start :cleansing
	
	#Cleanup from previous session if required
	edgewire_erase
	
	#Determining the plane
	determine_plane
		
	#Analysis contours
	vbar_progression :analyze_contours
	contour_analysis(@lst_pts)

	#Analyze the junction of extremities
	@lst_repairs = []
	@hsh_match = {}
	@hsh_match_same = {}
	vbar_progression :analyze_junctions
	junction_analysis

	#Analyzing small hooks
	vbar_progression :analyze_hooks
	hooks_analysis unless nocompute
		
	#Analyzing simplification of contours
	@nb_simp = 0
	vbar_progression :simplify_contours
	simplify_analysis unless nocompute

	#Generating the working views
	camera_compute
	
	#Generating the dessin
	dessin_compute
	
	#End of processing
	vbar_stop
	compute_infobox	
	
	!@lst_repairs.empty? || @nb_simp > 0
end

#TOP: Process resolution
def top_repairing
	#Analyze the list of repairs
	@hsh_contour_future = {}
	@hsh_match_ignore = {}
	@lst_repairs.each do |repair|
		next unless repair.ignored
		case repair.type
		when :junction
			key1 = repair.specs[0][2]
			key2 = repair.specs[1][2]
			@hsh_match_ignore[key1] = @hsh_match_ignore[key2] = true
		when :junction_same	
			key = repair.specs
			@hsh_match_ignore[key] = true
		when :hook
			hooks_ignore repair	
		end
	end

	#Proceeding with repairing
	junction_resolution
end

#TOP: Return the final contour points to be used for terrain
def get_lst_pts 
	if @new_contour_pts
		lst = @new_contour_pts
	else	
		lst = @lst_contours.collect { |contour| contour.pts }
	end	
	lst.collect { |pts| pts.collect { |pt| @tr_tot_inv * pt } }
end

def determine_plane
	@plane_normal = Z_AXIS
end

#TOP: Create a reparation: Junction or Hook
def repair_create(type, specs, pts)
	repair = CleansingRepair.new
	repair.type = type
	repair.specs = specs
	repair.pts = pts
	repair.ptsxy = pts.collect { |pt| ptxy = pt.clone ; ptxy.z = 0 ; ptxy }
	repair.inum = @lst_repairs.length
	@lst_repairs.push repair
	repair
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# SAVE: Saving the cleansed contours
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#Save a copy of the cleansed contours
def save_replace_contours
	lst_pts = get_lst_pts
		
	#Handling the contours subject to repairs
	lst_map = []
	@lst_contours.each_with_index do |contour, ik|
		ij = @hsh_contour_future[ik]
		if ij 
			lst_map[ik] = lst_pts[ij]
		elsif contour.ignored || @hsh_delete_contours[ik]
			lst_map[ik] = :delete
		elsif contour.pts.length != @lst_pts[ik].length
			lst_map[ik] = contour.pts.collect { |pt| @tr_tot_inv * pt }
		else
			lst_map[ik] = nil
		end
	end
	
	#Flagging the contour whose altitude has changed (and were not affected by repairs)
	@lst_contours.each_with_index do |contour, ik|
		lst_map[ik] = contour.pts.collect { |pt| @tr_tot_inv * pt } if contour.altitude_changed && !lst_map[ik]	
		contour.altitude_changed = false
	end
	
	lst_map
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# ASSOCIATE: Calculation of association between contours and repairs
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#SAVE: compute the associated contours and repairs
def compute_associate_contours
	
	#Reinitialization
	@lst_contours.each do |contour|
		contour.associate_contours = []
		contour.associate_repairs = []
	end
	
	#Going through the repairs to build the association
	@lst_repairs.each do |repair|
		specs = repair.specs
		case repair.type
		when :junction_same
			contour = @lst_contours[specs]
			contour.associate_repairs.push repair.inum
		when :junction
			next if repair.ignored
			ic1 = specs[0][1]
			ic2 = specs[1][1]
			contour1 = @lst_contours[ic1]
			contour2 = @lst_contours[ic2]
			contour1.associate_contours.push contour2.inum
			contour2.associate_contours.push contour1.inum
			contour1.associate_repairs.push repair.inum
			contour2.associate_repairs.push repair.inum
		when :hook
			contour = @lst_contours[specs.abs-1]
			contour.associate_repairs.push repair.inum			
		end	
	end
	
	#Cleaning up the list by chaining
	@lst_contours.each do |contour|
		inum = contour.inum
		lass_contours = contour.associate_contours.clone
		lass_repairs = contour.associate_repairs.clone
		contour.associate_contours.each do |ic|
			next if ic == inum
			lass_contours |= @lst_contours[ic].associate_contours
			lass_repairs |= @lst_contours[ic].associate_repairs
		end	
		lass_contours.delete contour.inum
		contour.associate_contours = lass_contours
		contour.associate_repairs = lass_repairs
	end	
end

#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------
# INFO: Message and tip calculation in palette
#-----------------------------------------------------------------------------------
#-----------------------------------------------------------------------------------

#INFO: Calculate the text for the palette information box and notify the tool
def compute_infobox
	nbedges = nsimp = nb_simp = nbcontour = 0
	@lst_contours.each do |contour| 
		next if contour.ignored
		nb_simp += 1 if contour.simplifiable
		nbcontour += 1
		nbedges += contour.pts.length - 1
		nsimp += 1 if contour.simplified
	end	
	
	#Counting the number of active repairs
	nb_junctions = nb_hooks = 0
	njunc = nhook = njunc_edges = nhook_edges = 0
	@lst_repairs.each do |repair|
		case repair.type
		when :hook
			nb_hooks += 1
			unless repair.ignored
				nhook += 1
				nhook_edges += repair.pts.length - 1
			end	
		when :junction, :junction_same
			nb_junctions += 1
			unless repair.ignored
				njunc += 1
				njunc_edges += repair.pts.length - 1
			end	
		end	
	end
	nbedges += njunc_edges
	nbedges -= nhook_edges
	
	info_contour = "#{nbcontour} [#{@lst_contours.length}]"
	info_edges = "#{nbedges} [#{@total_nb_edges}]"
	infobox1 = T6[:INFO_ContourEdges, info_contour, info_edges]
	
	info_junction = "#{njunc} [#{nb_junctions}]"
	info_hook = "#{nhook} [#{nb_hooks}]"
	info_simp = "#{nsimp} [#{nb_simp}]"
	infobox2 = T6[:INFO_Repairs, info_junction, info_hook, info_simp]
	
	@infobox = [infobox1, infobox2]
	@tool.notify :infobox, *@infobox
end

#INFO: Compute the message for display in the palette
def palette_message
	text = ""
	text
end

#INFO: check if the floating palette can e shown
def show_floating_palette?
	@lst_contours && @lst_contours.find { |contour| contour.selected }
end

#INFO: Contextual menu
def contextual_menu_contribution(cxmenu)	
	#Menu for contour selection
	cxmenu.add_sepa
	
	#Contour and junction selection
	if @hi_contour
		text = (@hi_contour.selected) ? T6[:MNU_UnSelectContour] : T6[:MNU_SelectContour]
		cxmenu.add_item(text) { contour_toggle_select @hi_contour}
	elsif @hi_junction
		text = (@hi_junction.ignored) ? T6[:MNU_JunctionAccept] : T6[:MNU_JunctionReject]
		cxmenu.add_item(text) { repair_toggle_accept @hi_junction}
	end
	
	#Contour exclusion or acceptation
	cxmenu.add_sepa	
	if contour_some_selected?
		cxmenu.add_item(T6[:MNU_IgnoreContour]) { notify_contour_set :ignore } unless contour_all_ignored?
		cxmenu.add_item(T6[:MNU_IncludeContour]) { notify_contour_set :include } unless contour_all_included?
	elsif @hi_contour	
		cxmenu.add_item(T6[:MNU_IgnoreContour]) { notify_contour_set_current :ignore } unless @hi_contour.ignored
		cxmenu.add_item(T6[:MNU_IncludeContour]) { notify_contour_set_current :include } if @hi_contour.ignored
	end	

	#Contour simplification
	cxmenu.add_sepa	
	if contour_some_selected?
		cxmenu.add_item(T6[:MNU_SimplifyContour]) { notify_contour_set :simplify } unless contour_all_simplified?
		cxmenu.add_item(T6[:MNU_UnSimplifyContour]) { notify_contour_set :unsimplify } unless contour_all_not_simplified?
	elsif @hi_contour	
		cxmenu.add_item(T6[:MNU_SimplifyContour]) { notify_contour_set_current :simplify } unless @hi_contour.simplified
		cxmenu.add_item(T6[:MNU_UnSimplifyContour]) { notify_contour_set_current :unsimplify } if @hi_contour.simplified
	end	
	
end

#CONTOUR: Compute box information for contour
def contour_compute_boxinfo(contour)
	ik = contour.inum
	nb_edges = contour.pts.length-1
	text = "##{ik} - Edges = #{nb_edges}"
	if contour.simplified
		text += "[#{contour.pts_orig.length-1}]" 
	elsif contour.simplifiable
		text += "[#{contour.simplifiable-1}]" 
	end
	text += " - Z = #{Sketchup.format_length(contour.altitude)}"
	contour.boxinfo = text
end


#INFO: methods to check the state of selectio and ignore / include for contours
def contour_some_selected? ; @lst_contours && @lst_contours.find { |contour| contour.selected } ; end
def contour_all_ignored? ; @lst_contours && !@lst_contours.find { |contour| contour.selected && !contour.ignored } ; end
def contour_all_included? ; @lst_contours && !@lst_contours.find { |contour| contour.selected && contour.ignored } ; end
def contour_all_simplified? ; @lst_contours && !@lst_contours.find { |contour| contour.selected && contour.simplified != simplify_options} ; end
def contour_all_not_simplified? ; @lst_contours && !@lst_contours.find { |contour| contour.selected && contour.simplified == simplify_options } ; end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# JUNCTION: Small gaps and contour junctions
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#JUNCTION: Explore possible junction
def junction_analysis
	t0 = Time.now
	@ndz = 100
	@dz = (@zmax_ct - @zmin_ct) / @ndz
	if @dz <= 0
		@dz = 1.0 
		@ndz = 1
	end	
	
	#Initializing the Z levels
	@hsh_extrem_friends = {}
	@hlevel_beg = {}
	@hlevel_end = {}
	@zlevels = []
	for i in 0..@ndz
		@zlevels[i] = []
	end

	ncontours = @lst_contours.length
	nbar = 2 * ncontours + @ndz+1 
	
	#Sorting the level of altitude for the contour extremities
	@lst_contours.each_with_index do |contour, ik|
		vbar_mini_progression(nbar, ik, 50, 20)
		pts = contour.pts
		ptbeg = pts.first
		ptend = pts.last
		if ptbeg == ptend
			contour.loop = true
			next
		end
		iz_beg = contour.iz_beg = (ptbeg.z / @dz).floor
		iz_end = contour.iz_end = (ptend.z / @dz).floor
		iz_beg = @ndz if iz_beg > @ndz
		iz_end = @ndz if iz_end > @ndz
		iz_beg = 0 if iz_beg < 0
		iz_end = 0 if iz_end < 0
		contour.loop = false
		vecbeg = pts[1].vector_to ptbeg
		vecend = pts[-2].vector_to ptend
		@zlevels[iz_beg].push [:beg, ik, -(ik+1), ptbeg, vecbeg]
		@zlevels[iz_end].push [:end, ik, ik+1, ptend, vecend]
		@hlevel_beg[ik] = iz_beg
		@hlevel_end[ik] = iz_end
	end	
	
	#No computation requested
	return if @no_compute
	
	#Trying to match the extremity
	@dprox_label = 0.05 * (@xmax - @xmin + @ymax - @ymin)
	
	@zlevels.each_with_index do |level, i|
		vbar_mini_progression(nbar, ncontours+i, 25, 15)
		n = level.length-1
		next if n < 1
		for i in 0..n
			for j in i+1..n
				junction_match_extremities level[i], level[j]
			end
		end	
	end	
	@hsh_match.delete_if { |key, val| junction_invalid_extremities? val[0], val[1], val[2] }
	
	#Matching contours with themselves
	@lst_contours.each_with_index do |contour, i|
		vbar_mini_progression(nbar, ncontours+@ndz+1+i, 50, 20)
		junction_match_with_itself(contour)
	end
	
	#No Match
	return if @hsh_match.empty?
	
	#Analyzing the mean distance
	nm = @hsh_match.length
	@davg_match = 0
	@hsh_match.each { |key, a| @davg_match += a[0] }
	@davg_match /= nm
	@sig_match = 0
	@hsh_match.each { |key, a| @sig_match += (@davg_match - a[0]).abs }
	@sig_match /= nm
	
	#Filtering the list of Junction Repairs
	dlimit = @davg_match + @sig_match * 30
	
	@hsh_match.each do |key, a|
		d, a1, a2, pts = a
		next if key == a2[2]
		repair_create :junction, [a1, a2], pts
	end	

	@hsh_match_same.each do |key, pts|
		repair_create :junction_same, key, pts
	end	
end

#JUNCTION: analyze the matching of 2 extremities
def junction_match_extremities(a1, a2)
	type1, ik1, key1, pt1, vec1 = a1
	type2, ik2, key2, pt2, vec2 = a2
	return if ik1 == ik2 || (pt1.z - pt2.z).abs > @tol_z
	pts1 = @lst_contours[ik1].pts
	pts2 = @lst_contours[ik2].pts
	
	other_pt1 = (type1 == :beg) ? pts1.last : pts1.first
	other_pt2 = (type2 == :beg) ? pts2.last : pts2.first
	d = pt1.distance pt2
	
	do1o2 = other_pt1.distance other_pt2
	do12 = other_pt1.distance pt2
	d1o2 = pt1.distance other_pt2
	return if d != [d, do1o2, do12, d1o2].min || d > @dprox_label

	#Checking and building the junction information
	ls1 = @hsh_match[key1]
	ls2 = @hsh_match[key2]
	return if (ls1 && ls1[0] < d) || (ls2 && ls2[0] < d)
	return if junction_invalid_extremities?(d, a1, a2)
	@hsh_match[key1] = @hsh_match[key2] = [d, a1, a2, junction_closure(pt1, vec1, pt2, vec2)]
end

#JUNCTION: process with detection of junction on the same contour
def junction_match_with_itself(contour)
	return if contour.loop
	pts = contour.pts
	return if pts.length < 3
	ptbeg = pts.first
	ptend = pts.last
	d = ptbeg.distance ptend
	return if d > @dprox_label || d > contour.length * 0.5
	ik = contour.inum
	vecbeg = pts[1].vector_to ptbeg
	vecend = pts[-2].vector_to ptend
	return if vecbeg % vecend > 0

	return if ptbeg.distance(pts[-2]) <d || ptend.distance(pts[1]) < d
	
	vec = ptbeg.vector_to ptend
	angle1 = vec.angle_between(vecbeg)
	angle2 = vec.angle_between(vecbeg)
	[angle1, angle2].each do |angle|
		return if angle > 40.degrees && angle < 140.degrees
	end	
	
	dd = nil
	[ik+1, -ik-1].each do |i|
		dd, a1, a2 = @hsh_match[i]
		next unless dd
		return if dd < d
		@hsh_match.delete i
		@hsh_match.delete -i
		break
	end	
	
	@hsh_match_same[ik] = junction_closure(ptbeg, vecbeg, ptend, vecend)
end

#JUNCTION: Recalculate the junction points after enabling again (due to altitude)
def junction_check_enabling(junction)
	a1, a2 = junction.specs
	pt1 = a1[3]
	vec1 = a1[4]
	pt2 = a2[3]
	vec2 = a2[4]
	junction.pts = junction_closure(pt1, vec1, pt2, vec2)
end

#JUNCTION: Compute the closure between 2 contour extremities
def junction_closure(pt1, vec1, pt2, vec2)
	bezier_junction(pt1, vec1, pt2, vec2)
end

def bezier_junction(pt1, vec1, pt2, vec2)
	d = pt1.distance pt2
	d3 = d * 0.3
	pt1b = pt1.offset vec1, d3
	pt2b = pt2.offset vec2, d3
	bz = G6::BezierCurve.compute [pt1, pt1b, pt2b, pt2], 10
	bz = G6.contour_simplify bz, { :angle => 8 }
	bz
end

#JUNCTION: check if extremity is valid
def junction_invalid_extremities?(d, a1, a2)
	type1, ik1, key1, pt1, vec1 = a1
	type2, ik2, key2, pt2, vec2 = a2
	
	#Check if pointing in the same direction
	d = pt1.distance pt2
	pt3 = pt1.offset vec1, d * 0.1
	pt4 = pt2.offset vec2, d * 0.1
	return true if pt3.distance(pt4) > d
	
	return true if vec1 % vec2 > 0
	return false if d < vec1.length || d < vec2.length
	
	vec = pt1.vector_to pt2
	angle1 = vec.angle_between(vec1)
	angle2 = vec.angle_between(vec2)
	[angle1, angle2].each do |angle|
		return true if angle > 40.degrees && angle < 140.degrees
	end	
	
	
	false
end

#JUNCTION: process with the resolution of a junction
def junction_resolution
	@new_contour_pts = []
	@hsh_delete_contours = {}
	@hsh_treated = {}
	
	#Resolution for joining different contours
	for ik in 0..@lst_contours.length-1
		next if @lst_contours[ik].ignored || @hsh_delete_contours[ik] || @hsh_match_same[ik]
		new_pts = junction_pursue(ik, ik+1)
		new_pts = junction_pursue(ik, -(ik+1), new_pts) unless new_pts && new_pts.first == new_pts.last
		if new_pts
			@hsh_contour_future[ik] = @new_contour_pts.length
		else
			new_pts = @lst_contours[ik].pts
		end	
		@new_contour_pts.push new_pts
	end
	
	#Resolution for closing the same contour
	@hsh_match_same.each do |ik, pts_junction|
		next if @lst_contours[ik].ignored || @hsh_match_ignore[ik]
		contour = @lst_contours[ik]
		pts = contour.pts
		ptsj = (pts_junction.first == pts.first) ? pts_junction[0..-2].reverse : pts_junction[1..-1]
		@new_contour_pts.push(pts + ptsj)
		@hsh_contour_future[ik] = @new_contour_pts.length-1
	end
end

#JUNCTION: recursive connection of junctions
def junction_pursue(ik, key, new_pts=nil)
	return new_pts if @hsh_treated[key]
	@hsh_treated[key] = true
	return new_pts if @lst_contours[ik].ignored || @hsh_match_same[ik] || @hsh_match_ignore[key]
	match = @hsh_match[key]
	return new_pts unless match
	contour = @lst_contours[ik]
	new_pts = contour.pts unless new_pts
	d, a1, a2, pts_junction = match
	ptsj = pts_junction
	type1, ik1, key1, pt1, vec1 = a1
	type2, ik2, key2, pt2, vec2 = a2
	return new_pts if @hsh_match_same[ik2]
	
	#Determining the other contour
	if ik1 == ik
		contour2 = @lst_contours[ik2]
		next_key = -key2
	else
		contour2 = @lst_contours[ik1]
		next_key = -key1
	end

	#The other contour is ignored
	return new_pts if contour2.ignored
	
	#Marking the original contours to be deleted and replaced
	@hsh_delete_contours[ik1] = true
	@hsh_delete_contours[ik2] = true
	
	#Constructing the continuous contour
	new_pts = new_pts.reverse if ptsj.first == new_pts.first || ptsj.last == new_pts.first
	ptsj = ptsj.reverse if ptsj.first != new_pts.last
	new_pts += ptsj[1..-1]
	
	#Stop exploration if the contour has become a loop
	return new_pts if new_pts.first == new_pts.last
	
	#Otherwise continue with second contour
	pts2 = contour2.pts
	pts2 = pts2.reverse if pts2.first != ptsj.last
	new_pts += pts2[1..-1]

	#Pursuing the exploration
	junction_pursue(ik2, next_key, new_pts)
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# EXTREMITY: Manual junctions
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#EXTREMITY: Calculate the matching extremity to a given extremity
def extremity_friends(extrem)
	return [] unless extrem
	inum = extrem.abs - 1
	ipt = (extrem < 0) ? 0 : 1
	ptextrem = @lst_contours[inum].pts[ipt]
	zextrem = ptextrem.z
	
	ls_friends = @hsh_extrem_friends[extrem]
	return ls_friends if ls_friends
	
	match = @hsh_match[extrem]
	@hsh_extrem_friends[extrem] = ls_friends = []
	@lst_contours.each_with_index do |contour, ik|
		next if contour.loop
		next unless (contour.pts[0].z - zextrem).abs < @dz
		next if ik == inum && @hsh_match_same[ik]
		if (ik != inum || extrem > 0) && (!match || @hsh_match[-(ik+1)] != match)
			ls_friends.push(-(ik+1))
		end	
		if (ik != inum || extrem < 0) && (!match || @hsh_match[ik+1] != match)
			ls_friends.push(ik+1)
		end		
	end
	ls_friends
end

#EXTREMITY: Calculate the point and end vector for an extremity
def extremity_point_vector(extrem)
	inum = extrem.abs - 1
	pts = @lst_contours[inum].pts
	if extrem < 0
		pt = pts[0]
		ipt = 1
	else	
		pt = pts[-1]
		ipt = -2
	end	
	[pt, pt.vector_to(pts[ipt])]
end

#EXTREMITY: Calculate the junction between 2 extremities
def extremity_junction(extrem1, extrem2)
	pt1, vec1 = extremity_point_vector extrem1
	pt2, vec2 = extremity_point_vector extrem2
	junction_closure pt1, vec1, pt2, vec2
end

#EXTREMITY: Start dragging process to link two extremities
def extremity_drag_start
	@extrem_dragging = true
	@hi_extrem_master = @hi_extrem
	@extrem_friends_master = extremity_friends @hi_extrem_master
end

#EXTREMITY: Finish dragging mode and create junction
def extremity_drag_stop
	if @hi_extrem && @hi_extrem != @hi_extrem_master
		extremity_create_junction @hi_extrem_master, @hi_extrem
	end
	@extrem_dragging = false
	@extrem_friends = nil
	@extrem_friends_master = nil
	@hi_extrem_master = nil
end

#EXTREMITY: Create a Junction between two extremities
def extremity_create_junction(extrem1, extrem2)
	repairs_remove = []
	@lst_repairs.each do |repair|
		case repair.type
		when :junction
			key1 = repair.specs[0][2]
			key2 = repair.specs[1][2]
			if ([key1, key2] & [extrem1, extrem2]).length > 0
				repairs_remove.push repair
			end
		when :junction_same
			key = (repair.specs + 1).abs
			if key == extrem1.abs || key == extrem2.abs
				repairs_remove.push repair
			end
		end
	end	
	
	#Removing the previous repair if any
	ls_keys = [extrem1, extrem2]
	repairs_remove.each do |repair|
		case repair.type
		when :junction
			a1, a2 = repair.specs
			@hsh_match.delete a1[2]
			@hsh_match.delete a2[2]
			ls_keys.push a1[2], a2[2]
		when :junction_same
			ik = repair.specs
			@hsh_match_same.delete ik
			ls_keys.push ik+1, -(ik+1)
		end
		@lst_repairs.delete repair
	end	
	
	#Registering the new repair
	if extrem1 == -extrem2
		extremity_register_junction_same(extrem1.abs - 1)
	else
		extremity_register_junction extrem1, extrem2
	end
	
	#Recounting the repairs
	ls_keys.each { |key| @hsh_extrem_friends.delete key }
	compute_infobox
end

#EXTREMITY: process with detection of junction on the same contour
def extremity_register_junction_same(ik)
	contour = @lst_contours[ik]
	pts = contour.pts
	ptbeg = pts.first
	ptend = pts.last
	vecbeg = pts[1].vector_to pts[0]
	vecend = pts[-2].vector_to pts[-1]
	
	pts = @hsh_match_same[ik] = junction_closure(ptbeg, vecbeg, ptend, vecend)
	
	repair_create :junction_same, ik, pts
end

#EXTREMITY: process with detection of junction on the same contour
def extremity_register_junction(key1, key2)
	a1 = extremity_info key1
	a2 = extremity_info key2
	pt1 = a1[3]
	vec1 = a1[4]
	pt2 = a2[3]
	vec2 = a2[4]
	pts = junction_closure(pt1, vec1, pt2, vec2)
	d = pt1.distance pt2
	
	@hsh_match[key1] = @hsh_match[key2] = [d, a1, a2, pts]
	repair_create :junction, [a1, a2], pts
end

#EXTREMITY: compute the informative description of an extremity
def extremity_info(key)
	ik = key.abs - 1
	contour = @lst_contours[ik]
	pts = contour.pts
	if key < 0
		info = [:beg, ik, key, pts[0], pts[1].vector_to(pts[0])]
	else	
		info = [:end, ik, key, pts[-1], pts[-2].vector_to(pts[-1])]
	end	
	info
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# HOOKS: Small hooks at contour terminations
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#HOOKS: Explore possible junction
def hooks_analysis
	@hsh_hooks = {}
	@angle_hook_min = 60.degrees
	@dist_hook_min = 0.05
	
	@lst_contours.each do |contour|
		next if contour.loop || contour.pts.length < 4
		ik = contour.inum
		pts = contour.pts
		len0 = contour.length * @dist_hook_min
		hooks_detect -(ik+1), pts, len0
		hooks_detect ik+1, pts, len0
	end
	
	#Modfying the contours
	@lst_contours.each do |contour|
		ik = contour.inum+1
		ipos_end, = @hsh_hooks[ik]
		ipos_beg, = @hsh_hooks[-ik]
		next unless ipos_beg || ipos_end
		contour.pts = contour.pts[0..ipos_end] if ipos_end
		contour.pts = contour.pts[ipos_beg..-1] if ipos_beg	
		contour.ptsxy = contour.pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
		contour_compute_boundaries contour
	end	
	
	#Adding to the list of repairs
	@hsh_hooks.each { |key, val| repair_create :hook, key, val[1] }
end

#HOOKS: Detect a hook at extremities of a contour
def hooks_detect(ik, pts, len0)
	return if @hsh_match[ik]
	
	n = pts.length - 1
	len = 0
	for j in 1..n-2
		i = (ik < 0) ? j : n-j
		pt = pts[i]
		len += pts[i-1].distance pt
		return if len > len0
		angle = pt.vector_to(pts[i-1]).angle_between pt.vector_to(pts[i+1])
		next if angle > @angle_hook_min
		hook_pts = (ik < 0) ? pts[0..i] : pts[i..-1]
		@hsh_hooks[ik] = [i, hook_pts.collect { |pt| pt.clone }]
		break
	end
end

#HOOKS: Re-establish original contour extremities if the hook repair is ignored
def hooks_ignore(repair)
	pts = repair.pts
	key = repair.specs
	ik = key.abs - 1
	contour = @lst_contours[ik]
	if key < 0
		contour.pts = pts[0..-1] + contour.pts
	else
		contour.pts = contour.pts + pts[1..-1]
	end	
	contour.ptsxy = contour.pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
	contour_compute_boundaries contour
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# SIMPLIFY: Simplification of contours
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#SIMPLIFY: Analyze if contours need simplification
def simplify_analysis
	hsh_options = { :angle => @simplify_angle }
	n = @lst_contours.length
	@lst_contours.each_with_index do |contour, i|
		vbar_mini_progression(n, i, 50, 15)
		simplify_contour contour
		@nb_simp += 1 if contour.simplified
	end	
end

def simplify_options
	{ :angle => @simplify_angle }
end

#SIMPLIFY: Simplify a contour
def simplify_contour(contour)
	hsh_options = simplify_options
	pts = contour.pts
	simp_pts = G6.contour_simplify pts, hsh_options
	simp_len = simp_pts.length
	if simp_len * 1.5 < pts.length
		contour.pts = simp_pts
		contour.ptsxy = contour.pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
		contour.simplified = hsh_options
		contour.simplifiable = simp_len
		contour_compute_boundaries contour
	end
end

#SIMPLIFY: Explore possible junction
def unsimplify_contour(contour)
	pt0 = contour.pts.first
	contour.pts = contour.pts_orig.collect { |pt| @tr_tot * pt }
	new_pt0 = contour.pts.first
	
	#Adjusting altitude if required
	delta = pt0.z - new_pt0.z
	if delta != 0
		contour.pts.each { |pt| pt.z += delta }
	end
	
	contour.ptsxy = contour.pts.collect { |pt| Geom::Point3d.new pt.x, pt.y, 0 }
	contour.simplified = nil
	contour_compute_boundaries contour
end

#----------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------
# TOGGLE: Set or unset properties for contours and repairs
#----------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------

#TOGGLE: Notify a change of contour parameter for the highlighted contour
def notify_contour_set_current(code)
	refresh = false
	case code
	when :ignore
		@hi_contour.ignored = true
	when :include
		@hi_contour.ignored = false
	when :simplify
		simplify_contour @hi_contour
		refresh = true
	when :unsimplify
		unsimplify_contour @hi_contour
		refresh = true
	end

	refresh_after_change [@hi_contour], refresh
	@view.invalidate
end

#TOGGLE: Notify a change of contour parameter
def notify_contour_set(code)
	lst_contours = @lst_contours.find_all { |contour| contour.selected }

	refresh = false
	case code
	when :ignore
		lst_contours.each { |contour| contour.ignored = true }
	when :include
		lst_contours.each { |contour| contour.ignored = false }
	when :simplify
		lst_contours.each { |contour| simplify_contour(contour) }
		refresh = true
	when :unsimplify
		lst_contours.each { |contour| unsimplify_contour(contour) }
		refresh = true
	end
	contour_select_all false
	refresh_after_change lst_contours, refresh
	@view.invalidate
end

#TOGGLE: Refresh view and info box
def refresh_after_change(lst_contours, refresh_dessin)
	if refresh_dessin
		dessin_compute_contours lst_contours
		dessin_update_when_view_changed_async
		dessin_refresh_when_view_changed
	end	
	compute_infobox
end

#TOGGLE: Toggle acceptance or reject of a repair
def repair_toggle_accept(repair)
	if repair.type == :junction && repair.ignored
		junction_check_enabling repair
	end
	repair.ignored = !repair.ignored
	compute_infobox
end

#NOTIFY: Notifcation from main tool about keys
def notify_key_down(key)
	return false unless @editing_altitude
	case key
	when VK_UP
		altitude_button_sense_click +1
	when VK_DOWN
		altitude_button_sense_click -1
	when VK_LEFT
		altitude_button_move_click -1
	when VK_RIGHT
		altitude_button_move_click +1
	else
		return false
	end
	@view.invalidate
	true
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# MOUSE: Manage mouse location and click
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

def zoom_target ; @zoom_target ; end

#MOUSE: Check the potential highlight of an element of the preview based on the mouse position
def mouse_on_move(flags, x, y, view)
	@x = x
	@y = y
	ray = view.pickray x, y
	eye = ray[0]
	@hi_contour = nil
	@hi_repair = nil
	@hi_extrem = nil
	lres = []
	
	#Picture Matching
	if @picture_matching
		picture_match_mouse_move(view, x, y)
		return true
	end
	
	#Checking if the mouse is in the map
	unless camera_in_situ? 
		res = mouse_in_map(ray)
		@zoom_target = res[1]
		lres.push [eye.distance(@zoom_target), res]
		@hi_contour = mouse_contour_in_terrain(flags, x, y, view)
	else	
		@hi_contour = mouse_contour_in_situ(flags, x, y, view)
	end	
		
	#Neither in terrain nor map
	return false if lres.empty?
	
	#Taking the closest one from camera eye
	lres.sort! { |a, b| a.first <=> b.first }
	within, @zoom_target, where = lres[0][1]
	
	#Finding the selected contour if any
	if within && where == :map
		@hi_contour = mouse_contour_in_map(@zoom_target)
		if @editing_altitude
		
		else
			@hi_extrem = mouse_on_extremity(@zoom_target, @hi_contour)
			@hi_repair = mouse_contour_in_repair_xy(@zoom_target) unless @hi_extrem
			@hi_contour = nil if @hi_repair
		end	
	end
	
	#Disabling contours and repair if dragging for expert
	@hi_contour = @hi_repair = nil if @extrem_dragging
	
	(@hi_contour) ? true : false
end

#MOUSE: Check if mouse location is within the Map
def mouse_in_map(ray)
	ptinter = Geom.intersect_line_plane ray, [@tr_bello * ORIGIN, Z_AXIS]
	return [false, ptinter, :map] unless ptinter && @bb_map.contains?(ptinter)
	[true, ptinter, :map]
end

#MOUSE: Check if mouse location is on a contour extremity
def mouse_on_extremity(target, hi_contour)
	pix = @mouse_precision
	return nil unless hi_contour && !hi_contour.loop
	ptsxy = hi_contour.ptsxy
	target2d = @view.screen_coords target
	
	ptbeg = @tr_bello * ptsxy[0]
	ptbeg2d = @view.screen_coords ptbeg
	dbeg = ptbeg2d.distance target2d
	
	ptend = @tr_bello * ptsxy[-1]
	ptend2d = @view.screen_coords ptend
	dend = ptend2d.distance target2d
	
	return nil if dbeg > pix && dend > pix
	inum = hi_contour.inum
	#hi_extrem = (dbeg < dend) ? [:beg, inum, ptbeg] : [:end, inum, ptend]
	hi_extrem = (dbeg < dend) ? -(inum+1) : inum+1
	
	return hi_extrem if @hi_extrem_master && hi_extrem == @hi_extrem_master
	return nil if @extrem_friends_master && !@extrem_friends_master.include?(hi_extrem)
	
	hi_extrem 		
end

#MOUSE: Check if the mouse is on a contour in the working view 3D mode
def mouse_contour_in_terrain(flags, x, y, view)
	@ph.do_pick x, y, @mouse_precision
	elt = @ph.picked_element
	return nil unless elt
	
	ik = @hsh_edgewire_cline[elt.entityID]
	(ik) ? @lst_contours[ik] : nil
end	

#MOUSE: Check if the mouse is on a contour in the working view 3D mode
def mouse_contour_in_situ(flags, x, y, view)
	@ph.do_pick x, y, @mouse_precision
	elt = @ph.picked_element
	return nil unless elt
	
	ik = @hsh_edgewire_cline[elt.entityID]
	(ik) ? @lst_contours[ik] : nil
end	

#MOUSE: Check if the mouse is on a contour in the working view 3D mode
def mouse_contour_in_repair_xy(target)
	dmin = @view.pixels_to_model @mouse_precision, target
	
	#Check if mouse is close to an extremity
	
	#Checking if close to a Repair: junction or Hook
	@hi_junction = @hi_hook = @hi_repair = nil
	pt = @tr_bello_inv * target	
	@lst_repairs.each do |repair|
		d, iseg, pt, ptc = G6.proximity_point_curve(pt, repair.ptsxy, dmin)
		if d && d < dmin
			dmin = d
			@hi_repair = repair
		end	
	end
	
	if @hi_repair
		case @hi_repair.type
		when :junction, :junction_same
			@hi_junction = @hi_repair
		when :hook
			@hi_hook = @hi_repair
		end	
	end	
	@hi_repair
end	

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# CLICK: Click Management
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------

#CLICK: Button click DOWN
def onLButtonDown(flags, x, y, view)
	@button_down = true
	
	#Picture matching mode
	if @picture_matching
		picture_match_button_down x, y, view
		return
	end
	
	#Other modes
	if @hi_extrem
		if @extrem_dragging
			extremity_drag_stop
		else
			extremity_drag_start
		end	
	else
		@hi_contour_down = @hi_contour
	end	
end

#CLICK: Button click UP - Means that we end the selection
def onLButtonUp(flags, x, y, view)
	@button_down = false	
	
	#Picture matching mode
	if @picture_matching
		picture_match_button_up x, y, view
		return
	end

	#Edition of altitude: assign the Z value
	if @editing_altitude
		contour_select_all false
		if @hi_contour && @hi_contour == @hi_contour_down
			contour_toggle_select @hi_contour
			altitude_assign_contour @hi_contour
		end
		
	else
		#Dragging mode
		if @extrem_dragging
			extremity_drag_stop unless @hi_extrem && @hi_extrem == @hi_extrem_master
		
		#Select or unselect a junction or hook
		elsif @hi_repair
			repair_toggle_accept @hi_repair
		elsif @hi_contour && @hi_contour == @hi_contour_down
			contour_toggle_select @hi_contour
		elsif !@hi_contour
			contour_select_all false
		end
	end	
	@hi_prev_contour = @hi_contour
end

#CLICK: Double Click received
def onLButtonDoubleClick(flags, x, y, view)
	#Picture matching mode
	if @picture_matching
		picture_match_button_double_click x, y, view
		return
	elsif @editing_altitude
		contour = @hi_contour
		contour = @hi_prev_contour unless contour
		return unless contour
		altitude_special_ask contour
		if @altitude_sense != 0
			altitude_current_set @altitude_current - 2 * @altitude_sense * @altitude_increment
		end	
	else	
		contour_select_all true
	end	
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# ALTITUDE: Manage the edition of Altitude
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#ALTITUDE: Return the altitude editing mode
def editing_altitude? ; @editing_altitude ; end

#ALTITUDE: toggle the Altitude Editing mode
def altitude_edition_toggle
	picture_match_stop
	@editing_altitude = !@editing_altitude
	unless @editing_altitude && altitude_check_parameters
		@editing_altitude = false
	end
	contour_select_all false
	edgewire_change_color_plane	
	compute_associate_contours if @editing_altitude
end
	
#ALTITUDE: Initialize the parameters of the altitude edition
def altitude_check_parameters
	return true if @altitude_initialized
	hsh = @tool.registry_info_get
	
	@altitude_reference = hsh[:alt_ref]
	@altitude_reference = 0.m unless @altitude_reference
	@altitude_reference = @altitude_reference.inch
	
	@altitude_increment = hsh[:alt_incr]
	@altitude_increment = 10.m unless @altitude_increment
	@altitude_increment = @altitude_increment.inch
	
	@altitude_current = hsh[:alt_current]
	@altitude_current = 0.m unless @altitude_current
	@altitude_current = @altitude_reference unless @altitude_current
	@altitude_current = @altitude_current.inch
	
	@altitude_sense = hsh[:alt_sense]
	@altitude_sense = +1 unless @altitude_sense
	
	@altitude_initialized = true
	
	true
end
	
#ALTITUDE: Update the registry information	
def altitude_update_registry
	hsh = { :alt_ref => @altitude_reference, :alt_incr => @altitude_increment,
        	:alt_sense => @altitude_sense, :alt_current => @altitude_current }
	@tool.registry_info_contribute hsh
end
	
#ALTITUDE: set the current altitude	
def altitude_current_set(val)
	@altitude_current = val
	altitude_update_registry
	val
end
	
#ALTITUDE: Reference altitude
def altitude_reference_get
	@altitude_reference
end

def altitude_reference_set(val)
	delta = val - @altitude_reference 
	@altitude_reference = val
	@altitude_current += delta
	altitude_update_registry
	val
end
	
#ALTITUDE: Altitude increment set and get
def altitude_increment_get ; @altitude_increment ; end
def altitude_increment_set(val)
	@altitude_increment = val
	altitude_update_registry
	val
end
	
#ALTITUDE: compute the text for an altitude button
def altitude_button_text_at(ipos)
	Sketchup.format_length(@altitude_current + ipos * @altitude_increment)
end

#ALTITUDE: setting current altitude from the button clicked
def altitude_button_exec(ipos)
	if ipos == 0
		altitude_current_ask
	else	
		altitude_current_set(@altitude_current + ipos * @altitude_increment)
	end	
end

#ALTITUDE: Setting and toggling of automatic direction for incrementing / decrementing altitudes
def altitude_button_sense_get ; @altitude_sense ; end
def altitude_button_sense_click(incr)
	if @altitude_sense == incr
		@altitude_sense = 0
	else
		@altitude_sense = incr
	end
	altitude_update_registry
	incr
end

#ALTITUDE: action when there a click to move the sliding set of altitude buttons
def altitude_button_move_click(incr)
	altitude_current_set(@altitude_current + incr * @altitude_increment)
end

#ALTITUDE: Assign an altitude to the contour
def altitude_assign_contour(contour, alt=nil)
	alt = @altitude_current unless alt
	
	#Changing the altitude of the contour and its associate contours and repairs
	zavg = contour.zavg + @altitude_reference
	delta = alt - zavg
	
	altitude_update_contour(contour, delta)
	contour.associate_contours.each { |ic| altitude_update_contour(@lst_contours[ic], delta) }
	contour.associate_repairs.each { |ic| altitude_update_repair(@lst_repairs[ic], delta) }
	
	#contour_compute_boundaries contour
	#contour_global_boundaries	
	dessin_compute
	
	#Changing the current altitude if applicable
	if @altitude_sense != 0 && !@tool.ctrl_down? && !@tool.shift_down?
		altitude_current_set(@altitude_current + @altitude_sense * @altitude_increment)
	end	
end
	
#ALTITUDE: update the altitude of a given contour by a delta	
def altitude_update_contour(contour, delta)
	contour.pts.each { |pt| pt.z += delta }
	edgewire_move_contour(contour, delta)
	contour.altitude_changed = true
	contour_compute_boundaries contour
end	

#ALTITUDE: update the altitude of a given repair by a delta	
def altitude_update_repair(repair, delta)
	repair.pts.each do |pt|
		pt.z += delta
	end
end	

#ALTITUDE: Ask for a special altitude
def altitude_special_ask(contour)
	alt = contour.zavg + @altitude_reference
	title = T6[:TIT_AltitudeAskSpecial]
	label = T6[:PROMPT_AltitudeAskSpecial] + "   "
	val = Sketchup.format_length alt
	while true
		val, = UI.inputbox [label], [val], [], title
		return unless val
		new_alt = Traductor.string_to_length_formula val
		unless new_alt
			UI.beep
			next
		end
		altitude_assign_contour(contour, new_alt)
		break
	end	
end

#ALTITUDE: Ask for a special altitude
def altitude_current_ask
	alt = @altitude_current
	title = T6[:TIT_AltitudeAskCurrent]
	label = T6[:PROMPT_AltitudeAskCurrent] + "   "
	val = Sketchup.format_length alt
	while true
		val, = UI.inputbox [label], [val], [], title
		return unless val
		new_alt = Traductor.string_to_length_formula val
		unless new_alt
			UI.beep
			next
		end
		altitude_current_set new_alt
		break
	end	
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# PICTURE: Manage the picture background map
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#PICTURE: Show the picture
def picture_show
	return unless @tool.picture_file
	picture_create_su unless @picture_su_image
	@picture_su_image.visible = true
	edgewire_plane_face_show false
end

#PICTURE: Hide the picture
def picture_hide
	return unless @picture_su_image
	@picture_su_image.erase! if @picture_su_image
	@picture_su_image = nil
	edgewire_plane_face_show true
end

#PICTURE: Create the picture
def picture_create_su
	picture_file = @tool.picture_file
	return unless picture_file
	pt = @tr_bello * Geom::Point3d.new(@xmax - @xmin, 0, 0)
	width = pt.x
	@picture_su_image = @edgewire_group.entities.add_image picture_file, ORIGIN, width
	hsh = @tool.registry_info_get
	@picture_file = hsh[:picture_file]
	@picture_visible = hsh[:picture_visible]
	unless @picture_transform
		array_transform = hsh[:picture_transform] unless @picture_transform
		if array_transform
			@picture_transform = Geom::Transformation.new array_transform
		else	
			@picture_transform = Geom::Transformation.new
		end	
	end	
	@edgewire_group.entities.transform_entities @picture_transform, [@picture_su_image]
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# MATCH: Manage the picture matching GUI
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

#MATCH: Enter the match mode for picture
def picture_matching? ; @picture_matching ; end
def picture_match_start
	if @picture_matching
		picture_match_stop
	else	
		@tool.picture_show
		@picture_matching = !@picture_matching
		@match_step = :move
		@tool.refresh_viewport
	end	
end

def picture_match_where_mouse(view, x, y)
	ray = view.pickray x, y
	plane = [ORIGIN, Z_AXIS]
	@match_pt = Geom.intersect_line_plane ray, plane
end

def picture_match_mouse_move(view, x, y)
	#Start dragging mode if mouse is down and away from initial point
	if !@picture_match_dragging && @picture_match_down && ((x - @xmatch_down).abs > 5 || (y - @ymatch_down).abs > 5)
		picture_match_drag_start
	end
	
	#Checking where is the mouse in the map
	picture_match_where_mouse(view, x, y)
	
	#Move image
	if @picture_match_dragging
		picture_match_move_image
	end	
end

def picture_match_button_down(x, y, view)
	@xmatch_down = x
	@ymatch_down = y
	@match_pt_down = @match_pt
	@match_transform = Geom::Transformation.new
	@picture_match_down = true
end

def picture_match_button_up(x, y, view)
	@picture_match_down = false
	if @picture_match_dragging
		picture_match_drag_stop
	elsif @xmatch_down && ((x - @xmatch_down).abs > 5 || (y - @ymatch_down).abs > 5)
		picture_match_drag_stop
	else
		picture_match_drag_start
	end	
end

def picture_match_drag_start
	@picture_match_dragging = true
end
	
def picture_match_drag_stop
	@picture_match_dragging = false
	picture_match_next
end
	
def picture_match_next
	if @match_step == :move
		@match_step = :adjust
	else
		#picture_match_stop
	end	
end

def picture_match_stop
	@picture_matching = false
	@match_step = false
	@tool.refresh_viewport
end

def picture_match_button_double_click(view, x, y)
	#do nothing
end

def picture_match_move_image
	if @match_step == :move
		vec = @match_pt_down.vector_to @match_pt
		tt = Geom::Transformation.translation(vec)
		@match_pivot = @match_pt
	elsif @match_step == :adjust
		origin = @match_pivot
		vec1 = origin.vector_to @match_pt_down
		vec2 = origin.vector_to @match_pt
		angle = vec1.angle_between vec2
		angle = -angle if vec1 * vec2 % Z_AXIS < 0
		tr = Geom::Transformation.rotation origin, Z_AXIS, angle
		target = tr * @match_pt_down
		scale = origin.distance(@match_pt) / origin.distance(target)
		ts = Geom::Transformation.scaling origin, scale
		tt = ts * tr
	end
	tt2 = tt * @match_transform.inverse
	@picture_transform = tt2 * @picture_transform
	@edgewire_group.entities.transform_entities tt2, [@picture_su_image]
	@match_transform = tt
	@tool.registry_info_get[:picture_transform] = @picture_transform.to_a
end

#MATCH: Manage the steps of picture matching mode
def picture_match_step ; @match_step ; end
def picture_match_set_step(step)
	case step
	when :move, :adjust
		@match_step = step
	when :exit
		picture_match_stop
	end
end

def picture_match_grayed(step)
	case step
	when :move
		false
	when :adjust
		!@match_pivot
	when :exit
		false
	end
end

def picture_match_escape
	@edgewire_group.entities.transform_entities @match_transform.inverse, [@picture_su_image]
	@match_transform = Geom::Transformation.new
	@picture_match_dragging = false
	@match_step = :move if !@picture_match_dragging && @match_step != :move
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# DESSIN: Drawing routine for GUI
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

def handle_escape
	if @picture_matching
		picture_match_escape
		return true
	end	
	false
end

#DESSIN: Manage cursor
def onSetCursor
	return 0 unless @picture_matching
	case @match_step
	when :move
		@id_cursor_picture_match_1
	when :adjust
		@id_cursor_picture_match_2
	else
		0
	end	
end

#DESSIN: Top method to draw the surface and map
def dessin(view)
	return unless @dessin_info
	dessin_compute_at_view
	dessin_map view
	dessin_repair view
	dessin_hi_contour view, @hi_contour
	if @editing_altitude
		dessin_altitude_contours view
		dessin_associate_contours_highlight view
		dessin_associate_repairs_highlight view
		dessin_altitude_labels view
	else
		dessin_selected_contours view
		dessin_hi_extrem_friends view
		dessin_hi_extrem_master view
		dessin_hi_extrem view
	end	
	dessin_debug view
end

def dessin_debug(view)
	#dessin_boite(view)
	#dessin_circles(view, @hull_contour)
end

def dessin_map(view)
	return if camera_in_situ?
	dessin_contours view
	dessin_contours_xy view
end

#DESSIN: Drawing contours
def dessin_altitude_contours(view)
	view.line_stipple = ''
	view.line_width = 3	
	view.drawing_color = @color_alt_contour
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	@lst_contours.each do |contour|
		next unless contour.altitude_changed || contour.zavg != 0.0
		view.draw GL_LINE_STRIP, contour.ptsxy.collect { |pt| G6.small_offset(view, @tr_bello * pt, 2) }
		view.draw2d GL_LINE_STRIP, contour.pts.collect { |pt| view.screen_coords(t * pt) }
	end	
end

#DESSIN: Draw the labels for altitude
def dessin_altitude_labels(view)
	@lst_contours.each do |contour|
		next unless contour.altitude_changed || contour.zavg != 0.0
		text = Sketchup.format_length(contour.zavg + @altitude_reference)
		w, h = G6.simple_text_size(text)
		n = contour.ptsxy.length
		pt = @tr_bello * contour.ptsxy[n/2]
		pt2d = view.screen_coords pt
		w += 2
		h += 2
		x = pt2d.x - w / 2
		y = pt2d.y - h
		rect = G6.pts_rectangle x, y, w, h
		pt_text = Geom::Point3d.new(x + 2, y)
		view.drawing_color = 'yellow'
		view.draw2d GL_POLYGON, rect
		view.draw_text pt_text, text
	end
end

#DESSIN: Draw the associate contours when in Altitude edition mode
def dessin_associate_contours_highlight(view)
	return unless @hi_contour
	@hi_contour.associate_contours.each { |ic|  dessin_hi_contour(view, @lst_contours[ic]) }
end

#DESSIN: Draw the associate repairs when in Altitude edition mode
def dessin_associate_repairs_highlight(view)
	return unless @hi_contour && @hi_contour.associate_repairs.length > 0
	dessin_repair view, @hi_contour.associate_repairs
end

#DESSIN: Draw the contours in the terrain view
def dessin_contours(view)
	return unless @dessin_info
	view.line_width = 2
	@lst_contours.each do |contour|
		if contour.ignored
			view.drawing_color = 'gray'
			view.line_stipple = '_'
		elsif contour.simplified
			view.drawing_color = 'green'
			view.line_stipple = ''
		else
			view.drawing_color = 'black'
			view.line_stipple = ''
		end	
		view.draw GL_LINE_STRIP, contour.lines_ds
	end
end

#DESSIN: Draw the contours in the map view
def dessin_contours_xy(view)
	return unless @dessin_info
	view.line_width = 2
	@lst_contours.each do |contour|
		if contour.ignored
			view.drawing_color = 'gray'
			view.line_stipple = '_'
		elsif contour.simplified
			view.drawing_color = 'green'
			view.line_stipple = ''
		else
			view.drawing_color = 'black'
			view.line_stipple = ''
		end	
		view.draw GL_LINE_STRIP, contour.lines_xy_ds
	end
end

#DESSIN: Draw the hooks and junctions
def dessin_repair(view, lst_hi=nil)
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	
	@lst_repairs.each do |repair|
		inum = repair.inum
		type = repair.type
		case type
		when :junction, :junction_same
			view.drawing_color = @color_junction
			stipple = (repair.ignored) ? '_' : ''
		when :hook
			view.drawing_color = @color_hook			
			stipple = (repair.ignored) ? '' : '_'
		end
		
		view.line_width = (@hi_repair == repair || (lst_hi && lst_hi.include?(inum))) ? 6 : 4
		view.line_stipple = stipple
		lpt3d = repair.pts.collect { |pt| t * pt }
		view.draw GL_LINE_STRIP, lpt3d
		unless @dessin_info.in_situ
			lpt2d = repair.ptsxy.collect { |pt| @tr_bello * pt }
			view.draw GL_LINE_STRIP, lpt2d
		end	
	end	
end

def extremity_point2d(extrem)
	contour = @lst_contours[extrem.abs-1]
	ipt = (extrem < 0) ? 0 : -1 
	pt2d = @view.screen_coords(@tr_bello * contour.ptsxy[ipt])
end

#DESSIN: Draw the main Extremity
def dessin_hi_extrem(view)
	return unless @hi_extrem && @hi_extrem_master != @hi_extrem
	pt2d = extremity_point2d(@hi_extrem)
	pts = G6.pts_square pt2d.x, pt2d.y, 5
	view.drawing_color = (@hi_extrem_master) ? 'red' : 'blue'
	view.draw2d GL_POLYGON, pts
end

#DESSIN: Draw the main Extremity
def dessin_hi_extrem_master(view)
	return unless @hi_extrem_master
	target2d = @view.screen_coords @zoom_target
	pt2dmaster = extremity_point2d(@hi_extrem_master)
	view.line_stipple = '-'
	view.line_width = 2
	view.drawing_color = 'red'
	view.draw2d GL_LINE_STRIP, [target2d, pt2dmaster]
	pts = G6.pts_square pt2dmaster.x, pt2dmaster.y, 5
	view.drawing_color = 'red'
	view.draw2d GL_POLYGON, pts
end

#DESSIN: Draw the friends of main Extremity
def dessin_hi_extrem_friends(view)
	extrem_cur = @hi_extrem_master ? @hi_extrem_master : @hi_extrem
	return unless extrem_cur
	ls_friends = extremity_friends extrem_cur
	return if ls_friends.empty?
	
	view.drawing_color = 'green'
	ls_friends.each do |extrem|
		next if extrem == @hi_extrem
		pt2d = extremity_point2d(extrem)
		pts = G6.pts_circle pt2d.x, pt2d.y, 5
		view.draw2d GL_POLYGON, pts
	end	
end

#DESSIN: Draw the tooltip box
def dessin_boxinfo(view, x, y)
	if @picture_matching
		if @match_step == :move
			G6.draw_rectangle_multi_text view, x, y, @tip_picture_match_move, @hsh_boxinfo_match_move
		elsif @match_step == :adjust
			G6.draw_rectangle_multi_text view, x, y, @tip_picture_match_adjust, @hsh_boxinfo_match_adjust
		end	
	
	elsif @editing_altitude
		text = Sketchup.format_length @altitude_current
		G6.draw_rectangle_multi_text view, x, y, text, @hsh_boxinfo_altitude		
	
	#Tip for Extremity when dragging
	elsif @hi_extrem_master
		if @hi_extrem && @hi_extrem != @hi_extrem_master
			text = (@button_down) ? @tip_release_extremity : @tip_down_extremity
			G6.draw_rectangle_multi_text view, x, y, text, @hsh_boxinfo_extrem
		else
			text = @tip_move_extremity
			G6.draw_rectangle_multi_text view, x, y, text, @hsh_boxinfo_extrem		
		end

	#Mouse is just over an extremity
	elsif @hi_extrem
		G6.draw_rectangle_multi_text view, x, y, @tip_drag_extremity, @hsh_boxinfo_extrem
	
	#Tip for contour selection
	elsif @hi_contour
		G6.draw_rectangle_multi_text view, x, y, @hi_contour.boxinfo, @hsh_boxinfo_contour

	#Tip for junction and hook selection
	elsif @hi_repair
		case @hi_repair.type
		when :junction, :junction_same
			tip = (@hi_repair.ignored) ? @tip_junction_accept : @tip_junction_ignore
		when :hook
			tip = (@hi_repair.ignored) ? @tip_hook_accept : @tip_hook_ignore
		end
		hcolor = (@hi_repair.ignored) ? @hsh_boxinfo_junction_accept : @hsh_boxinfo_junction_ignore
		G6.draw_rectangle_multi_text view, x, y, tip, hcolor
		
	end	
end

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# DESSIN: Computing routines for GUI
#-----------------------------------------------------------------------
#-----------------------------------------------------------------------

def dessin_compute
	vbar_progression :prepare_dessin
	@dessin_info = Topo_Dessin.new unless @dessin_info
	@dessin_info.done = false
	
	dessin_compute_contours
	
	@dessin_info.in_situ = nil
	@dessin_info.done = true
	dessin_compute_at_view
end

def dessin_compute_at_view
	return unless @dessin_info && @dessin_info.done 
	in_situ = camera_in_situ?
	return if in_situ == @dessin_info.in_situ
	@dessin_info.in_situ = in_situ
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	
	#Updating with current view
	dessin_update_when_view_changed_async
	dessin_refresh_when_view_changed
end

#DESSIN COMPUTE: Immediate refresh when view changes
def dessin_refresh_when_view_changed

end

#DESSIN COMPUTE: Deferred refresh when view changes
def dessin_update_when_view_changed_async
	t = (@dessin_info.in_situ) ? @tr_tot_inv : @tr_dessin
	@lst_contours.each do |contour|
		contour.lines_xy_ds = contour.lines_xy.collect { |pt| G6.small_offset @view, pt }
	end	
end

#DESSIN COMPUTE: Compute the lines for the Contours
def dessin_compute_contours(contours=nil)
	lst_contours = (contours) ? contours : @lst_contours
	lst_contours.each do |contour| 
		contour.lines_ds = contour.pts.collect { |pt| @tr_dessin * pt }
		contour.lines_xy = contour.ptsxy.collect { |pt| @tr_bello * pt }
	end	
end

#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
# VCB: Handling VCB inputs
#---------------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------
	
def enableVCB? ; @editing_altitude ; end
	
#VCB: Declare vcb formats
def init_VCB	
	@vcb = Traductor::VCB.new
	@vcb.declare_input_format :cur, 'l'
end

#VCB: Handle VCB input
def handle_VCB(text, view)
	return UI.beep unless @vcb
	nb_errors = @vcb.process_parsing(text)
	
	if nb_errors > 0
		lmsg = []
		@vcb.each_error { |symb, s| lmsg.push "<#{s}>" }
		msg = "#{T6[:T_ERROR_InVCB]} \"#{text}\"  --> #{lmsg.join(' - ')}"
		@tool.palette_set_message msg, 'E'
		view.invalidate
	else
		action_from_VCB(text)
	end	
end

#VCB: Execute actions from the VCB inputs for the grid dimensions	
def action_from_VCB(text)
	@vcb.each_result do |symb, val, suffix|
		case symb
		when :cur
			altitude_current_set val
		end	
	end
end

end	#class TopoShaperCleanser

end	#End Module F6_TopoShaper
