require "numru/gphys/subsetmapping"
require "numru/gphys/attribute"
require "narray_miss"
require "numru/units"
require "numru/gphys/unumeric"
require "rational"    # for VArray#sqrt
require "numru/misc"

module NumRu

=begin
=class NumRu::VArray

VArray is a Virtual Array class, in which a multi-dimensional array data is 
stored on memory (NArray, NArrayMiss) or in file (NetCDFVar etc). 
The in-file data handling is left to subclasses such as VArrayNetCDF,
and this base class handles the following two cases:

(1) Data are stored on memory using NArray
(2) Subset of another VArray (possibly a subclass such as VArrayNetCDF).

Perhaps the latter case needs more explanation. Here, a VArray is defined 
as a subset of another VArray, so the current VArray has only the link
and info on how the subset is mapped to the other VArray.

A VArray behaves just like a NArray, i.e., a numeric multi-dimensional 
array. The important difference is that a VArray has a name and can 
have "attributes" like a NetCDF variable. Therefore, VArray can perfectly
represent a NetCDFVar, which is realized by a sub-class VArrayNetCDF.

NOMENCLATURE

* value(s): The multi-dimensional numeric array contained in the VArray,
  or its contents

* attribute(s): A combination of name and data that describes a VArray.
  Often realized by NumRu::Attribute class (or it is a NetCDFAttr in 
  VArrayNetCDF). The name must be a string, and the type of attribute
  values is restricted to a few classes for compatibility with
  NetCDFAttr (See NumRu::Attribute)

==Class Methods

---VArray.new(narray=nil, attr=nil, name=nil)

    A constructor

    ARGUMENTS
    * narray (NArray or NArrayMiss; default:nil) The array to be held.
      (nil is used to initialize a mapping to another VArray by a protected
      method).
    * attr (NumRu::Attribute; default:nil) Attributes. If nil, an empty 
     attribute object is created and stored.
    * name (String; default nil) Name of the VArray. If nil, it is named "noname".

    RETURN VALUE
    * a VArray

    EXAMPLE

        na = NArray.int(6,3).indgen!
        va1 = VArray.new( na, nil, "test" )

---VArray.new2(ntype, shape, attr=nil, name=nil)

    Another constructor. Uses parameters to initialize a NArray to hold.

    ARGUMENTS
    * ntype (String or NArray constants): Numeric type of the NArray to be
      held (e.g., "sfloat", "float", NArray::SFLOAT, NArray::FLOAT)
    * shape (Array of Integers): shape of the NArray
    * attr (Attribute; default:nil) Attributes. If nil, an empty attribute
      object is created and stored.
    * name (String; default nil) Name of the VArray.

    RETURN VALUE
    * a VArray


==Instance Methods

---val
    Returns the values as a NArray (or NArrayMiss).

    This is the case even when the VArray is a mapping to another. Also,
    this method is to be redefined in subclasses to do the same thing.

    ARGUMENTS -- none

    RETURN VALUE
    * a NArray (or NArrayMiss)

---val=(narray)

    Set values.

    The whole values are set. If you want to set partly, use ((<[]=>)).
    In this method, values are read from narray and set into the internal
    value holder. Thus, for exampled, the numeric type is not changed
    regardress the numeric type of narray. Use ((<replace_val>)) to
    replace entirely with narray.

    ARGUMENTS
    * narray (NArray or NArrayMiss or Numeric): If Numeric, the whole
      values are set to be equal to it. If NArray (or NArrayMiss), its
      shape must agree with the shape of the VArray.

---replace_val(narray)

    Replace the internal array data with the object narray.
    Use ((<val=>)) if you want to copy the values of narray.

    ARGUMENTS
    * narray (NArray or NArrayMiss): as pxlained above.
      Its shape must agree with the shape of the VArray (self).

    RETURN VALUE
    * if (self.class == VArray), self;
      otherwise (if self.class is a subclass of VArray), a new VArray.

