=begin

	MetaRuby
	file: UndoQueue

	Copyright (c) 2001 by Mathieu Bouchard
	Licensed under the same license as Ruby.

=end

require "MainLoop" # for class MainLoop::Message.
Message = MainLoop::Message

class UndoQueue
	attr_reader :undo_queue
	attr_reader :redo_queue

	def initialize(*args)
		@undo_queue = []
		@redo_queue = []
		super
	end

	# overload this if you want to control how many levels
	# of undo may be kept.
	# keep in mind that undo information is kept hierarchically.
	def add(message)
		@undo_queue << message
		@redo_queue.clear
	end

	# runs the restore procedure for the last item in the root undo_queue.
	def undo
		backup_undo = @undo_queue
		@undo_queue = @redo_queue
		@redo_queue = []
		perform backup_undo.pop
		@redo_queue = @undo_queue
		@undo_queue = backup_undo
		self
	end

	def redo
		backup_redo = @redo_queue
		@redo_queue = []
		perform backup_redo.pop
		@redo_queue = backup_redo
	end

	# this traverses a tree and runs all the restore operations.
	#!@#$ could I use #flatten! now ?
	def perform(message); atomically {
		if Array===message then
			message.reverse_each{|x| perform(x) }
		else
			message.call
		end

	} end

	def atomically(&proc)
		backup_undo = @undo_queue
		backup_redo = @redo_queue
		@undo_queue = []
		@redo_queue = []
		begin
			proc.call
		ensure
			backup_undo << @undo_queue
			@undo_queue = backup_undo
			@redo_queue = backup_redo
		end
	end
end

#--------------------------------------------------------------------------#

module UndoFeature

	attr_accessor :undo_queue

	def initialize(*args)
		@undo_queue = UndoQueue.new
		super
	end

	# this version of #modify atomicizes undo information;
	# which means that all operations occurring during the #modify block
	# will be undone by a single call to #undo.
	def modify(&proc); @undo_queue.atomically { super(&proc) }; end

	def undo; @undo_queue.undo; end
	def redo; @undo_queue.redo; end
end

#--------------------------------------------------------------------------#

require "Hollow/Array"

class UndoableArray < AutoArray
	include UndoFeature
#	def initialize(n=0,v=nil); super; end
	def put(i,v)
		@undo_queue.add Message.new(self,:put,i,get(i))
		super
	end
	def put_seq(i,n,v)
		@undo_queue.add Message.new(self,:put_seq,i,v.length,get_seq(i,n))
		super
	end
end

require "Hollow/String"

class UndoableString < AutoString
	include UndoFeature
#	def initialize(arg=""); super; end
	def put(i,v)
		@undo_queue.add Message.new(self,:put,i,get(i))
		super
	end
	def put_seq(i,n,v)
		@undo_queue.add Message.new(self,:put_seq,i,v.length,get_seq(i,n))
		super
	end
end

require "Hollow/Hash"
class UndoableHash < AutoHash
	include UndoFeature
#	def initialize(ifnone=nil); super; end
	def put(i,v)
		if has_key?(i) then
			@undo_queue.add Message.new(self,:put,i,get(i))
		else
			@undo_queue.add Message.new(self,:remove,i)
		end
		super
	end
	def remove(i)
		@undo_queue.add Message.new(self,:put,i,get(i))
		super
	end
end

#!@#$ fix #dup in those three sample classes.
