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

	MetaRuby
	file: Hollow Array

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

=end

require "Contract"
require "Hollow/List"

module ArrayP; end
class Array; include ArrayP; end

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

module SimpleArrayP; module Contract
	include ::Contract

	# length() returns the number of elements in the array.
	def length
		result = super
		assert_type("post: result",result,Fixnum)
		assert_nonneg("post: result",result)
		result
	end

	# those methods handle a single cell at a time.
	# i, the cell number, must be in 0...length
	# get: returns the value
	# put: sets to a new value v; returns void.
	# note: those never, ever change the value of length.

	def get(i)
		assert_index_excl(i)
		super
	end

	def put(i,v)
		assert_not_frozen
		assert_index_excl(i)
		result = super
		assert_type("post: result",result,NilClass)
		result
	end

	# those methods handle any number of cells at a time.
	# i, the start point, must be in 0..length
	# i+n, the end point, must be in 0..length
	# get_seq: returns plain Array of values at i...(i+n)
	# put_seq: at i, replaces n elements by elements of v; returns void
	# v is a plain Array.

	def get_seq(i,n)
		assert_index_incl(i)
		assert_nonneg("n",n)
		assert_extent(i,n)
		result = super
		assert_type("post: result",result,Array)
		result
	end

	def put_seq(i,n,v)
		assert_not_frozen
#		old_length = length
		assert_index_incl(i)
		assert_nonneg("n",n)
		assert_extent(i,n)
		assert_type("v",v,Array)
		result = super
		assert_type("post: result",result,NilClass)
#		assert_eq("post: length",length,old_length-n+v.length)
		result
	end

	# Exceptions:
	# ArgumentError: misc argument error, if applicable
	# May throw other exceptions when it makes sense.
end end

#--------------------------------------------------------------------------#
module HollowArray
	include ArrayP
	include HollowList
	include Enumerable

	# could have:
	#   include Comparable
	# well, maybe

# category: base

	def builtin_class;    Array; end
	def builtin_new;      x=Array[nil]; x.pop; x; end
	def to_builtin(v);    v.to_a; end
	def to_builtin2(v);   v.to_ary; end
	def same_builtin?(v); ArrayP === v; end
	def get_default(i);       nil; end

	# private :builtin_class, :builtin_new, :to_builtin, :same_builtin?

	def initialize(length=0,value=nil)
		# presuming the array is created by the implementor,
		# and is empty.

		@frozen = false # not used yet

		if length < 0 then
			raise ArgumentError, "negative array size"
		end

		(0...length).each {|i| put i,value }
	end

# category: frozen

	# see also HollowList#modify
	def freeze; @frozen = true; end
	def frozen?; @frozen; end

# category: [] and []=

	# see HollowList now

	def first; self[0]; end
	def last; self[-1]; end

	def []=(*stuff); modify {super}; end
	def slice!(*stuff); modify {super}; end

# category: predicates and such

	# see HollowList for the rest

	alias === ==