---[]
    Get a subset. Its usage is the same as NArray#[]

---[]=
    Set a subset. Its usage is the same as NArray#[]=

---attr
    To be undefined.

---ntype

    Returns the numeric type.

    ARGUMENTS -- none

    RETURN VALUE
    * a String ("byte", "sint", "int", "sfloat", "float", "scomplex"
      "complex", or "obj"). It can be used in NArray.new to initialize
      another NArray.

---rank
    Returns the rank (number of dimensions)

---shape
    Returns the shape

---shape_current
    aliased to ((<shape>)).

---length
    Returns the length of the VArray

---typecode
    Returns the NArray typecode

---name
    Returns the name

    RETURN VALUE
    * (String) name of the VArray

---name=(nm)
    Changes the name.

    ARGUMENTS
    * nm(String): the new name.

    RETURN VALUE
    * nm

---rename!(nm)
    Changes the name (Same as ((<name=>)), but returns self)

    ARGUMENTS
    * nm(String): the new name.

    RETURN VALUE
    * self

---rename(nm)
    Same as rename! but duplicate the VArray object and set its name.

    This method may not be supported in sub-classes, since it is sometimes
    problematic not to change the original.

---copy(to=nil)
    Copy a VArray. If to is nil, works as the deep cloning (duplication of the entire object).

    Both the values and the attributes are copied.

    ARGUMENTS
    * to (VArray (possibly a subclass) or nil): The VArray to which the 
      copying is made.

---reshape!( *shape )
    Changes the shape without changing the total size. May not be available in subclasses.

    ARGUMENTS
    * shape (Array of Integer): new shape

    RETURN VALUE
    * self

    EXAMPLE
       vary = VArray.new2( "float", [10,2])
       vary.reshape!(5,4)   # changes the vary 
       vary.copy.reshape!(2,2,5)  # make a deep clone and change it
             # This always works with a VArray subclass, since vary.copy
             # makes a deep clone to VArray with NArray.

---file
    Returns a file object if the data of the VArray is in a file, nil if it is on memory

    ARGUMENTS
    * none

    RETURN VALUE
    * an object representing the file in which data are stored. Its class
      depends on the file type. nil is returned if the data is not in a file.


---transpose(*dims)
    Transpose. Argument specification is as in NArray.

---reshape(*shape)
    Reshape. Argument specification is as in NArray.

---axis_draw_positive
    Returns the direction to plot the axis (meaningful only if self is a
    coordinate variable.) 
    
    The current implementation is based on NetCDF conventions, 
    so REDEFINE IT IN SUBCLASSES if it is not appropriate.
    
    RETURN VALUE
    * one of the following:
      * true: axis should be drawn in the increasing order (to right/upward)
      * false: axis should be drawn in the decreasing order
      * nil: not specified

---axis_cyclic?
    Returns whether the axis is cyclic (meaningful only if self is a
    coordinate variable.) 
    
    The current implementation is based on NetCDF conventions, 
    so REDEFINE IT IN SUBCLASSES if it is not appropriate.
    
    RETURN VALUE
    * one of the following:
      * true: cyclic
      * false: not cyclic
      * nil: not specified

---axis_modulo
    Returns the modulo of a cyclic axis (meaningful only if self is a
    coordinate variable and it is cyclic.) 
    
    The current implementation is based on NetCDF conventions, 
    so REDEFINE IT IN SUBCLASSES if it is not appropriate.
    
    RETURN VALUE
    * one of the following:
      * Float if the modulo is defined
      * nil if modulo is not found

---axis_cyclic_extendible?
    (meaningful only if self is a coordinate variable.) 
    Returns true if self is cyclic and it is suitable to exend
    cyclically (having the distance between both ends 
    equal to (modulo - dx), where dx is the mean increment).

---coerce(other)
    For Numeric operators. (If you do not know it, see a manual or book of Ruby)

==Methods compatible with NArray

VArray is a numeric multi-dimensional array, so it supports most of the
methods and operators in NArray. Here, the name of those methods are just 
quoted. See the documentation of NArray for their usage.

