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

require "set/intspan"

module News

class Newsrc

def initialize(file=nil)
	@newsrc = { "group" => Hash.new, "list" => Array.new }
	if file
		unless load(file)
			print "Can't load #{file}\n"
			exit
		end
	end
end

def load(file=nil)
	file = "#{ENV['HOME']}/.newsrc" unless file
	@newsrc["file"] = file
	@newsrc["group"] = {}
	@newsrc["list"] = []

	if FileTest.file?( "#{file}" ) and FileTest.readable?( "#{file}" )
		lines = IO.readlines("#{file}")
		import_rc(lines)
	end

	return true
end

def import_rc(lines)
	@newsrc["group"] = {}
	@newsrc["list"] = []
	linenumber = 1
	for line in lines
		parse(line)
	end
end

def parse(line)
	unless line =~ /^([^!:]+)([!:])\s(.*)$/x
		print "Newsrc.parse: Bad newsrc line: #{line}\n"
		exit
	end

	name = $1
	mark = $2
	articles = $3

	unless Set::IntSpan.valid(articles)
		print "Newsrc.parse: Bad article list: #{line}\n"
	end


	group = { "name" => name, "subscribed" => (mark == ":"),
		"articles" => Set::IntSpan.new(articles)}

	@newsrc["group"][name] = group
	@newsrc["list"] += [ group ]
end

def save
	unless @newsrc.has_key?("file")
		@newsrc["file"] = "#{$ENV['HOME']}/.newsrc"
	end
	save_as(@newsrc["file"])
end

def save_as(file)
	if FileTest.exists?("#{file}")
		begin
			File.rename(file, "#{file}.bak")
		rescue
			print "Can't rename #{file}, #{file}.bak: #{$!}\n"
			exit
		end
	end
	begin
		newsrc = File.new(file, "w")
	rescue
		print "Can't open #{file}: #{$!}\n"
		exit
	end
	@newsrc["file"] = file
	for group in @newsrc["list"]
		newsrc.print format(group)
	end
	newsrc.close
end

def format(group)
	name = group["name"]
	sub = group["subscribed"] ? ':' : '!'
	articles = group["articles"].run_list
	#space = articles ? ' ' : ''
	#return "#{name}#{sub}#{space}#{articles}\n"
	return "#{name}#{sub} #{articles}\n"
end

def export_rc
	lines = @newsrc["list"].collect{ |group|
		name     = group["name"]
		sub      = group["subscribed"] ? ':' : '!'
		articles = group["articles"].run_list
		space    = articles ? ' ' : ''
		"#{name}#{sub}#{space}#{articles}\n" }
	return lines
end

def add_group(name, options)

	if @newsrc["group"].has_key?(name)
		options.has_key?("replace") or return false
		del_group(name)
	end
	group = {"name"       => name,
		"subscribed" => true,
		"articles"   => Set::IntSpan.new }

	@newsrc["group"][name] = group
	_insert(group, options)

	return true
end

def move_group(name, options)
	if @newsrc["group"].has_key?(name)
		group = @newsrc["group"][name]
	else
		return false
	end

	@newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name}

	_insert(group, options)
	return true
end

def _insert(group, options)
	list = @newsrc["list"]

	where = ""
	arg = ""
	if options.has_key?("where")
		where = options["where"]
	end
	arg = where.slice!(1) if where.type.to_s == "Array"

	case where.to_s
	when "first"
		@newsrc["list"].unshift(group)
	when "last"
		@newsrc["list"] += [ group ]
	when ""
		@newsrc["list"] += [ group ]		# default
	when "alpha"
		alpha(group)
	when "before"
		before(group, arg)
	when "after"
		after(group, arg)
	when "number"
		number(group, arg)
	end
end

def alpha (group)
	name = group["name"]
	for i in (0...@newsrc["list"].length)
		if ((name <=> @newsrc["list"][i]["name"]) == -1)
			upper = @newsrc["list"].slice!(i..@newsrc["list"].length)
			@newsrc["list"] += [ group ]
			@newsrc["list"] += upper
			return;
		end
	end
	@newsrc["list"] += [group]
end

def before(group, before)
	name = group["name"]
	for i in (0...@newsrc["list"].length)
		if (@newsrc["list"][i]["name"] == before.to_s)
			upper = @newsrc["list"].slice!(i..@newsrc["list"].length)
			@newsrc["list"] += [ group ]
			@newsrc["list"] += upper
		
			return;
		end
	end

	@newsrc["list"] += [group]
