#!/usr/bin/env ruby
# $Id: IO.rb,v 1.5 2001/08/28 09:07:49 matju Exp $
=begin

	MetaRuby
	file: IO-related and File-related modules

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


	<blink>
	WARNING: not only this sourcefile is not debugged, it is also
	confused, confusing, and subject to sudden changes at any time
	with no backward compatibility.
	</blink>

TESTING (TestIO+TestFile)

	Some Test Results (Most Recent First):

	goal: <= 42

	*     Run: 52+33/85 (103+33 asserts) Fails: 3+36 Errs: 33+0 Pass: 13
	*     Run:    52/52     (20 asserts) Fails:    0 Errs:   52 Pass:  0

	(note: total asserts: 1240)

=end

require "Contract"

#--------------------------------------------------------------------------#
module SimpleIOP; module Contract
	include ::Contract

# category: stream

	# a stream is a system in which data is "push"ed onto one end and
	# "shift"ed from the other end.

	# SimpleIOP is an character-stream protocol that exposes at most
	# one of each kind of stream end. When two stream ends are available,
	# they may be one end of a duplex stream, one end of a read-write stream,
	# or the two ends of a same stream.



	# for finding out which parts of the protocol are usable.

	def open_read?
		result = super
		assert_type("post: result",result,Boolean)
		result
	end

	def open_write?
		result = super
		assert_type("post: result",result,Boolean)
		result
	end

	# reads n (or less) bytes of data from the input stream.
	# all data read is returned as a String.
	# #open_read? must return true.

	def sysread(n)
		assert_nonneg("n",n)
		result = super(n)
		assert_type("post: result",result,String)
		assert_type("post: result.length",result.length,0...n)
		result
	end

	# writes String v to the output stream.
	# in some modes, less data may be written.
	# #open_write? must return true.

	def syswrite(v)
		assert_type("v",v,String)
		result = super(v)
		assert_type("post: result",result,Integer)
		assert_nonneg("post: result",result)
	end


# category: other

	def close_read;  raise NotImplementedError; end
	def close_write; raise NotImplementedError; end
	def close;       raise NotImplementedError; end
	def fileno;      raise NotImplementedError; end

	#!@#$ ???

	def sysseek(i,whence); raise NotImplementedError; end
	def systell;           raise NotImplementedError; end
end end

#--------------------------------------------------------------------------#
module HollowIO

# category: base

	def initialize

		# backward compatibility with DOS or something
		@binmode = false

		# buffer for reading
		@rbuffer = ""

		# buffer for writing
		@wbuffer = ""

		# operations should try to keep buffer sizes from
		# growing over this level
		@buffermax = 4096

		# recommended buffer size for reading (not used for now)
		@buffermin = 4096

		# whether buffering is turned off
		@sync = false

		# byte position in the file
		@pos = 0

		# line counter
		@lineno = 0
	end

# category: main

	def <<(v)
		write v
		self
	end

	def binmode
		@binmode = true
	end

	def closed?
		not open_read? and not open_write?
	end

	def eof; eof?; end

	def eof?
		#!@#$
		false
	end

#	def reopen(other_io)
#		#!@#$ write me
#	end

#	def stat
#		#!@#$ write me
#	end

	def tell; pos; end
	def to_i; fileno; end
	def to_io; self; end

# category: buffering system

	def flush
		flush2
		self
		#!@#$
	end

	def flush2
		n = syswrite(@wbuffer)
		@wbuffer[0,n]=""
		n
	end

	attr_reader :sync

	def sync_protect(&proc)
		old = @sync
		@sync = false
		proc.call
		@sync = true
	end
	# private :sync_protect

	def sync=(a_bool)
		@sync = a_bool
		flush if @sync
	end

	attr_reader :lineno

	def lineno=(v)
		v===Integer or raise TypeError
		@lineno = v
	end

	attr_reader :pos

	def ungetc(char)
		@rbuffer[0,0]=""
		@rbuffer[0] = char
	end

	# try to fill the read buffer if it's smaller than n
	# except n=-1 means always try
	# returns new size of the read buffer.
	def read_fill(n=-1)
		if n<0 or @rbuffer.length < n then
			v = sysread(@buffermax-n)
			@rbuffer << v
		end
		@rbuffer.length
	end
	# private :read_fill

