;+
; Purpose: Object that provides an efficient means of concatenating arrays
; da= DynamicArray([InitialArray][,name='name1')
; Purpose:  Returns a "dynamic array" object.  This dynamic array can have any number of elements and can be efficiently 
; appended to. 
; 
; This routine is particularly useful when appending to large arrays on numerous occassions.  This is especially useful
; when the final size of the array is not known when first initialized.
; 
; It is functionally equivalent to:
; a= findgen(10)
; b = 1.
; a = [a,b]
; but considerably more effiecient because the array size does not need to be increased at every append operation.
; 
; Because one can produce arrays of objects, it is a conveniant way of constructing arrays of arrays
; containing different things.
; 
; Works with multidimensional arrays too.
; 
; USAGE:
;   da = dynamicarray(findgen(1000000), name='Test1')
;      Or 
;   da.array = findgen(1000000)    ; equivalent
;      Or
;   da = dynamicarray()     &   da.append, findgen(1000000)
;   da.append,  findgen(1000)  ;  append some data
;   da.append, !values.f_nan      ; add a NAN at the end.
;   da.name = 'NewName'   ; change name
;   
;   a = da.array   ; retrieve a copy of the array
;   print,da.size   ; print the number of elements in the array  (first dimension of multidimensional arrays)
;   help,da.name   ; display the optional user name
;   
;   object_destroy, da   ; cleanup when done.
;   
;   Written by Davin Larson - August 2016
;-

pro dynamicarray_example
  t0=systime(1)
  ;  start_array = lindgen(10)   ; execution time is highly dependent on the size of the array that is appended to.
  start_array = !null
  printdat,start_array
  n=2000  ;*2
  block = replicate(1,1000)

  print,'Using dynamic array:'
  da1 = dynamicarray(start_array,name='example1')
  for i=0L,n-1 do  begin
    da1.append,i*block
  endfor
  a = da1.array
  t1 = systime(1)
  dt = t1-t0
  printdat,a,dt
  print,'Appending to end of array ',n,' times takes ',dt,' seconds'
  obj_destroy,da1

  print,'Using standard array concatenation:  time increases as n^2'
  a = start_array
  for i=0L,n-1 do begin
    a = [a,i*block]
  endfor
  t2 = systime(1)
  dt = t2-t1
  printdat,a,dt
  print,'Appending to end of array ',n,' times takes ',dt,' seconds'
end





FUNCTION DynamicArray::Init,array, _EXTRA=ex
COMPILE_OPT IDL2
; Call our superclass Initialization method.
void = self->generic_object::Init(_extra=ex)
self.xfactor = .5
self.ptr_array = ptr_new(!null)
;dim = size(/dimen,array)
;self.size = dim[0]
;self.dlevel = 4
IF (ISA(ex)) THEN self->SetProperty, _EXTRA=ex
self.append,array
dprint,verbose=verbose,dlevel=self.dlevel+2,'Created new '+typename(self)+ ': "'+self.name+'"'
RETURN, 1
END
 
 
PRO DynamicArray::Cleanup
COMPILE_OPT IDL2
; Call our superclass Cleanup method
;  *self.ptr_array = !null   ;; line not needed
dprint,verbose=verbose,dlevel=self.dlevel+2,'Cleanup of '+typename(self)+ ': '+self.name
ptr_free,self.ptr_array
;   self->generic_object::Cleanup  ;  not required???
END

;
;function DynamicArray::printdat_string,full=full
;  COMPILE_OPT IDL2
;  outstr = !null
;  if keyword_set(full) then help,self,/object
;  printdat,self.name,varname='NAME',output=str   &   outstr = [outstr,str]
;  printdat,self.ptr_array,varname='PTR_ARRAY',output=str    &   outstr = [outstr,str]
;  printdat,self.size,varname='SIZE',output=str   &   outstr = [outstr,str]
;  dim = size(/dimen,*self.ptr_array)
;  printdat,dim,output=str   &   outstr = [outstr,str]
;  printdat,typename(*self.ptr_array),varname='TYPE',output=str   &   outstr = [outstr,str]
;  return,outstr
;END



;PRO DynamicArray::help,full=full
;  COMPILE_OPT IDL2
;  if keyword_set(full) then help,self,/object
;  printdat,self.ptr_array,varname='PTR_ARRAY'
;  printdat,self.size,varname='SIZE'
;  dim = size(/dimen,*self.ptr_array)
;  printdat,dim
;  printdat,self.name,varname='NAME'
;  printdat,typename(*self.ptr_array),varname='TYPE'
;END



pro DynamicArray::append, a1, error = error
compile_opt IDL2

error = ''

if 1 then begin  ;  Don't use old routine append_array
  fillnan =1

  if isa(a1,'Undefined')  then begin              ; n_elements(a1) eq 0;;  Warning- this could have unexpected results if a1 is a null pointer or null object
    dprint,verbose=verbose,dlevel=self.dlevel+1,'Appending Null'
    return     ; Quietly do nothing
  endif

  dim1 = size(/dimension,a1)  
  n1 = dim1[0] > 1
  a0 = self.ptr_array

  if n_elements(*a0) eq 0   then begin   ; Initialize if  undefined.     
    *a0 = [a1]
    dim0 = size(/dimension,*a0)
    self.size = dim0[0]
    return
  endif
  
  dim0 = size(/dimension,*a0) 
  n0 = dim0[0] > 1
    
  type0 = size(/type,*a0)
  type1 = size(/type,a1)
  
  
  a0_set = 1
  if a0_set then begin
    if (type1 eq 8) || (type0 eq 8) then begin   ; structures
      if type1 ne type0 then begin
        error = 'Type Mismatch, Unable to concatenate'
      endif else   if n_tags(*a0) ne n_tags(a1) then begin
        error = 'Type Mismatch, Unable to concatenate structures with different tags'
      endif else   if n_tags(/length,*a0) ne n_tags(/length,a1)  then begin
        error = 'Type Mismatch, Unable to concatenate structures with different size'
      endif
    endif

    if (type0 eq 10 || type1 eq 10) && (type1 ne type0) then begin   ; pointers
      error = 'Type Mismatch, Unable to concatenate'
    endif

    if (type0 eq 11 || type1 eq 11) && (type1 ne type0) then begin   ;  objects
      error = 'Type Mismatch, Unable to concatenate'
    endif

    if n_elements(dim0) ne n_elements(dim1) || ((n_elements(dim0) ge 2) &&  array_equal(dim0[1,*],dim1[1,*] eq 0))  then begin
      error = 'Size Mismatch, Unable to concatenate'
    endif
    
    if keyword_set(error) then begin
      dimstr = '['+string(dim0,format="(8(i0.0,:,','))")+']'
      prefix = 'DynamicArray("'+self.name+'"): '+typename(*a0) + dimstr
      dimstr = '['+string(dim1,format="(8(i0.0,:,','))")+']'
      suffix = typename(a1) + dimstr
      error = prefix +": "+ error +" " + suffix      
    endif
  endif


  if keyword_set(error) then begin
    dprint,verbose=verbose,dlevel=self.dlevel,error
    return
  endif
  
  index = self.size

  if n1 + index gt n0 then begin    ;   existing buffer not large enough to insert new values - incease size
;    xfactor = .5
    fill = (*a0)[0]
    if keyword_set(fillnan) then fill =   fill_nan(fill) 
    if n_elements(dim1) ne n_elements(dim0) then begin
      dprint,verbose=verbose,dlevel=self.dlevel,'Incompatible appending'
    endif
    dim = dim0
    add = floor((n0+n1) * self.xfactor+ n1 )
    dim[0] = add
    fillx = replicate(fill,dim)
    *a0 = [*a0,fillx]                       ;  This is the operation that can take a long time to perform
    dprint,verbose=verbose,dlevel=self.dlevel+2,'Enlarging '+self.name+' array by ',add,' elements. New size:', size(/dim,*a0)
    n0=n0+add
  endif


  (*a0)[index:index+n1-1,*,*,*] = a1               ; Insert new values
  self.size  = index + n1

  return
 
endif else begin    ; Usee old version of append_array
  ind =self.size
  append_array,*self.ptr_array,a1,index=ind,error=error
  if keyword_set(error) then begin
    dprint,verbose=verbose,dlevel=self.dlevel,self.name,error
    ;self.typename
  endif
  self.size=ind  
endelse
end


pro DynamicArray::trim    ; Truncate ptr_array to its proper value
compile_opt IDL2
if 1 then begin
  *self.ptr_array = (*self.ptr_array)[0:self.size-1,*]
endif else begin
  ind = self.size
  append_array,*self.ptr_array,index= ind
  self.size = ind  
endelse
end

 
 
 
PRO DynamicArray::GetProperty, array=array, size=size, ptr=ptr, name=name  ,  typestring=typestring
; This method can be called either as a static or instance.
COMPILE_OPT IDL2
IF (ARG_PRESENT(array)) THEN begin
  if self.size eq 0 then array=!null   else  array = (*self.ptr_array)[0:self.size-1,*,*,*]
ENDIF
IF (ARG_PRESENT(size)) THEN size = self.size
IF (ARG_PRESENT(ptr)) THEN ptr = self.ptr_array
IF (ARG_PRESENT(name)) THEN name = self.name
IF (ARG_PRESENT(typestring)) THEN typestring = typename(*self.ptr_array)
END
 
 
 
PRO DynamicArray::SetProperty, array=array, name=name, xfactor=xfactor ;,dlevel=dlevel
COMPILE_OPT IDL2
; If user passed in a property, then set it.
IF (ISA(array) || isa(array,/null)) THEN begin
  dprint,verbose=verbose,dlevel=self.dlevel+2,'Changing array: "'+self.name+'"'
  if 0 then begin   ; This section has been commented out because it was not needed and extraordinarily slow for large arrays
    ptrs = ptr_extract(*self.ptr_array)
    if isa(ptrs) then begin
      dprint,verbose=verbose,'Warning! old pointers NOT freed in old dynamicarray: "'+self.name+'"',dlevel=self.dlevel+1
 ;     ptr_free,ptrs
    endif
  endif
  *self.ptr_array = !null    ; Warning there is possibility of leaving dangling pointers.
  self.size = 0
  self.append, array
ENDIF
if isa(name,/string) then begin
  self.name = name
endif
if isa(xfactor) then self.xfactor = xfactor
;if isa(dlevel) then self.dlevel = dlevel
END
 
 
 
PRO DynamicArray__define
COMPILE_OPT IDL2
void = {DynamicArray, $
;  inherits IDL_Object, $ ; superclass
  inherits generic_object,  $
  name: '',  $     ; optional name
  size: 0L, $     ; user size  (less than or equal to actual size)  (first dimension of multi dimensional arrays)
  ptr_array: ptr_new(), $ ; pointer to array
  xfactor: 0.  $  ;  Fractional increase in size of array
;  dlevel: 0 $     ; controls dprint dlevel for debugging
}
END