end

def after(group, after)
	name = group["name"]

	for i in (0...@newsrc["list"].length)
		if (@newsrc["list"][i]["name"] == after.to_s)
			upper = @newsrc["list"].slice!((i+1)..@newsrc["list"].length)
			@newsrc["list"] += [ group ]
			@newsrc["list"] += upper
			return;
		end
	end

	@newsrc["list"] += [group]
end

def number(group, offset)
	offset = @newsrc["list"].length if offset[0] > @newsrc["list"].length
	upper = @newsrc["list"].slice!(offset..@newsrc["list"].length)
	@newsrc["list"] += [ group ]
	@newsrc["list"] += upper
end


def del_group(name)
	if @newsrc["group"].has_key?(name)
		group = @newsrc["group"][name]
	else
		return false
	end

	@newsrc["group"].delete(name)
	@newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name}

	return true
end

def subscribe(name, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["subscribed"] = true
end

def unsubscribe(name, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["subscribed"] = false
end

def mark(name, article, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["articles"].insert(article)
end

def mark_list(name, list, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	articles = @newsrc["group"][name]["articles"].union(list)
	@newsrc["group"][name]["articles"] = articles
end

def mark_range(name, from, to, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	range = Set::IntSpan.new("#{from}-#{to}")
	articles = @newsrc["group"][name]["articles"].union(range)
	@newsrc["group"][name]["articles"] = articles
end

def unmark(name, article, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["articles"].remove(article)
end

def unmark_list(name, list, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	articles = @newsrc["group"][name]["articles"].diff(list)
	@newsrc["group"][name]["articles"] = articles
end

def unmark_range(name, from, to, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	range = Set::IntSpan.new("#{from}-#{to}")
	articles = @newsrc["group"][name]["articles"].diff(range)
	@newsrc["group"][name]["articles"] = articles
end

def exists(name)
	return @newsrc["group"].has_key?(name) ? true : false
end

def subscribed(name)
	exists(name) and @newsrc["group"][name]["subscribed"]
end

def marked(name, article)
	exists(name) and @newsrc["group"][name]["articles"].member(article)
end

def num_groups
	return @newsrc["list"].length
end

def groups
	list = @newsrc["list"].dup
	list.collect!{|x| x["name"]}
end

def sub_groups
	list = @newsrc["list"].dup
	list.collect!{|x| x["subscribed"] ? x["name"] : nil}.compact!
end

def unsub_groups
	list = @newsrc["list"].dup
	list.collect!{|x| x["subscribed"] ? nil : x["name"]}.compact!
end

def marked_articles(name, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	return @newsrc["group"][name]["articles"].elements
end

def unmarked_articles(name, from, to, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	range = Set::IntSpan.new("#{from}-#{to}")
	return range.diff(@newsrc["group"][name]["articles"]).elements
end

def get_articles(name, options = {"where" => ""})
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["articles"].run_list
end

def set_articles(name, articles, options = {"where" => ""})
	Set::IntSpan.valid(articles) or return false
	set = Set::IntSpan.new(articles)
	set.finite or return false
	min = set.min
	min != nil and min < 0 and return false
	unless @newsrc["group"].has_key?(name)
		add_group(name, options)
	end
	@newsrc["group"][name]["articles"] = set
	return true
end

end # class

end # module


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

# [x] new
# [x] load
# [ ] _scan    # Initializes a Newsrc object from a string.  Used for testing.
# [x] import_rc
# [x] parse   # parses a single line from a newsrc file
# [x] save
# [x] save_as
# [x] format
# [x] export_rc
# [ ] _dump	# Formats a Newsrc object to a string.  Used for testing
# [x] add_group
# [x] move_group
# [x] Splice(\@$$@)			# heet nu number en is simpeler
# [x] _insert
# [x] Alpha
# [x] Before
# [x] After
# [x] del_group
# [x] subscribe
# [x] unsubscribe
# [x] mark
# [x] mark_list
# [x] mark_range
# [x] unmark
# [x] unmark_list
# [x] unmark_range
# [x] exists
# [x] subscribed
# [x] marked
# [x] num_groups
# [x] groups
# [x] sub_groups
# [x] unsub_groups
# [x] marked_articles
# [x] unmarked_articles
# [x] get_articles
# [x] set_articles
