=begin

full file_system module
will be available and recommended usage, allowing Ole::Storage, Dir, and Zip::ZipFile to be
used pretty exchangably down the track. should be possible to write a recursive copy using
the plain api, such that you can copy dirs/files agnostically between any of ole docs, dirs,
and zip files.

i think its okay to have an api like this on top, but there are certain things that ole
does that aren't captured.
ole::storage can have multiple files with the same name, for example, or with / in the
name, and other things that are probably invalid anyway.
i think this should remain an addon, built on top of my core api.
but still the ideas can be reflected in the core, ie, changing the read/write semantics.

once the core changes are complete, this will be a pretty straight forward file to complete.

=end

module Ole
	class Storage
		def file
			@file ||= FileParent.new self
		end

		def dir
			@dir ||= DirParent.new self
		end

		def dirent_from_path path_str
			path = path_str.sub(/^\/*/, '').sub(/\/*$/, '')
			dirent = @root
			return dirent if path.empty?
			path = path.split /\/+/
			until path.empty?
				raise "invalid path #{path_str.inspect}" if dirent.file?
				if tmp = dirent[path.shift]
					dirent = tmp
				else
					# allow write etc later.
					raise "invalid path #{path_str.inspect}"
				end
			end
			dirent
		end

		class FileParent
			def initialize ole
				@ole = ole
			end

			def open path_str, mode='r'
				dirent = @ole.dirent_from_path path_str
				# like Errno::EISDIR
				raise "#{path_str.inspect} is a directory" unless dirent.file?
				io = dirent.io
				if block_given?
					yield io
				else
					io
				end
			end

			alias new :open

			def read path
				open(path) { |f| f.read }
			end

			# crappy copy from Dir.
			def unlink path
				dirent = @ole.dirent_from_path path
				# EPERM 
				raise "operation not permitted #{path.inspect}" unless dirent.file?
				# i think we should free all of our blocks. i think the best way to do that would be
				# like:
				# open(path) { |f| f.truncate 0 }. which should free all our blocks from the
				# allocation table. then if we remove ourself from our parent, we won't be part of
				# the bat at save time.
				# i think if you run repack, all free blocks should get zeroed.
				parent = @ole.dirent_from_path(('/' + path).sub(/\/[^\/]+$/, ''))
				parent.children.delete dirent
				1 # hmmm. as per ::File ?
			end
		end

		class DirParent
			def initialize ole
				@ole = ole
			end

			def open path_str
				dirent = @ole.dirent_from_path path_str
				# like Errno::ENOTDIR
				raise "#{path_str.inspect} is not a directory" unless dirent.dir?
				dir = Dir.new dirent, path_str
				if block_given?
					yield dir
				else
					dir
				end
			end

			# certain Dir class methods proxy in this fashion:
			def entries path
				open(path) { |dir| dir.entries }
			end

			# there are some other important ones, like:
			# chroot (!), mkdir, chdir, rmdir, glob etc etc. for now, i think
			# mkdir, and rmdir are the main ones we'd need to support
			def rmdir path
				dirent = @ole.dirent_from_path path

				# repeating myself
				raise "#{path.inspect} is not a directory" unless dirent.dir?
				# ENOTEMPTY:
				raise "directory not empty #{path.inspect}" unless dirent.children.empty?
				# now delete it, how to do that? the canonical representation that is
				# maintained is the root tree, and the children array. we must remove it
				# from the children array.
				# we need the parent then. this sucks but anyway:
				parent = @ole.dirent_from_path path.sub(/\/[^\/]+$/, '') || '/'
				# note that the way this currently works, on save and repack time this will get
				# reflected. to work properly, ie to make a difference now it would have to re-write
				# the dirent. i think that Ole::Storage#close will handle that. and maybe include a
				# #repack.
				parent.children.delete dirent
				0 # hmmm. as per ::Dir ?
			end

			class Dir
				include Enumerable
				attr_reader :dirent, :path, :entries, :pos

				def initialize dirent, path
					@dirent, @path = dirent, path
					@pos = 0
					# FIXME: hack, and probably not really desired
					@entries = %w[. ..] + @dirent.children.map(&:name)
				end

				def each(&block)
					@entries.each(&block)
				end

				def close
				end

				def read
					@entries[@pos]
				ensure
					@pos += 1 if @pos < @entries.length
				end

				def pos= pos
					@pos = [[0, pos].max, @entries.length].min
				end

				def rewind
					@pos = 0
				end

				alias tell :pos
				alias seek :pos=
			end
		end
	end
end