#!/usr/bin/env ruby

# processes a .cfg file

$node_hash = Hash.new
$path_array = []
$output_file = ".config.cfg"
$input_files = []
$debugging = false
$default_config=false
class Node
	attr_accessor :name
	attr_accessor :desc
	attr_accessor :ans
	attr_accessor :default
	attr_accessor :path
	attr_accessor :key
	attr_accessor :result
	attr_accessor :dnwv
	attr_accessor :depends
	attr_accessor :res_include
	def initialize(k)
		if $debugging then puts "new node: #{k}" end
		@key = k
		@path = $path_array.dup
		@default = nil
		@ans = nil
		@name = nil
		@desc = nil
		@result = nil
		@dnwv = nil
		@depends = nil
		@res_include = Hash.new
	end

	def parse_line(line)
		if $debugging then puts "parsing: #{line}" end
		if @desc != nil
			@desc << "\n" + line
		else
			kv = line.split("=")
			if kv[0].nil? || kv[1].nil?
				return
			end
			
			case kv[0]
			when "name"
				@name=kv[1].dup.strip
			when "ans"
				@ans=kv[1].dup.strip
			when "default"
				@default=kv[1].dup.strip
			when "dnwv"
				@dnwv=kv[1].dup.strip
			when "depends"
				@depends=kv[1].dup.strip
			when "desc"
				@desc=kv[1].dup.strip
			when "condinclude"
				@res_include[kv[1].strip.to_sym] = kv[2].dup.strip
			end
		end
	end
	
	def check_deps
		if @depends.nil?
			return true
		end
		ar = @depends.split(",")
		ar.each do |dep|
			if $debugging then puts "checking dep for #{@key}: #{dep}" end
			if $node_hash[dep.to_sym].result == "n"
				return false
			end
		end
		return true
	end
end

config_header = <<DOC
# This file was generated by conf.rb
# It is not recommended to edit this file directly.

DOC

def get_input(question, array, default)
	while true
		printf("%s%s%s? ", question, array.nil? ? "" : " (" + array.join("/") + ")", (default != nil) ? " [#{default}]" : "")
		input = $stdin.gets.downcase.chomp
		if input == "" && default != nil
			puts "--> #{default}"
			return default
		elsif input != "" && (array.nil? || (array.index(input) || array.index(input.capitalize)))
			puts "--> #{input}"
			return input
		end
		$stderr.puts "invalid input"
	end
end

def process_file(path)
	temp = ""
	if $debugging then puts "reading file: #{path}" end
	file = File.open(path, "r")
	lines = Array.new
	file.each do |line|
		lines.push(line.dup)
	end
	file.close
	nindex=0
	node = nil
	lines.each do |line|
		nindex+=1
		if line.nil?
			next
		end
		# get rid of comments and empty lines
		hash = line.index("#")
		if hash != nil
			line.slice!(hash..-1)
		end
		line = line.chomp.strip

		if line == "" || line.nil?
			next
		end
		if line.index("}") && line.length != 1
			b = line.split("}")
			lines.insert(nindex, b[0].strip)
			lines.insert(nindex + 1, "}")
			lines.insert(nindex + 2, b[1].strip)
		elsif line.index("}")
			popped = $path_array.pop
			if $debugging then puts "popping: #{popped}" end
			temp.clear
			if popped.index("=")
				# popped a node! Add it to the chain
				if $debugging then puts "adding node #{node.key} to chain" end
				$node_hash[node.key.to_sym] = node.dup
				node = nil
			end
		elsif line.index("{")
			a = line.split("{")
			if a[0].nil? || a[0] == ""
				$path_array.push(temp.dup.strip)
			else
				$path_array.push(a[0].dup.strip)
			end
			temp.clear
			if $debugging then puts "pushed: #{$path_array[-1]}" end
			lines.insert(nindex, a[1])
			if $path_array.last.index("=")
				key = $path_array.last.split("=")
				if key[1].nil?
					$stderr.puts "error in configuration file: key could not be split"
					exit 1
				end
				node = Node.new(key[1].strip)
			end
		elsif node != nil
			# we have a line to process, and the line is node data
			node.parse_line(line.strip)
		elsif line.index("include")
			c = line.split("include")
			if c[1].nil?
				$stderr.puts "specify file to include from!"
				exit 1
			end
			c[1].strip!
			if $debugging then puts "including file: #{c[1]}" end
			process_file(c[1])
		else
			temp << line
		end
	end
	if $debugging then puts "end of file: #{path}" end
end

def ask_node(node)
	if(node.nil?) then return end
	file_to_add = nil
	
	if(!node.result.nil?) then return end

	# iterate deps
	if(!node.depends.nil?)
		node.depends.split(",").each {|dep|
			if ! (file_to_add = ask_node($node_hash[dep.to_sym])).nil?
				return file_to_add
			end
		}
	end
	
	path = node.path.dup
	path.pop
	printf("%s/ : %s\n%s\n", path.join("/"), node.key, node.desc)
	if (node.depends != "" && !node.depends.nil?)
		puts "depends on: #{node.depends}"
	end
	result=nil
	# check dependencies
	if node.check_deps == false
		puts node.name + "?"
		puts "--> n (DEPEND)"
		result="n"
	elsif $default_config
		if !node.default.nil?
			puts node.name + "?"
			result = node.default
			puts "--> #{result} (DEFAULT)"
		end
	end
	if result.nil?
		result = get_input(node.name,
					node.ans.nil? ? 
						nil : node.ans.split(","), node.default)
	end
	puts ""
	node.result = result
	if !node.res_include[result.to_sym].nil? then
		file_to_add = node.res_include[result.to_sym];
	end
	return file_to_add
end

def iterate_questions()
	file_to_add = nil;
	$node_hash.each_pair do |key, node|
		if ! node.result.nil?
			next
		end

		if ! (file_to_add = ask_node(node)).nil?
			break
		end
	end

	if ! file_to_add.nil?
		process_file(file_to_add);
		iterate_questions()
	end
end


ARGV.each_index do |idx|
	opt = ARGV[idx]
	if opt == "-o" || opt == "--output"
		$output_file = ARGV[idx+1].dup
		ARGV.delete_at(idx+1);
	elsif opt == "-$"
		$debugging = true
	elsif opt == "-d"
		$default_config = true
	else
		$input_files.push(opt.dup)
	end
end

if $debugging then puts "building chain..." end

$input_files.each { |file| process_file(file) }

if $debugging then puts "chain built. Asking questions..." end

iterate_questions()

file = File.open($output_file, "w")
File.truncate($output_file, 0)

puts "writing configuration to #{$output_file}"
file.puts(config_header)
$node_hash.each_value do |node|
	if node.result.nil?
		puts "*** BUG: unanswered configuration node"
	end
	if node.dnwv != node.result
		file.puts(node.key + "=" + node.result.to_s)
	end
end
file.close

