include Math

class BasicNumArray < Array

# BasicNumArray -- Numeric Array
# 1999/09/08  T. Horinouchi
# 2000/01/22  T. Horinouchi
# 
# Super class: Array
#
# class methods
#
#   BasicNumArray.new(len[,val])  create an array of length len, filled
#                                 with val or nil if val is omitted
#                                 (Sorry not zero)
#   BasicNumArray.from_a(array)                   Array->BasicNumArray
#   BasicNumArray[*array]                         the same as above
#
#   BasicNumArray.span(len,inival=0,incrememt=1)  arithmetic series starting
#                                                 with inival
#
# methods Extention/Redefinition
#
#   repeat    : '*' of Array
#   merge     : '+' of Array
#
#   to_a                          :    into Array class
#
#   (operators) + - * / **        :    Numeric definitions
#
#   span([inival[,increment]])    :   fill with inival+increment*[0,1,2,..]
#
#   abs
#
#   sum                  sum of all elements
#   product              product of all elements
#
#   sin cos tan exp log log10 sqrt ldexp atan2  :  from Math module
#                         No remedy for invalid inputs such as negative values
#                         for sqrt.

  class << self
    alias __new__ new   # preserve the orinal "new"
    private :__new__    # and hide it
    def new(len, val=0)
      if(!val.is_a?(Numeric) && !val.is_a?(TrueClass) && 
	 !val.is_a?(FalseClass) && !val.is_a?(NilClass)) then
	raise("Fill value is not numeric") 
      end
      super(len,val)
    end
  end

  def numtype
    # return the type of the elements
    self[0].type       # do not check the other elements
  end

  # never ever needed:
  private :assoc, :rassoc
  private :flatten, :flatten!
  private :compact, :compact!, :clear, :nitems
  # may become public again later, but not needed for a while:
  # (lets's start with a small set)
  private :<=>, :&, :|, :<<
  private :delete, :delete_at, :delete_if, :reject!, :uniq, :uniq!
  #

  alias _repeat_ *
  private :_repeat_    # redefined as "repeat" below
  alias _merge_ +
  private :_merge_     # redifined as "merge" below

  def repeat(b); BasicNumArray.from_a(_repeat_(b)) ; end
  def merge(b); BasicNumArray.from_a(_merge_(b)) ; end

  def BasicNumArray.from_a(ar)
    # Array->BasicNumArray (no content check)
    BasicNumArray[*ar]
  end

  def BasicNumArray.span(len,inival=0,increment=1)
    # create a linearly valued BasicNumArray
    out=BasicNumArray.new(len)
    out.span(inival,increment)
    return out
  end

  def [](*a)
    if a.length==1 and a[0].is_a?(Numeric) then
      super(*a)
    else
      BasicNumArray[*super(*a)]
    end
  end

  def to_a
    Array[*self]
  end

  def span(inival=0,increment=1)
    # fill linearly varying values starting at INIVAL with INCREMENT
    for i in 0..self.length-1
      self[i]=inival+increment*i
    end
  end

  def indices(*a)
    BasicNumArray[*super(*a)]
  end

  ## logical operations ##

  def logical?
    # whether a "logical" array that consists of true and/or false(nil)
    (t=self.numtype) == TrueClass || t == FalseClass || t == NilClass
  end

  def negate!
    if ! logical? then raise "not a logical array"; end
    for i in 0..self.length-1
      self[i] = !self[i]
    end
  end
  def negate
    out=self.dup
    out.negate!
    out
  end

  Operators = [["le","<="],["lt","<"],["ge",">="],["gt",">"],["and_","&&"],["or_","||"]]

  private
  def BasicNumArray.def_logical_operators
    for op in Operators
      eval <<-EOS
      def #{op[0]}(other)
	if other.is_a?(Numeric) then
	  out=BasicNumArray.new(l=self.length)
	  for i in 0..l-1
	    out[i] = self[i] #{op[1]} other
	  end
	  out
	elsif other.is_a?(Array) then
	  out=BasicNumArray.new(l=self.length)
	  for i in 0..l-1
	    out[i] = self[i] #{op[1]} other[i]
	  end
	  out
	else
	  coerce_me, coerce_other = other.coerce(self)
	  coerce_me.#{op[0]}(coerce_other)
	end
      end
      EOS
    end
  end

  def_logical_operators

  public

  ##private :BasicNumArray.def_operators

  ## operators ##
  def coerce(other)
    if other.kind_of?(Float) || other.kind_of?(Integer)
      return BasicNumArray.new(self.length,other), self
    else
      super
    end
  end

  def +(other)
    if other.is_a?(Numeric) then
      out=self.dup
      for i in 0..self.length-1
	out[i]=self[i]+other
      end
      out
    elsif other.is_a?(Array) then
      out=self.dup
      if other.length < self.length then
	raise(RuntimeError,"size of the operand is too short")
      end
      for i in 0..self.length-1
	out[i]=self[i]+other[i]
      end
      out
    else
      coerce_me, coerce_other = other.coerce(self)
      coerce_me + coerce_other
    end
  end
  def -(other)
    if other.is_a?(Numeric) then
      out=self.dup
      for i in 0..self.length-1
	out[i]=self[i]-other
      end
      out
    elsif other.is_a?(Array) then
      out=self.dup
      if other.length < self.length then
	raise(RuntimeError,"size of the operand is too short")
      end
      for i in 0..self.length-1
	out[i]=self[i]-other[i]
      end
      out
    else
      coerce_me, coerce_other = other.coerce(self)
      coerce_me - coerce_other
    end
  end
  def *(other)
    if other.is_a?(Numeric) then
      out=self.dup
      for i in 0..self.length-1
	out[i]=self[i]*other
      end
      out
    elsif other.is_a?(Array) then
      out=self.dup
      if other.length < self.length then
	raise(RuntimeError,"size of the operand is too short")
      end
      for i in 0..self.length-1
	out[i]=self[i]*other[i]
      end
      out
    else
      coerce_me, coerce_other = other.coerce(self)
      coerce_me * coerce_other
    end
  end
  def /(other)
    if other.is_a?(Numeric) then
      out=self.dup
      for i in 0..self.length-1
	out[i]=self[i]/other
      end
      out
    elsif other.is_a?(Array) then
      out=self.dup
      if other.length < self.length then
	raise(RuntimeError,"size of the operand is too short")
      end
      for i in 0..self.length-1
	out[i]=self[i]/other[i]
      end
      out
    else
      coerce_me, coerce_other = other.coerce(self)
      coerce_me / coerce_other
    end
  end

  def +@
    return self.dup
  end
  def -@
    out=self.dup
    for i in 0..self.length-1
      out[i]=-self[i]
    end
    return out
  end

  def **(num)
    out=self.dup
    if num.is_a?(Numeric)
      for i in 0..self.length-1
	out[i]=self[i]**num
      end
    else
      raise(RuntimeError,"invalid type of the operand: "+num.type.to_s)
    end
    return out
  end

  ## mathematics ##

  def abs
    out=self.dup
    for i in 0..self.length-1
      out[i]=self[i].abs
    end
    return out
  end

  def sum
    sum=0
    for i in 0..self.length-1
      sum+=self[i]
    end
    sum
  end

  def product
    pro=1
    for i in 0..self.length-1
      pro*=self[i]
    end
    pro
  end

  ### from Math module ###
  def sin
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.sin(self[i]); end
    return out
  end
  def cos
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.cos(self[i]); end
    return out
  end
  def tan
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.tan(self[i]); end
    return out
  end
  def exp
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.exp(self[i]); end
    return out
  end
  def log
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.log(self[i]); end
    return out
  end
  def log10
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.log10(self[i]); end
    return out
  end
  def sqrt
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.sqrt(self[i]); end
    return out
  end
  def ldexp(exp)
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.ldexp(self[i],exp); end
    return out
  end
  def atan2(y)
    # atan2(self,y) = atan(self/y)
    out=self.dup
    for i in 0..self.length-1; out[i]=Math.atan2(self[i],y[i]); end
    return out
  end

end

## test for develeopment ##
if __FILE__ == $0
  p a=BasicNumArray.new(3)
  p a=BasicNumArray.new(3,true)
  p a=BasicNumArray.span(3)
  p "#",a[1],a[0..-1],a[0..-1].type,"#"
  p a.type,a.methods
  p a+10,a+a
  p 10-a
  p a.length
  for i in 0..2; a[i]=i+1; end
  p a
  p a+a
  p a*10
  p a**4
  p -a
  c=BasicNumArray[true,false,false,nil,true]
  p c.logical?,c
  p c.negate
  b=(a-2)*3
  p a,b 
  p a.le b
  c=a*2-1
  p a.lt c
  d=a.le b
  p d.type
  p "$$",a.le(b).type,(a.le(b)).type
  p a.le(b).and_ a.lt(c)
end