# category: main buffered operations

	def getc
		return nil if eof?
		read_fill 1
		return nil if @length<0
		@pos+=1
		@rbuffer.slice!(0)
	end

	def print(*args)
		#!@#$ problem with $_
		args << $_ if args.length==0
		args.each {|v|
			write(case v
				when String; v
				else v.to_s; end)
			write $\ unless $\.nil?
		}
		nil
	end

	def printf(format,*args)
		write sprintf format, *args
	end

	def putc(other)
		@wbuffer << (case other
			when String; other[0]
			when Integer; other; end)
		flush if sync
	end

	def puts(*args)
		#!@#$ handle loops like IO#puts
		args = args.flatten
		args.each {|v|
			write(case v
				when String; v
				else v.to_s; end)
			write $\ unless $\.nil? or v[-1]==10
		}
	end

	def read(n)
		return nil if eof?
		miss = n-@rbuffer.length
		@rbuffer = sysread(miss) if miss
		@rbuffer.slice!(0,[@rbuffer.length,n].min)
	end

	def readchar
		raise EOFError if eof? 
		getc
	end

	def each_byte(&proc)
		while v=getc
			proc.call v
		end
	end

	def write(v)
		@wbuffer << (case v
			when String; v
			else; v.to_s; end)
		flush if sync
	end

# category: reading lines

	def each(sep=$/,&proc); each_line(sep,&proc); end

	def each_line(sep=$/,&proc)
		while v=gets(sep)
			proc.call v
		end
	end

	def gets(sep=$/)
		sep ||= "\n"*2
		read_fill
		foo = @rbuffer.match(/^(.*\n)/)
		result = foo[0]
		@rbuffer[0,result.length]=""
		#!@#$ problem with $_
		$_ = result
		result
	end

	def readline(sep=$/)
		raise EOFError if eof?
		gets(sep)
	end

	def readlines(sep=$/)
		lines=[]
		each_line(sep) {|v| lines << v}
		lines
	end

# category: seekable

	def pos=(i); seek(i,IO::SEEK_SET); end
	def rewind;  seek(0,IO::SEEK_SET); end

	def seek(i,whence)
		@rbuffer = ""
		sysseek(i,whence) #!@#$
		@pos = systell #!@#$
	end

# category: special streams

#	def fcntl(intcmd,arg)
#		#!@#$ write me
#	end

#	def ioctl(intcmd,arg)
#		#!@#$ write me
#	end

	def isatty; tty?; end

	#!@#$
	def tty?; false; end

	#!@#$
	def pid; -1; end
end
#--------------------------------------------------------------------------#
module HollowFile
	include HollowIO # ??? multiple-inheritance's house of mirrors ???


	#!@#$ write me
end

#--------------------------------------------------------------------------#
module HollowIOClass
	class LOL < StandardError; end

	def delegate; IO; end

	def pipe; delegate.pipe; end

	def popen(cmd,mode="r",&proc)
		raise LOL
		delegate.popen(cmd,mode,&proc)
	end

	def readlines(portname,sep=$/)
		delegate.open(portname) {|f| f.readlines }
	end

	def select(readarray,writearray=[],errorarray=[],timeout=-1)
		delegate.select(readarray,writearray,errorarray,timeout)
	end

	def foreach(portname,sep=$/,&proc)
		File.open(portname) {|f| f.each(&proc) }
	end
end

#--------------------------------------------------------------------------#
module HollowFileClass
	include HollowIOClass # ???

	def delegate; File; end
	def associate; AutoFile; end

#	def open(filename,mode='r',&proc)
#	def open(filename,modenum,permnum,&proc)
	def open(filename,mode="r",*args,&proc)
		#!@#$ write me
		f = associate.new(filename,mode,*args)
		if proc then
			result = proc.call f
			f.close
			result
		else
			f
		end
	end

	def delete(filename,*args); delegate.delete(filename,*args); end
	def exist?(filename); delegate.exist?(filename); end

end

#--------------------------------------------------------------------------#
class AutoIO
	module Implementation
		#!@#$ write me
	end

	def new(fd,mode)
		# dummy
		@fd = fd
		@mode = mode
		AutoFile.new
	end

	def initialize
		raise "AbstractClass" if type==AutoIO
	end

	include HollowIO
	include Implementation
	include SimpleIOP::Contract
	extend HollowIOClass
end

#--------------------------------------------------------------------------#
class AutoFile
	module Implementation; include SimpleIOP
		def sysread(n);  @inner.sysread(n);  end
		def syswrite(n); @inner.syswrite(n); end
		def close;  @inner.close;  end
		def fileno; @inner.fileno; end
		def sysseek(n); @inner.seek(n); end
		def systell(n); @inner.tell(n); end
	end

	def initialize(filename,*args)
		@inner = File.new(filename,*args)
		super()
	end

	include HollowIO
	include Implementation
	include SimpleIOP::Contract
	extend HollowFileClass
end
