#!/usr/bin/ruby

#############################################################
# Copyright (C) by Steve Litt, all rights reserved
# Licensed under version 1 of the 
#    Litt Scripting Language Development Tools License
# See COPYING file
# Or COPYING.LSLDTL.1.0
# Or see http://www.troubleshooters.com/licenses/LSLDTL/COPYING.LSLDTL.1.0
#
# Node.rb version 0.01, pre-alpha, written by Steve Litt 12/7/2005
# Node.rb version 0.02, pre-alpha, written by Steve Litt 12/7/2005
#   Added WalkerReverse
# Node.rb version 0.03, pre-alpha, written by Steve Litt 12/17/2005
#   Check for parent on certain calls, prevent crashes
#
#
# ABSOLUTELY NO WARRANTY, USE AT YOUR OWN RISK!
#############################################################


class Node < Object
	def initialize(nam, type, value)
		super()
		@name = nam
		@type = type
		@value = value
		@attribs = {}
		@parent = nil
		@prevsibling = nil
		@nextsibling = nil
		@firstchild = nil
		@lastchild = nil
	end

	attr_reader :name, :type, :value
	attr_writer :name, :type, :value
	attr_reader :parent, :prevsibling, :nextsibling, :firstchild, :lastchild

	def setAttributes(attribs) @attribs = attribs; end
	def getAttributes() return @attribs; end
	def hasAttributes() return @attribs != nil; end
	def setAttribute(key, value) @attribs[key] = value; end
	def getAttribute(key) return @attribs[key]; end
	def hasAttribute(key) return @attribs[key] != nil; end

	protected
	attr_writer :parent, :prevsibling, :nextsibling, :firstchild, :lastchild
	def setParent(parent)
		@parent = parent
		node = self
		while node.prevsibling
			node = node.prevsibling
		end
		@parent.firstchild = node
		node = self
		while node.nextsibling
			node = node.nextsibling
		end
		@parent.lastchild = node
		@parent
	end

	public
	def insertSiblingAfterYou(newnode)
		nextSibTemp = self.nextsibling
		self.nextsibling = newnode
		newnode.parent = self.parent
		newnode.prevsibling = self
		newnode.nextsibling = nextSibTemp
		nextSibTemp.prevsibling = newnode if nextSibTemp
		self.parent.lastchild = newnode if self.parent and not nextSibTemp
		self.parent.firstchild = newnode if self.parent and not self.parent.firstchild
		return newnode
	end

	def insertSiblingBeforeYou(newnode)
		prevSibTemp = self.prevsibling
		self.prevsibling = newnode
		newnode.parent = self.parent
		newnode.nextsibling = self
		newnode.prevsibling = prevSibTemp
		prevSibTemp.nextsibling = newnode if prevSibTemp
		self.parent.firstchild = newnode unless prevSibTemp
		self.parent.lastchild = newnode unless self.parent.lastchild
		return newnode
	end

	def insertFirstChild(newnode)
		firstChildTemp = self.firstchild
		self.firstchild = newnode
		newnode.parent = self
		newnode.prevsibling = nil
		newnode.nextsibling = firstChildTemp
		firstChildTemp.prevsibling = newnode if firstChildTemp
		self.lastchild = newnode unless self.lastchild
		return newnode
	end


	def insertLastChild(newnode)
		lastChildTemp = self.lastchild
		self.lastchild = newnode
		newnode.parent = self
		newnode.nextsibling = nil
		newnode.prevsibling = lastChildTemp
		lastChildTemp.nextsibling = newnode if lastChildTemp
		self.firstchild = newnode unless self.firstchild
		return newnode
	end

	def deleteSelf()
		prev = self.prevsibling
		nextt = self.nextsibling

		if prev
			nextt.prevsibling = prev if nextt
		else
			self.parent.firstchild = nextt if self.parent
			nextt.prevsibling = nil if nextt
		end

		if nextt
			prev.nextsibling = nextt if prev
		else
			self.parent.lastchild = prev if self.parent
			prev.nextsibling = nil if prev
		end

		if !prev and !nextt and self.parent
			self.parent.firstchild = nil
			self.parent.lastchild = nil
		end
		self
	end

