#################################
#
# $Id$
# $Source$
#
# intspan.rb
# ported from Perl code by Ward Wouts
#
# (C) 2001, Ward Wouts
#
#################################

module Set

class IntSpan

Empty_String = '-'
Debuglevel = 0

def initialize(setspec=nil)
	@set = { "empty_string" => Empty_String }
	print "initialize: Calling copy\n" if Debuglevel > 0
	copy(setspec)
end

def IntSpan.valid(run_list)
	testset = new
	begin
		testset._copy_run_list(run_list)
	rescue SystemExit
		return false
	end
	return true
end

def copy(set_spec)
	print "Copy #{set_spec.type.to_s}\n" if Debuglevel > 0
	case set_spec.type.to_s
		when "NilClass"
			print "copy: Calling _copy_empty\n" if Debuglevel > 0
			_copy_empty
		when "String"
			print "copy: Calling _copy_run_list\n" if Debuglevel > 0
			_copy_run_list(set_spec)
		when "Array"
			print "copy: Calling _copy_array\n" if Debuglevel > 0
			_copy_array(set_spec)
		when "Set::Intspan"
			print "copy: Calling _copy_set\n"
			_copy_set(set_spec)
		when "Hash"
			print "copy: Calling _copy_set\n"
			_copy_set(set_spec)
		else
			print "eeps\n"
	end
end

def _copy_empty			# makes @set the empty set
	@set = { "negInf" => false }
	@set["posInf"] = false
	@set["edges"] = []
	@set["run"] = []
end

def _copy_array(array)		# copies an array into @set
	@set["negInf"] = false
	@set["posInf"] = false

	#print "scary thingy gets called!!!\n"
	edges = []
	for element in array.sort
		next if (edges.length > 0) and (edges[-1] == element) # skip duplicates

		if (edges.length > 0) and (edges[-1] == element-1)
			edges[-1] = element
		else
			edges += [ element-1, element ]
		end
	end

	@set["edges"] = edges
	@set["run"] = []
end

def _copy_set(src)	# copies one set to another
	@set["negInf"] = src.neg_inf
	@set["posInf"] = src.pos_inf
	@set["edges"] = src.edges
	@set["run"] = []
end

def _copy_run_list(runlist)

	_copy_empty

	runlist.gsub!(/\s|_/, '')
	return true if runlist == ""


	print "copy run list...\n" if Debuglevel > 0

	first = true
	last = false

	edges = []

	for i in runlist.split(/,/)
		print "#{i}\n" if Debuglevel > 0
		begin
			if i =~ /^(-?\d+)$/x
				edges += [ ($1.to_i-1), $1.to_i ]
				next
			end

			if i =~ /^ (-?\d+) - (-?\d+) $/x
				if $1.to_i > $2.to_i
					print "match rule 1 #{$1} > #{$2}\n"
					print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n"
					exit
				else
					edges += [ ($1.to_i-1),  $2.to_i ]
					next
				end
			end

			if i =~ /^\(-(-?\d+)$/x
				unless first
					print "match rule 2\n"
					print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n"
					exit
				end
				@set = {"negInf" => true}
				edges += [ $1.to_i ]
				next
			end

			if i =~ /^(-?\d+)-\)$/x
				print "match rule 3\n"
				edges += [ ($1.to_i-1) ]
				@set = {"posInf" => true}
				last = true
				next
			end

			if i =~ /^\(-\)$/x
				unless first
					print "match rule 4\n"
					print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n"
					exit
				end
				@set = {"negInf" => true}
				@set = {"posInf" => true}
				last = true
				next
			end

			print "no match! \"#{i}\"\n"
			print "Set::IntSpan::_copy_run_list: Bad syntax: #{runlist}\n"
		end
		first = false
	end

	@set["edges"] = edges
	@set["run"] = []

	return true
end

# check for overlapping runs
# delete duplicate edges
def _cleanup
	edges = @set["edges"]

	for i in (0..(edges.length-1))
		cmp = edges[i] <=> edges[i+1];
		begin
			case cmp
			when -1
				i = i + 1
				break
			when  0
				edges.slice!(i..(i+1))
				break
			when 1
				return 0
			end
		end
	end

	1