# category: miscellaneous operations

	def *(n)
		case n
		when String
			join(n)
		when Fixnum
			n >= 0 or raise ArgumentError, "negative count"
			foo = builtin_new
			return foo if length == 0
			n.times { foo.push(*self) }
			foo
		end
	end

	def clear; modify {
		put_seq 0,length,[]
		self
	}end

	def concat(other); modify {
		put_seq length,0,other.to_ary
		self
	}end

	def delete(v,&proc); modify {
		a = length
		delete_if {|x| x == v}
		if a == length then
			proc && proc.call
		else
			v
		end
	}end

	def delete_at(i); modify {
		i += length if i < 0
		return nil if not (0...length) === i
		foo = get(i)
		put_seq(i,1,[])
		foo
	}end

	def delete_if(&proc); modify {
		foo = []
		each {|x|
			foo << x unless proc.call(x)
		}
		replace foo
	}end

	def reject!(&proc); modify {
		foo = length
		delete_if(&proc)
		foo == length ? nil : self
	}end

	def each(&proc)
		length.times {|i|
			# this is the magic case of yield in action. sorry.
			yield get(i)
#			proc.call get(i)
		}
		self
	end

	def each_index(&proc)
		(0...length).each(&proc)
		self
	end

	def fill(v,start=0...length,n=nil); modify {
		i=42
		case start
		when nil
			i = 0
		when Range
			i = start.begin
			n = start.end - start.begin + (start.exclude_end? ? 0 : 1)
		when Fixnum
			i = start
		else
			raise TypeError
		end
		n = length - i if nil==n
		(i ... (i+n)).each {|i| self[i] = v }
		self
	}end

	def filter(&proc)
		(0...length).each {|i| put i, proc.call(get i) }
	end

	def flatten!; modify {
		modified = false
		visited = []
		i=0
		while i < length
			x = get i
			if ArrayP === x then
				if self == x then
					x = nil
				else
					id = x.id
					if visited.include?(id) then
						raise "tried to flatten recursive array"
					end
					visited << id
				end
				put_seq i, 1, x.to_ary
				modified = true
			else
				i += 1
			end
		end
		return self if modified
		nil
	}end

	# this may be a good candidate for adding to SimpleArrayP
	def indices(*i)
		i.collect {|x| at x }
	end
	alias indexes indices

	# now in O(n)
	def join(sep=$,)
		sep = sep.to_s
		return "" if length==0
		foo = get(0).to_s.dup
		(1...length).each {|i|
			foo.concat sep
			foo.concat get(i).to_s
		}
		foo
	end

	def pack(template)
		#!@#$ write me (?)
		to_ary.pack(template)
	end

	def replace(other); modify {
		put_seq 0,length,other.to_ary
		self
	}end

	def reverse_each(&proc)
		length.times {|i|
			proc.call get(length-1-i)
		}
		self
	end

	# ruby 1.6
	alias slice []

	# can't say just "self"
	def to_a  ; get_seq 0,length; end
	def to_ary; get_seq 0,length; end

	def to_s;	join; end

	#!@#$ ???
	def hash
		h = 0
		each {|v|
			h = (h<<1) | (h<0 ? 1 : 0)
			h &= 2**32-1
			n = v.hash
			n &= 2**32-1
			h ^= n
		}
		h
	end

	def inspect
		#!@#$ should prepend class name ?
		"[%s]" % [map {|x| x.inspect }.join ', ']
	end

	def dup
		foo = type.new.replace(self)
		foo.taint if tainted?
		foo
	end

# category: finding

	def include?(v)
		[] != find_all {|x| x == v }
	end

	def index(v)
		ret = nil
		each_index {|i|
			x = get i
#			STDERR.printf "x=%s, v=%s, i=%d\n", x.inspect, v.inspect, i
			if x==v then ret = i; break; end
		}
		ret
	end

	def rindex(v)
		ret = nil
		each_index {|i|
			x = get length-1-i
			if x==v then ret = length-1-i; break; end
		}
		ret
	end

# category: stack/queue

	def push(*v); modify {
		# Array#push does this check (may be superfluous)
		raise ArgumentError, "wrong # of arguments(at least 1)" if v.length == 0
		put_seq length,0,to_builtin(v)
		self
	}end

	alias << push

	def pop; modify {
		return nil if length == 0
		foo = get(length-1)
		put_seq length-1,1,builtin_new
		foo
	}end

	def shift; modify {
		return nil if length == 0
		foo = get(0)
		put_seq 0,1,builtin_new
		foo
	}end

	def unshift(*v); modify { put_seq 0,0,v; self }; end

# category: dup/replace wrappers for some methods

	def compact; foo = dup; foo.compact!; foo; end
	def flatten; foo = dup; foo.flatten!; foo; end
	def sort(&proc); foo = dup; foo.sort!(&proc); foo; end
	def reject(&proc); dup.reject!(&proc); end
	def map!(&proc); modify { replace map(&proc) }; end
	def collect!(&proc); modify { replace collect(&proc) }; end

# category: shortcuts involving nil

	def compact!; modify { reject! {|x| x.nil? }}; end
	def nitems; find_all {|x| ! x.nil? }.length; end