end

class WalkerReverse;
	def initialize(top, entrycallback, exitcallback)
		@top = top
		@entrycallback = entrycallback
		@exitcallback = exitcallback
	end

	def walk
		node = @top
		level = 0
		ascending = false
		done = false
		while(not done)
			#### TAKE ACTION ON THE NODE
			unless ascending
				@entrycallback.call(node, level) if @entrycallback
			end

			#### WALK TO NEXT NODE
			if node.firstchild and not ascending  # Down if possible
				node = node.lastchild
				ascending = false
				level += 1
			elsif node.prevsibling  #left if possible
				@exitcallback.call(node, level) if @exitcallback
				node = node.prevsibling
				ascending = false
			elsif node.parent  #up if possible
				@exitcallback.call(node, level) if @exitcallback
				node = node.parent
				ascending = true
				level -= 1
			else  # can't go up, must be done
				@exitcallback.call(node, level) if @exitcallback
				done = true
			end
		end
	end
end

class Walker;
	def initialize(top, entrycallback, exitcallback)
		@top = top
		@entrycallback = entrycallback
		@exitcallback = exitcallback
	end

	def walk
		node = @top
		level = 0
		ascending = false
		done = false
		while(not done)
			#### TAKE ACTION ON THE NODE
			unless ascending
				@entrycallback.call(node, level) if @entrycallback
			end

			if node.firstchild and not ascending  # down if possible
				node = node.firstchild
				ascending = false
				level += 1
			elsif node.nextsibling  # else right if possible
				@exitcallback.call(node, level) if @exitcallback
				node = node.nextsibling
				ascending = false
			elsif node.parent  # else up if possible
				@exitcallback.call(node, level) if @exitcallback
				node = node.parent
				ascending = true
				level -= 1
			else  # can't go up, must be done.
				@exitcallback.call(node, level) if @exitcallback
				done = true
			end
		end
	end
end

class OutlineParser
	def initialize
		@head=Node.new("Header Node", "Head", "Inserted by OutlineParser")
		@commentchar = '#'
		@skipblanks = true
	end

	attr_reader :commentchar, :skipblanks
	attr_writer :commentchar, :skipblanks

	def skipLine?(line)
		regex_comment = Regexp.new("^\\s*" + @commentchar )
		if @skipblanks and line =~ /^\s*$/
			return true
		elsif @commentchar and regex_comment.match(line)
			return true
		else
			return false
		end
	end

	def counttabs(s)
		answer = s.index(/[^\t]/)
		answer = 0 if not answer
		return answer
	end

	def parse(filename)
		@filename = filename

		#### SLURP OUTLINE
		lines = []
		if filename
			infile = File.new(@filename, "r")
			lines = infile.readlines
			infile.close
		else
			while(line = gets)
				line.chomp!
				lines.push line
			end
		end

		#### PARSE OUTLINE
		node = @head
		prevlevel = 0
		prevlevel_nonblank = 0
		parentstack = []
		parentstack.push(@head)
		for lineno in 1..lines.length
			line = lines[lineno-1]
			unless self.skipLine?(line)
				level = 1 + self.counttabs(line)
				if line =~ /^\s*$/
					level = prevlevel_nonblank + 1
				end
				newnode = Node.new("", "Node", line.strip);
				newnode.setAttribute('_lineno', lineno);
				if level == prevlevel
					parentstack[level] =
						parentstack[level].insertSiblingAfterYou(newnode)
				elsif level == prevlevel + 1
					parentstack[level] =
						parentstack[prevlevel].insertFirstChild(newnode)
				elsif level < prevlevel
					parentstack[level] = 
						parentstack[level].insertSiblingAfterYou(newnode)
				end
				prevlevel = level
				prevlevel_nonblank = level unless line =~ /^\s*$/
			end
		end
		return @head
	end
end