end

#def splice(array, offset, length=nil, list=[])
#	if offset >= 0
#		length = array.length-offset unless length
#		leftarray = array.slice(0, offset)
#		rightarray = array.slice(offset+length, (array.length - offset))
#	else
#		length = array.length+offset unless length
#		leftarray = array.slice(0, (array.length+offset))
#		rightarray = array.slice(array.length+length+offset, array.length+offset)
#	end
#
#	array = leftarray
#	array += list
#	array += rightarray if rightarray
#
#	return array
#end

def run_list
	if empty
		return @set["empty_string"]
	end

	print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0
	edges = []
	edges = @set["edges"]
	runs = []

	if edges.length > 0
		edges = ['(', edges] if @set["negInf"]
		edges += [')'] if @set["posInf"]

		print edges.join("/"),"\n" if Debuglevel > 0

		while(edges.length>0)
			print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0
			lower = edges[0]
			upper = edges[1]
			print "Lower: \"#{lower}\" Upper: \"#{upper}\"\n" if Debuglevel > 0
			edges = edges.slice(2..edges.length)

			if ((lower.to_s <=> '(')!=0 and
				(upper.to_s <=> ')')!=0 and
				((lower+1) == upper))
				print "#{upper}\n" if Debuglevel > 0
				runs += [ "#{upper}" ]
			else
				lower += 1 if (lower.to_s <=> "(")!=0
				print "#{lower}-#{upper}\n" if Debuglevel > 0
				runs += [ "#{lower}-#{upper}" ]
			end
		end
	end

	print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0

	return runs.join(',')
end

def elements
	if (@set["negInf"] == true or @set["posInf"] == true)
		print "Set::IntSpan::elements: infinite set\n"
		exit
	end

	elements = []
	edges = @set["edges"].dup
	while (edges.length>0)
		lower, upper = edges.slice!(0..1)
		elements += (lower+1 .. upper).to_a
	end

	return elements
end

def _real_set(set_spec=nil)		# converts a set specification into a set
	(set_spec != nil and set_spec.type.to_s == "Set::IntSpan") ?
		set_spec :
		IntSpan.new(set_spec)
end

def union(set_spec)
	b = _real_set(set_spec)
	s = IntSpan.new
	
	s.set_neg_inf(@set["negInf"] || b.neg_inf)

	eA = @set["edges"]
	eB = b.edges
	eS = s.edges

	inA = @set["negInf"]
	inB = b.neg_inf

	iA = 0
	iB = 0

	while (iA < eA.length and iB < eB.length)
		xA = eA[iA]
		xB = eB[iB]

		if (xA < xB)
			iA += 1
			inA = ! inA
			not inB and eS += [ xA ]
		elsif (xB < xA)
			iB += 1
			inB = ! inB
			not inA and eS += [ xB ]
		else
			iA += 1
			iB += 1
			inA = ! inA
			inB = ! inB
			inA == inB and eS += [ xA ]
		end
	end

	iA < eA.length and (! inB) and  eS += eA[iA..eA.length]
	iB < eB.length and (! inA) and  eS += eB[iB..eB.length]

	s.set_pos_inf(@set["posInf"] || b.pos_inf)
	s.set_edges(eS)

	return s
end

def intersect(set_spec)
	b = _real_set(set_spec)
	s = IntSpan.new

	s.set_neg_inf(@set["negInf"] && b.neg_inf)

	eA = @set["edges"]
	eB = b.edges
	eS = s.edges

	inA = @set["negInf"]
	inB = b.neg_inf

	iA = 0
	iB = 0

	while (iA < eA.length and iB < eB.length)
		xA = eA[iA]
		xB = eB[iB]
		
		if (xA < xB)
			iA += 1
			inA = ! inA
			inB and eS += [ xA ]
		elsif (xB < xA)
			iB += 1
			inB = ! inB
			inA and eS += [ xB ]
		else
			iA += 1
			iB += 1
			inA = ! inA
			inB = ! inB
			inA == inB and eS += [ xA ]
		end
	end

	iA < eA.length and inB and eS += eA[iA..eA.length]
	iB < eB.length and inA and eS += eB[iB..eB.length]

	s.set_neg_inf(@set["posInf"] && b.pos_inf)
	s.set_edges(eS)
	return s