=== Math functions

====sqrt, exp, log, log10, log2, sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, asinh, acosh, atanh, csc, sec, cot, csch, sech, coth, acsc, asec, acot, acsch, asech, acoth

=== Binary operators

====-, +, *, /, %, **, .add!, .sub!, .mul!, .div!, mod!, >, >=, <, <=, &, |, ^, .eq, .ne, .gt, .ge, .lt, .le, .and, .or, .xor, .not

=== Unary operators

====~ - +

=== Mean etc

====mean, sum, stddev, min, max, median

=== Other methods

These methods returns a NArray (not a VArray).

====all?, any?, none?, where, where2, floor, ceil, round, to_f, to_i, to_a

=end

   class Units
      # for automatic operator generation in VArray
      def mul!(other); self*other; end
      def div!(other); self/other; end
   end    # class Units

   class VArray

      ### < basic parts to be redefined in subclasses > ###

      def initialize(narray=nil, attr=nil, name=nil)
	 # initialize with an actual array --- initialization by subset
	 # mapping is made with VArray.new.initialize_mapping(...)
	 @name = ( name || "noname" )
	 @mapping = nil
	 @varray = nil
	 @ary = __check_ary_class(narray)
	 case attr
	 when Attribute
	    @attr = attr
	 when VArray
	    vary = attr
	    @attr = vary.attr_copy
	 when Hash
	    @attr = NumRu::Attribute.new
	    attr.each{|key,val| @attr[key]=val}
	 when nil
	    @attr = NumRu::Attribute.new
	 else
	   raise TypeError, "#{attr.class} is unsupported for the 2nd arg"
	 end
      end

      attr_reader :mapping, :varray, :ary
      protected :mapping, :varray, :ary

      def inspect
	 if !@mapping
            "<'#{name}' #{ntype}#{shape.inspect} val=[#{(0...(4<length ? 4 : length)).collect do |i| @ary[i].to_s+',' end}#{'...' if 4<length}]>"
	 else
	    "<'#{name}' shape=#{shape.inspect}  subset of a #{@varray.class}>"
	 end
      end

      def VArray.new2(ntype, shape, attr=nil, name=nil)
	 ary = NArray.new(ntype, *shape)
	 VArray.new(ary, attr, name)
      end

      def val
	if @mapping
	  ary = @varray.ary
	  slicer = @mapping.slicer
	  if ary.is_a?(NArray) || ary.is_a?(NArrayMiss)
	    # interpret Hash slicers for NArray/NArrayMiss 
	    #   -- this part would not be needed if NArray supported it.
	    new_slicer = Array.new
	    for idx in 0...slicer.length
	      sl = slicer[idx]
	      if sl.is_a?(Hash)
		range, step = sl.to_a[0]
		dim_len = ary.shape[idx]
		first = range.first
		first += dim_len if first<0
		last = range.last
		last += dim_len if last<0
		if first<0 || first>=dim_len || last<0 || last>=dim_len || step==0
		  raise "slicer #{slicer.inspect} for dim #{idx} is invalid (dim_len=#{dim_len})"
		end
		step = -step if ( (last-first)*step < 0 )
		length = (last-first) / step + 1
		new_slicer[idx] = first + step * NArray.int(length).indgen!
	      else
		new_slicer[idx] = sl
	      end
	    end
	    slicer = new_slicer
	  end
	  ary[*slicer]
	else
	  @ary.dup
	end
      end

      def val=(narray)
	 if @mapping
	    @varray.ary[*@mapping.slicer] = __check_ary_class2(narray)
	 else
	    @ary[] = __check_ary_class2(narray)
	 end
	 narray
      end

      def replace_val(narray)
	narray = __check_ary_class(narray)
	if self.class != VArray
	  raise "replace_val to #{self.class} is disabled. Use val= instead"
	end
	if narray.shape != shape 
	  raise "shape of the argument (#{narray.shape.inspect}) !="+
	    " self.shape (#{shape.inspect})"
	end
	@ary = narray
	if @mapping
	  # to non subset
	  @name = @varray.name
	  @attr = @varray.attr_copy
	  @mapping = @varray = nil
	end
	self
      end

      def ntype
        __ntype(typecode)
      end

      def name=(nm)
	 raise ArgumentError, "name should be a String" if ! nm.is_a?(String)
	 if @mapping
	    @varray.name = nm
	 else
	    @name = nm
	 end
	 nm
      end
      def rename!(nm)
	 self.name=nm
	 self
      end
      def rename(nm)
	self.dup.rename!(nm)
      end

      def reshape!( *shape )
	if @mapping
	  raise "Cannot reshape an VArray mapped to another. Use copy first to make it independent"
	else
	  @ary.reshape!( *shape )
	end
	self
      end

      def file
	if @mapping
	  @varray.file
	else
	  return nil
	end
      end

      ### < basic parts invariant in subclasses > ###

      def copy(to=nil)
	 attr = self.attr_copy( (to ? to.attr : to) )
	 val = self.val
	 if self.class==VArray && !self.mapped? && (to==nil || to.class==VArray)
	    val = val.dup
	 end
	 if to
	    to.val = val
	    to
	 else
	    VArray.new(val, attr, self.name)
	 end
      end

      #def reshape( *shape )
      #	 # reshape method that returns a new entire copy (deep one).
      #	 # ToDo :: prepare another reshape method that does not make the
      #	 # entire copy (you could use NArray.refer, but be careful not 
      #	 # to make it public, because it's easily misused)
      #	 newobj = self.copy
      #	 newobj.ary.reshape!( *shape )
      #	 newobj
      #end

      def mapped?
	 @mapping ? true : false
      end

      def initialize_mapping(mapping, varray)
	 # protected method
	 raise ArgumentError if ! mapping.is_a?(SubsetMapping)
	 raise ArgumentError if ! varray.is_a?(VArray)
	 if ! varray.mapping
	    @mapping = mapping
	    @varray = varray
	 else
	    # To keep the mapping within one step
	    @mapping = varray.mapping.composite(mapping)
	    @varray = varray.varray
	 end
	 @attr = NumRu::Attribute.new
	 @ary = nil
	 self
      end
      protected :initialize_mapping

      def attr
	 if @mapping
	    @varray.attr
	 else
	    @attr
	 end
      end
      protected :attr

      def attr_copy(to=nil)
	attr.copy(to)
      end

      def att_names
	attr.keys
      end
      def get_att(name)
	attr[name]
      end
      def set_att(name, val)
	attr[name]=val
	self
      end
      def del_att(name)
	attr.delete(name)
      end
      alias put_att set_att

      def units
	str_units = attr['units']
	if !str_units || str_units==''
	  str_units = '1'              # represents non-dimension
	end
	Units.new( str_units )
      end

      def units=(units)
	attr['units'] = units.to_s
	units
      end

      def convert_units(to)
	if ! to.is_a?(Units)
	  to = Units.new(to)
	end
	myunits = self.units
	if myunits != to
	  gp = myunits.convert2(self, to)
	  gp.units = to
	  gp
	else
	  self   # returns self (no duplication)
	end
      end

      def long_name
	attr['long_name']
      end

      def long_name=(nm)
	attr['long_name'] = nm
      end

      def [](*slicer)
	 slicer = __rubber_expansion( slicer )
	 mapping = SubsetMapping.new(self.shape_current, slicer)
	 VArray.new.initialize_mapping(mapping, self)
      end

      def []=(*args)
	 val = args.pop
	 slicer = args
	 slicer = __rubber_expansion( slicer )
	 if val.is_a?(VArray)
	    val = val.val
	 else
	    val = __check_ary_class2(val)
	 end
	 if @mapping
	    sl= @mapping.composite(SubsetMapping.new(self.shape,slicer)).slicer
	    @varray[*sl]=val
	 else
	    @ary[*slicer]=val
	 end
	 val
      end

      def name
	 if @mapping
	    @varray.name
	 else
	    @name.dup
	 end
      end

      def transpose(*dims)
	VArray.new( val.transpose(*dims), attr_copy, name )
      end

      def reshape(*shape)
	VArray.new( val.reshape(*shape), attr_copy, name )
      end

      def axis_draw_positive
	# Default setting is taken from a widely used netcdf convention.
	# You can override it in a sub-class or using convention specific 
	# mixins.
	positive = attr['positive']
	case positive
	when /up/i
	  true
	when /down/i
	  false
	else
	  nil    # not defined, or if not 'up' or 'down'
	end  
      end

      def axis_cyclic?
	# Default setting is taken from a widely used netcdf convention.
	# You can override it in a sub-class or using convention specific 
	# mixins.
	topology = attr['topology']
	case topology
	when /circular/i
	  true
	when nil     # 'topology' not defined
	  if /degrees?_east/ =~ attr['units']
	    true     # special treatment a common convention for the earth
	  else
	    nil      # not defined --> nil
	  end
	else
	  false
	end  
      end

      def axis_modulo
	# Default setting is taken from a widely used netcdf convention.
	# You can override it in a sub-class or using convention specific 
	# mixins.
	if attval=attr['modulo']
	  if attval.is_a?(String)
	    attval.to_f
	  else
	    attval[0]
	  end
	elsif /degrees?_east/ =~ attr['units']
          360.0     # special treatment: a common convention for the earth
        elsif (tp = attr['topology']) and (/circular/i =~ tp)
          un = Units[attr['units']]
          if un == Units['degrees']
	    360.0
          elsif un == Units['radian']
            2*Math::PI
          else
            nil    # cannot guess --> nil
          end
        else
          nil      # not defined --> nil
	end
      end

      def axis_cyclic_extendible?
        modulo = axis_modulo
        return false if !modulo
        v = val
        width = (v[-1] - v[0]).abs
	dx = width / (length-1)
	eps = 1e-4
	modulo = modulo.abs
	extendible = ( ((width+dx) - modulo).abs < eps*modulo )
        return extendible
      end

      ### < NArray methods > ###

      ## ToDo: implement units handling
      ## ToDo: coerce

      def coerce(other)
	oattr = self.attr_copy
	case other
	when UNumeric
	  oattr['units'] = other.units.to_s
	  na_other, = NArray.new(self.typecode, 1).coerce(other.val)   # scalar
	  c_other = VArray.new(na_other, oattr, self.name)
	else
	  oattr['units'] = self.get_att('units')     # Assume the same units
	  case other
	  when Numeric
	    na_other, = NArray.new(self.typecode, 1).coerce(other)   # scalar
	    c_other = VArray.new(na_other, oattr, self.name)
	  when Array
	    na = NArray.to_na(other)
	    c_other = VArray.new(na, oattr, self.name)
	  when NArray, NArrayMiss
	    c_other = VArray.new(other, oattr, self.name)
	  else
	    raise "Cannot coerse #{other.class}"
	  end
	end
	[c_other, self]
      end

      Math_funcs_nondim = ["exp","log","log10","log2","sin","cos","tan",
	    "sinh","cosh","tanh","asinh","acosh",
	    "atanh","csc","sec","cot","csch","sech","coth",
	    "acsch","asech","acoth"]
      Math_funcs_radian = ["asin","acos","atan","atan2","acsc","asec","acot"]
      Math_funcs = Math_funcs_nondim + Math_funcs_radian + ["sqrt"]

      Binary_operators_Uop   = ["*","/","**", ".mul!",".div!"]
      Binary_operators_Uconv = ["+","-",".add!",".sbt!"]
      Binary_operators_Unone = ["%",".mod!",".imag="]
      Binary_operators = Binary_operators_Uop + 
	                 Binary_operators_Uconv +
	                 Binary_operators_Unone

      Binary_operatorsL_comp = [">",">=","<","<=",
	                 ".eq",".ne",".gt",".ge",".lt",".le"]
      Binary_operatorsL_other = ["&","|","^",".and",".or",".xor",".not"]
      Binary_operatorsL = Binary_operatorsL_comp +
                          Binary_operatorsL_other

      Unary_operators = ["-@","~"]

      # type1 methods: returns a VArray with the same shape
      # type2 methods: returns the result directly
      NArray_type1_methods = ["sort", "sort_index", 
	    "floor","ceil","round","to_f","to_i","to_type","abs",
	    "real","im","imag","angle","arg","conj","conjugate","cumsum",
	    "indgen","random"]

      NArray_type2_methods1 = ["all?","any?","none?","where","where2",
            "to_a", "to_string"]
      NArray_type2_methods2 = ["rank", "shape", "total","length"]
      NArray_type2_methods3 = ["typecode"]
      NArray_type3_methods = ["mean","sum","stddev","min","max","median"]
      # remaining: "transpose"

      NArray_type2_methods = Array.new.push(*NArray_type2_methods1).
	                         push(*NArray_type2_methods2).
	                         push(*NArray_type2_methods3)

      for f in Math_funcs_nondim
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}(*arg)
            newattr = self.attr_copy
            newattr['units'] = '1'
	    case arg.length
	    when 0
	      VArray.new( Misc::EMath.#{f}(self.val), newattr, self.name )
	    #when 1  # for atan2
	    #  ar = arg[0].respond_to?(:val) ? arg[0].val : arg[0]
	    #  VArray.new( Misc::EMath.#{f}(self.val, ar), newattr, self.name )
	    else
	      raise ArgumentError, "# of args must be 0 or 1"
	    end
	 end
         EOS
      end
      for f in Math_funcs_radian
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}(*arg)
            newattr = self.attr_copy
            newattr['units'] = 'rad'
	    case arg.length
	    when 0
	      VArray.new( Misc::EMath.#{f}(self.val), newattr, self.name )
	    when 1  # for atan2
	      ar = arg[0].respond_to?(:val) ? arg[0].val : arg[0]
	      ## ar = ar.to_f   # NMath.atan2 does not work with NArray.int
	      VArray.new( Misc::EMath.#{f}(self.val, ar), newattr, self.name )
	    else
	      raise ArgumentError, "# of args must be 0 or 1"
	    end
	 end
         EOS
      end
      def sqrt
	va = VArray.new( Misc::EMath.sqrt(self.val), self.attr_copy, self.name )
	va.units = units**Rational(1,2)
	va
      end

      for f in Binary_operators_Uop
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f.delete(".")}(other)
	    case other
            when VArray, UNumeric
	       vl = self.val
	       vr = other.val
	       if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
		 ary = vl#{f}(vr)
	       else
		 ary = NArrayMiss.to_nam(vl)#{f}(vr)
	       end
	       va = VArray.new( ary, self.attr_copy, self.name )
	       va.units= self.units#{f}(other.units)
               va
	    when Numeric, NArray, NArrayMiss, Array
	       vl = self.val
	       vr = other
	       if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
		 ary = vl#{f}(vr)
	       else
		 ary = NArrayMiss.to_nam(vl)#{f}(vr)
	       end
	       va = VArray.new( ary, self.attr_copy, self.name )
	       if "#{f}" == "**" 
		 va.units= self.units#{f}(other)
               end
               va
            else
               c_me, c_other = other.coerce(self)
               c_me#{f}(c_other)
            end
	 end
	 EOS
      end
      for f in Binary_operators_Uconv
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f.delete(".")}(other)
	    case other
            when VArray, UNumeric
               if self.get_att('units')
		 # self have non nil units
		 oval = other.units.convert2(other.val,self.units)
		 uni = self.units
	       else
		 oval = other.val
		 uni = other.units
	       end
	       vl = self.val
	       vr = oval
               if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
                 ary = vl#{f}(vr)
               else
                 ary = NArrayMiss.to_nam(vl)#{f}(vr)
               end
	       va = VArray.new( ary, self.attr_copy, self.name )
	       va.units = uni
	       va
	    when Numeric, NArray, NArrayMiss, Array
	       vl = self.val
	       vr = other
	       if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
		 ary = vl#{f}(vr)
               else
		 ary = NArrayMiss.to_nam(vl)#{f}(vr)
	       end
	       VArray.new( ary, self.attr_copy, self.name )
            else
               c_me, c_other = other.coerce(self)
               c_me#{f}(c_other)
            end
	 end
	 EOS
      end
      for f in Binary_operators_Unone
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f.delete(".")}(other)
	    case other
            when VArray, UNumeric
	       vl = self.val
	       vr = other.val
	       if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
		 ary = vl#{f}(vr)
	       else
		 ary = NArrayMiss.to_nam(vl)#{f}(vr)
	       end
	       VArray.new( ary, self.attr_copy, self.name )
	    when Numeric, NArray, NArrayMiss, Array
	       vl = self.val
	       vr = other
	       if !( vl.is_a?(NArray) and vr.is_a?(NArrayMiss) )
		 ary = vl#{f}(vr)
	       else
		 ary = NArrayMiss.to_nam(vl)#{f}(vr)
	       end
	       VArray.new( ary, self.attr_copy, self.name )
            else
               c_me, c_other = other.coerce(self)
               c_me#{f}(c_other)
            end
	 end
	 EOS
      end
      for f in Binary_operatorsL_comp
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f.delete(".")}(other)
            # returns NArray
	    case other
            when VArray, UNumeric
               self.val#{f}( other.units.convert2(other.val,units) )
	    when Numeric, NArray, NArrayMiss, Array
               self.val#{f}(other)
            else
               c_me, c_other = other.coerce(self)
               self#{f}(other)
            end
	 end
	 EOS
      end
      for f in Binary_operatorsL_other
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f.delete(".")}(other)
            # returns NArray
	    case other
            when VArray, UNumeric
               self.val#{f}(other.val)
	    when Numeric, NArray, NArrayMiss, Array
               self.val#{f}(other)
            else
               c_me, c_other = other.coerce(self)
               self#{f}(other)
            end
	 end
	 EOS
      end
      for f in Unary_operators
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}
            ary = #{f.delete("@")} self.val
	    VArray.new( ary, self.attr_copy, self.name )
	 end
	 EOS
      end
      def +@
	self
      end
      for f in NArray_type1_methods
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}(*args)
            newattr = self.attr_copy
            newattr['units'] = '1' if "#{f}"=="angle" || "#{f}"=="arg"
	    VArray.new(self.val.#{f}(*args), newattr, self.name )
	 end
         EOS
      end
      for f in NArray_type2_methods1
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}(*args)
	    self.val.#{f}(*args)
	 end
	 EOS
      end
      for f in NArray_type2_methods2
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}
            if @mapping
	       @mapping.#{f}
	    else
	       @ary.#{f}
            end
	 end
	 EOS
      end
      for f in NArray_type2_methods3
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}
            if @mapping
	       @varray.ary.#{f}
	    else
	       @ary.#{f}
            end
	 end
	 EOS
      end
      for f in NArray_type3_methods
	 eval <<-EOS, nil, __FILE__, __LINE__+1
	 def #{f}(*args)
            result = self.val.#{f}(*args)
            if result.is_a?(NArray) || result.is_a?(NArrayMiss)
	       VArray.new(result , self.attr_copy, self.name )
            elsif result.nil?
               result
            else
	       UNumeric[result, units]    # used to be 'result' (not UNumeric)
            end
	 end
         EOS
      end

      alias shape_current shape

      ## < marshal dump/load >
      def marshal_dump
        [@name, @mapping, @varray, @ary, @attr]
      end
       
      def marshal_load(ary)
        @name, @mapping, @varray, @ary, @attr = *ary
      end

      ## < private methods >
      private
      def __rubber_expansion( args )
	if (id = args.index(false))  # substitution into id
          # false is incuded
	  alen = args.length
	  if args.rindex(false) != id
	    raise ArguemntError,"only one rubber dimension is permitted"
	  elsif alen > rank+1
	    raise ArgumentError, "too many args"
	  end
	  ar = ( id!=0 ? args[0..id-1] : [] )
	  args = ar + [true]*(rank-alen+1) + args[id+1..-1]
	end
	args
      end
      def __check_ary_class(narray)
	 case narray
	 when NArray, NArrayMiss, nil
	 else
	    raise ArgumentError, "Invalid array class: #{narray.class}" 
	 end
	 narray
      end
      def __check_ary_class2(narray)
	 case narray
	 when NArray, NArrayMiss, nil, Numeric
	 else
	    raise ArgumentError, "Invalid array class: #{narray.class}" 
	 end
	 narray
      end
      def __ntype(typecode)
	 case typecode
	 when NArray::BYTE
	    "byte"
	 when NArray::SINT
	    "sint"
	 when NArray::LINT
	    "int"
	 when NArray::SFLOAT
	    "sfloat"
	 when NArray::DFLOAT
	    "float"
	 when NArray::SCOMPLEX
	    "scomplex"
	 when NArray::DCOMPLEX
	    "complex"
	 when NArray::ROBJ
	    "obj"
	 end
      end

   end    # class VArray

