class ArrayDimIndex
  # class of array index for each dimension
  #
  # indexing rules:
  #    negative indice are treated as those from the end of the dimension,
  #    just like in Array class
  #
  # interpreted index specifiers (the 1st argument of ArrayDimIndex.new):
  #    Integer                  one element
  #    Range                    range
  #    true                     select all (same as 0..-1)
  #    Hash({range,step})       range&step (Note:{range,step}=={range=>step})
  #    integer array            direct indices specification (negative values
  #                             are interpreted)
  #    "mask" -- a NumArray that consists of true / false(or nil) 
  #                             its length must be qual to dimsize

  def initialize(idx,dimsize)
    # idx        index specifier
    # dimsize    length of the current dimension of the original array

    ##@stride=stride    # @stride=1; for i in 0..dim-1; @stride *= shape[i];end
    @idxarray=nil     # will be set below if needed

    if idx.is_a?(Integer) then
      @start= (idx<0 ? idx + dimsize : idx)
      @step=1
      @len=1
      if @start<0 || @start>dimsize-1 then raise "idx<0 or idx>=dimsize"; end
    elsif idx.is_a?(Range) then
      f=idx.first; f += dimsize if f<0
      e=idx.last; e -= 1 if idx.exclude_end?; e += dimsize if e<0
      @start=f
      @step=1
      @len=e-f+1
      if @len <= 0 then raise "Invalid indexing: size<=0"; end
      if f<0 || e>dimsize-1 then raise "first<0 or end>=dimsize"; end
    elsif idx == true then
      @start=0
      @step=1
      @len=dimsize
    elsif idx.is_a?(Hash) || idx.length == 1 then
      range,step=idx.to_a[0]
      if range == true; range=0..-1; end
      if ! range.is_a?(Range) || ! step.is_a?(Integer) then
	raise "Inalid Hash as a range&step specifier"
      end
      f=range.first; f += dimsize if f<0
      e=range.last; e -= 1 if range.exclude_end?; e += dimsize if e<0
      @start=f
      @step=step
      @len=((e-f).abs)/step.abs+1
      if @len <= 0 then raise "Invalid indexing: size<=0"; end
      if (e-f)*step < 0 then raise "Invalid range"; end
      if f<0 || f>dimsize-1 || e<0 || e>dimsize-1 then 
	raise "invalid range"
      end
    elsif (idx.is_a?(NumArray) || idx.is_a?(BasicNumArray)) &&idx.logical? then
      if idx.is_a?(NumArray) then
	mask=idx.to_basic
      else
	mask=idx
      end
      if mask.length != dimsize then raise "invalid mask length"; end
      @idxarray=BasicNumArray[]
      for i in 0..mask.length-1
	if mask[i] then
	  @idxarray.push(i)
	end
      end
      @len=@idxarray.length
    elsif idx.is_a?(BasicNumArray) || idx.is_a?(NumArray) || 
	                              idx.is_a?(Array) then
      if idx.is_a?(BasicNumArray) then
	@idxarray=idx.dup
      elsif idx.is_a?(NumArray) 
	@idxarray=idx.to_basic
      else
	@idxarray=BasicNumArray[*idx]
      end
      @idxarray.filter{|i| i < 0 ? i+dimsize : i}
      if @idxarray.min < 0 || @idxarray.max > dimsize-1 then
	raise "has a index outside the range"
      end
      @len=@idxarray.length
    else
      raise "Invalid index specification"
    end
  end

  def length; @len; end
  def indices
    if @idxarray then
      return @idxarray
    else
      return BasicNumArray.span(@len,@start,@step)
    end
  end      

end


if __FILE__ == $0
  require "numarray"

  print "test: Int\n"
  a=ArrayDimIndex.new(-3,10)
  p a.length,a.indices

  print "\ntest: Range\n"
  a=ArrayDimIndex.new(0..-1,10)
  p a.length,a.indices

  print "\ntest: Range with step\n"
  a=ArrayDimIndex.new({true,3},10)
  p a.length,a.indices

  a=ArrayDimIndex.new({9..3,-3},10)
  p a.length,a.indices

  print "\ntest: mask\n"
  mask=BasicNumArray[false,true,false,true,true,false]
  dimlen=mask.length
  a=ArrayDimIndex.new(mask,dimlen)
  p a.length,a.indices

  print "\ntest: direct indices\n"
  indices=BasicNumArray[3,0,-1,-3]
  a=ArrayDimIndex.new(indices,10)
  p a.length,a.indices

end