end

def diff (set_spec)
	b = _real_set(set_spec)
	s = IntSpan.new

	s.set_neg_inf(@set["negInf"] && ! b.neg_inf)

	eA = @set["edges"]
	eB = b.edges
	eS = s.edges

	inA = @set["negInf"]
	inB = b.neg_inf

	iA = 0
	iB = 0

	while (iA < eA.length and iB < eB.length)
		xA = eA[iA]
		xB = eB[iB]
		
		if (xA < xB)
			iA += 1
			inA = ! inA
			not inB and eS += [ xA ]
		elsif (xB < xA)
			iB += 1
			inB = ! inB
			inA and eS += [ xB ]
		else
			iA += 1
			iB += 1
			inA = ! inA
			inB = ! inB
			inA != inB and eS += xA
		end
	end

	iA < eA.length and not inB and eS += eA[iA..eA.length]
	iB < eB.length and     inA and eS += eB[iB..eB.length]

	s.set_edges(eS)

	s.set_pos_inf(@set["posInf"] && ! b.pos_inf)
	return s
end

def xor(set_spec)
	b = _real_set(set_spec)
	s = IntSpan.new

	s.set_neg_inf(@set["negInf"] ^ b.neg_inf)

	eA = @set["edges"]
	eB = b.edges
	eS = s.edges

	iA = 0
	iB = 0

	while (iA < eA.length and iB < eB.length)
		xA = eA[iA]
		xB = eB[iB]
		
		if (xA < xB)
			iA += 1
			eS += [ xA ]
		elsif (xB < xA)
			iB += 1
			eS += [ xB ]
		else
			iA += 1
			iB += 1
		end
	end

	iA < eA.length and eS += eA[iA..eA.length]
	iB < eB.length and eS += eB[iB..eB.length]

	s.set_pos_inf(@set["posInf"] ^ b.pos_inf)
	s.set_edges(eS)
	return s
end

def complement
# complement is inverse set; dit klopt hier dus niet
	a = first
	b = last

	print "first #{a} last #{b}\n" if Debuglevel > 0
	if a!=b
		s = IntSpan.new("#{a}-#{b}")
		comp = xor(s)
	else
		comp = IntSpan.new("#{a}")
	end

	if Debuglevel > 0
		while i = comp.next
			print "#{i}\n"
		end
	end

	comp.set_neg_inf(! comp.neg_inf)
	comp.set_pos_inf(! comp.pos_inf)
	return comp
end


def superset(set_spec)
	b = _real_set(set_spec)

#	$b->diff($a)->empty
	s = b.diff(self)
	return s.empty
end


def subset(set_spec)
	b = _real_set(set_spec)

#	$a->diff($b)->empty
	s = diff(b)
	return s.empty
end


def equal(set_spec)
	b = _real_set(set_spec)

print "a\n"
	@set["negInf"] == b.neg_inf or return false
print "b\n"
	@set["posInf"] == b.pos_inf or return false

	aEdge = @set["edges"]
	bEdge = b.edges
	print "aEdge #{aEdge.length} bEdge #{bEdge.length}\n"
	aEdge.length == bEdge.length or return false
print "c\n"

	for i in (0...aEdge.length)
		aEdge[i] == bEdge[i] or return false
	end

	return true
end

def equivalent(set_spec)
	b = _real_set(set_spec)

	cardinality == b.cardinality
end


def cardinality
	(@set["negInf"] or @set["posInf"]) and return -1

	car = 0
	edges = @set["edges"]
	i=0
	while (i < edges.length)
		lower = edges[i]
		upper = edges[i+1]
		car += upper - lower
		i += 2
	end

	return car
end