end

##################################
### < test > ###

if $0 == __FILE__
   include NumRu
   p va = VArray.new( NArray.int(6,2,3).indgen!, nil, 'va' )
   va.units="m"
   va.long_name="test data"
   vs = va[2..4,0,0..1]
   p "@@@",vs.rank,vs.shape,vs.total,vs.val,vs.get_att("name")
   p '@@@@',vs.long_name,vs.units.to_s
   vs.val=999
   p "*1*",va
   co,=va.coerce(UNumeric.new(1,Units.new("rad")))
   p '*coerce*',co, co.units
   p "*2*",vt = vs/9, vs + vt
   p "*3*",vt.log10
   p "*4*",(vt < vs)
   vt.name='vvvttt'
   p "*5*",(3+vt), vt.sin, vt.cos
   vc = vt.copy
   p 'atan2'
   vv = VArray.new( NArray.sfloat(5).indgen!, nil, 'vv' )
   p vv.atan2(vv).val
   p "eq",vc.eq(vt),vc.equal?(vt)

   vd = VArray.new( NArray.int(6).indgen!+10 )
   p "+++",vd[1],vd[1].rank,vd[1].val
   p va.val
   p vs
   p va.sort.val
   p vs.to_a, vs.to_string, vs.to_f, vs.to_type(NArray::SINT)
   p "@@@",va.max, va.max(0), va.max(0,1)

   vkg = VArray.new( NArray.float(4,3).indgen!, nil, 'vkg' )
   vkg.units = 'kg'
   vg = vkg.copy
   vg.units = 'g'
   vmul = vkg*vg
   p '##', vkg.val, vmul.get_att('units'), vmul.units
   p '##', (vkg + vg).val, (vg + vkg).val, vkg > vg

   p '*convert_units*'
   p vg.units,vg.val
   vkg = vg.convert_units('kg')
   p vkg.units,vkg.val

   p '*axis conventions*'
   p vx = VArray.new( NArray.int(6).indgen!, nil, 'x' )

   vx.put_att("topology","circular")
   vx.set_att('modulo',[360.0])
   vx.set_att('positive','down')
   p vx.axis_draw_positive, vx.axis_cyclic?, vx.axis_modulo

   p '   cyclic extendible:'
   p vx.axis_cyclic_extendible?
   vx.set_att('modulo',[6.0])
   p vx.axis_cyclic_extendible?

   p '*typecode*'
   p vx.typecode, vx[0..1].typecode
end