# category: array of [key,value] arrays

	def assoc(key)
		find {|x| x[0] == key}
	end

	def rassoc(value)
#		print "\n"; p value
#		find {|x| print "\n"; p x; p(x[1] == value); x[1] == value}
		find {|x| x[1] == value}
	end

# category: sorting

	# &proc must be present
	def sort_partition_range!(i,n,&proc)
		pivot = get(i+n-1)
		foo = get_seq(i,n)
		bar = foo.find_all {|x| proc.call(x,pivot) == -1 }
		a = bar.length
		bar[bar.length,0] = foo.find_all {|x| proc.call(x,pivot) == 0 }
		b = bar.length
		bar[bar.length,0] = foo.find_all {|x| proc.call(x,pivot) == +1 }
		raise IndexError, 'err' if bar.length != foo.length
		put_seq i,n,bar
		return [i,a,i+b,bar.length-b]
	end

	# &proc must be present
	def sort_range!(i,n,&proc)
		if n > 1
			a,an,b,bn = sort_partition_range!(i,n,&proc)
			ar = sort_range!(a, an, &proc)
			br = sort_range!(b, bn, &proc)
		end
	end

	def sort!(&proc); modify {
		proc ||= proc{|a,b| a<=>b }
		if length > 1 then
			sort_range!(0,length,&proc)
			self
		else
			VERSION < "1.7" ? nil : self
		end
	}end

	# private :sort_partition_range!, :sort_range!

# category: set operations

	def +(other)
		foo = dup
		foo[length,0]=other.to_ary
		foo
	end

	def -(other)
		t = {}
		other.each {|x| t[x]=1 }
		foo = dup
		foo.delete_if {|x| t[x] }
		foo.uniq! || foo
	end

	# as seen in [ruby-talk:6756]
	def &(other)
		t = {}
		r = []
		other.each {|x| t[x]=1 }
		uniq.each  {|x| r<<x if t.has_key? x }
		r
	end

	# as seen in [ruby-talk:6756]
	def |(other)
		t = {}
		r = []
		(self+other).each {|x| r<<x unless t.has_key?(x); t[x]=1 }
		r
	end

	# as seen in [ruby-talk:6756]
	def uniq; self|[] end
	def uniq!; modify {
		foo=length; replace self|[]; foo==length ? nil : self
	}end

# methods for: modification wrapping

	# i wrote modify { ... } all over instead

#	class << self
#		@cereal = 0
#
#		def wrap(method,*args)
#			newname = sprintf "zzz_%03d",cereal
#			cereal+=1
#			eval %{
#				alias :#{newname} :#{method}
#				def #{method}(...)
#			}
#			wrap(*args) if args.length>0
#		end
#	end
#
#	wrap :<<, :[]=, :clear, :collect!, :compact!, :concat
#	wrap :delete, :delete_at, :delete_if, :fill, :flatten!
#	wrap :map!, :pop, :push, :reject!, :replace, :reverse!
#	wrap :shift, :slice!, :sort!, :uniq!, :unshift

end

#--------------------------------------------------------------------------#
# not sure whether I really should do such things,
# but I think Array should cooperate.

class Array
	unless instance_methods.include?(:orig_equality)
		alias orig_equality ==
		def ==(other)
			if HollowArray===other then
				other==self
			else
				orig_equality(other)
			end
		end
	end
end

#--------------------------------------------------------------------------#
class AutoArray

	def self.[](*v); new.replace v; end

	def initialize(*args)
		(@inner = [nil]).pop # this makes array smaller than just []
		super(*args)
	end

	module Implementation; include SimpleArrayP
		def length; @inner.length; end
		def get(i); @inner[i]; end
		def get_seq(i,n); @inner[i,n]; end

		def put(i,v)
#			STDERR.puts "put(#{i},?)"
			@inner[i]=v
			nil
		end
		def put_seq(i,n,v)
#			STDERR.puts "put_seq(#{i},#{n},? of length #{v.length})"
			@inner[i,n]=v
			nil
		end
	end

	include HollowArray
	include Implementation
	include SimpleArrayP::Contract
end

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