def empty
	if @set["negInf"] == false and @set["edges"].length > 0 and
		@set["posInf"] == false
		return false
	end
	return true
end

def finite
	if @set["negInf"] == false and @set["posInf"] == false
		return true
	end
	return false
end

def edges
	return @set["edges"]
end

def set_edges(edges)
	@set["edges"] = edges
end

def neg_inf
	return @set["negInf"]
end

def set_neg_inf(negInf)
	@set["negInf"] = negInf
end

def pos_inf
	return @set["posInf"]
end

def set_pos_inf(posInf)
	@set["posInf"] = posInf
end

def infinite
	@set["negInf"] or @set["posInf"]
end

def universal
	@set["negInf"] and not @set["edges"].length > 0 and @set["posInf"]
end

def member(n)
	inSet = @set["negInf"]
	edge  = @set["edges"]

	for i in (0...edge.length)
		if inSet
			return true if n <= edge[i]
			inSet = false
		else
			return false if n <= edge[i]
			inSet = true
		end
	end

	inSet
end

def insert(n)
	inSet = @set["negInf"]
	edge = @set["edges"]

	if (edge.length == 0)
		@set["edges"] = [n-1, n]
		return
	end

	if n > edge[-1]+1
		@set["edges"] += [n-1, n]
		return
	elsif n > edge[-1]
		@set["edges"][-1] += 1
		return
	end

	for i in (0...edge.length)
		if (inSet)
			n <= edge[i] and return
			inSet = false
		else
			n <=edge[i] and break
			inSet = true
		end
	end

	inSet and return

	lGap = i == 0 || n-1 - edge[i-1]
	lGap = false if lGap == 0

	rGap = i == edge.length-1 ? i : edge[i] - n
	rGap = false if rGap == 0

	if ( lGap and rGap)
		lower = edge[0...i]
		upper = edge[i...edge.length]
		edge = lower
		edge += [n-1, n]
		edge += upper
	elsif (not lGap and rGap)
		edge[i-1] += 1
	elsif ( lGap and not rGap)
		edge[i] -= 1
	else
		lower = edge[0...i-1]
		upper = edge[i+1..edge.length]
		edge = lower
		edge += upper
	end

	@set["edges"] = edge
end

def remove(n)
	n or return

	inSet = @set["negInf"]
	edge  = @set["edges"]

	for i in (0...edge.length)
		if (inSet)
			break if n <= edge[i]
			inSet = false
		else
			return if n <= edge[i]
			inSet = true
		end
	end

	return unless inSet

	for i in (0...edge.length)
		if edge[i] == n-1 and edge[i+1] == n
			lower = edge[0...i]
			upper = edge[i+2..edge.length]
			edge = lower + upper
			break
		elsif edge[i] == n-1
			edge[i] += 1
			break
		elsif edge[i] == n
			edge[i] += 1
			break
		elsif edge[i+1] == n
			edge[i+1] -= 1
			break
		elsif edge[i]<n and edge[i+1]>n
			lower = edge[0..i]
			upper = edge[i+1..edge.length]
			edge = lower + [n-1, n] +upper
			break
		end
		i += 1
	end

	@set["edges"] = edge
end

def min
	empty and return nil
	neg_inf and return nil
	@set["edges"][0]+1
end


def max
	empty and return nil
	pos_inf and return nil
	@set["edges"][-1]
end

def grep_set(block)
	return nil if @set["negInf"] or @set["posInf"]

	edges     = @set["edges"]
	sub_edges = []

	while (edges.length > 0)
		lower = edges[0]
		upper = edges[1]
		edges = edges.slice(2..edges.length)

		for i in (lower+1..upper)
#			local $_ = i
#			&$block() or next		# definately wrong, must eval block

			if (sub_edges.length > 0 and sub_edges[-1] == i-1)
				sub_edges[-1] = i
			else
				sub_edges += [ i-1, i ]
			end
		end
	end

	sub_set = new
	sub_set["edges"] = sub_edges
	sub_set
end

def map_set(block)
	return nil if @set["negInf"] or @set["posInf"]

	map_set = new

	edges = @set["edges"]
	while (edges.length > 0)
		lower = edges[0]
		upper = edges[1]
		edges = edges.slice(2..edges.length)

		for domain in (lower+1..upper)
			local $_ = domain;

#			for range (&$block())		# definately wrong, must eval block
#				map_set.insert(range)
#			end
		end
	end

	map_set
end

def first
	@set["iterator"] = min
	@set["run"] = []
	@set["run"][0]   = 0
	@set["run"][1]   = @set["edges"].length > 0 ? 1 : nil

	@set["iterator"]
end


def last
	lastEdge     = @set["edges"].length - 1
	@set["iterator"] = max
	@set["run"][0]   = lastEdge > 0 ? lastEdge-1 : nil
	@set["run"][1]   = lastEdge

	@set["iterator"]
end

def start(startval)
	set["iterator"] = nil
	startval or return nil

	inSet = @set["negInf"]
	edges = @set["edges"]

	for i in (0...edges.length)
		if (inSet)
			if (startval <= edges[i])
				@set["iterator"] = startval
				@set["run"][0] = i ? i-1 : nil
				@set["run"][1] = i
				return $startval
			end
			inSet = false
		else
			if (startval <= edges[i])
				return nil
			end
			inSet = true
		end
	end

	if (inSet)
		@set["iterator"] = startval
		@set["run"][0]   = edges.length > 0 ? edges.length: nil
		@set["run"][1]   = nil
	end

	@set["iterator"]
end

def current
	@set["iterator"]
end


def next
	@set["iterator"] or return first

	run1 = @set["run"][1]
	run1 or return ++@set["iterator"]

	edges = @set["edges"]
	if (@set["iterator"] < edges[run1])
		@set["iterator"] += 1
		return @set["iterator"]
	end

	if (run1 < edges.length-2)
		run0 = run1 + 1
		@set["run"] = [run0, run0+1]
		@set["iterator"] = edges[run0]+1
	elsif (run1 < edges.length-1)
		run0 = run1 + 1
		@set["run"] = [run0, nil]
		@set["iterator"] = edges[run0]+1
	else
		@set["iterator"] = nil
	end

	@set["iterator"]
end

def prev
	@set["iterator"] or return last

	run0 = @set["run"][0]
	run0 or return --@set["iterator"]

	edges = @set["edges"]

	if (@set["iterator"] > edges[run0]+1)
		@set["iterator"] -= 1
		return @set["iterator"]
	end

	if (run0 > 1)
		run1 = run0 - 1
		@set["run"] = [run1-1, run1]
		@set["iterator"] = edges[run1]
	elsif (run0 > 0)
		run1 = run0 - 1
		@set["run"] = [nil, run1]
		@set["iterator"] = edges[run1]
	else
		@set["iterator"] = nil
	end

	@set["iterator"]
end


end	# class

end	# module


# TODO
# Do not kill an item until it's tested!

# [x] new
# [x] valid
# [ ] copy
# [ ] _copy_empty			# makes $set the empty set
# [x] _copy_array			# copies an array into a set
# [ ] _copy_set			# copies one set to another
# [ ] _copy_run_list		# parses a run list
# [ ] _cleanup
# [x] run_list
# [x] elements
# [x] _real_set		# converts a set specification into a set
# [x] union
# [x] intersect
# [x] diff
# [x] xor
# [ ] complement
# [x] superset
# [x] subset
# [x] equal
# [x] equivalent
# [x] cardinality
# [x] empty
# [x] finite
# [x] neg_inf { shift->{negInf} }
# [x] pos_inf { shift->{posInf} }
# [x] infinite
# [ ] universal
# [x] member
# [x] insert		# way to much code i think
# [x] remove
# [x] min
# [x] max
# [ ] grep_set(&$)
# [ ] map_set(&$)
# [x] first($)
# [x] last($)
# [ ] start($$)
# [x] current($) { shift->{iterator} }
# [x] next($)
# [x] prev($)

# New methods
# [x] set_neg_inf
# [x] set_pos_inf
# [x] set_edges
# [x] edges
