;+
; Project     : HESSI
;
; Name        : ADD_METHOD
;
; Purpose     : Dynamically add a method to a class (and it's objects)
;
; Category    : utility objects
;
; Explanation : Based on method name (e.g. CLONE), looks for a class
;               definition file (e.g. CLONE__DEFINE.PRO). It then makes a 
;               temporary copy of this file, and replaces all instances
;               of CLASS::METHOD with OBJ::METHOD where OBJ is the
;               class name that you want to add METHOD to. It then
;               compiles this temporary file.
;
; Syntax      : IDL> add_method,method,object
;
; Examples    : IDL> add_method,'clone',map_object
;                             or
;               IDL> add_method,'gen::getprop',map_object
;
; Inputs      : METHOD = string method name
;             : OBJECT = object reference (or class name) 
;
; Outputs     : None
;
; Keywords    : ERR = error string
;               VERBOSE = set for output messages
;
; Restrictions: METHOD must have a METHOD__DEFINE.PRO in !path 
;               If user enters method such as, for example, GEN::GETPROP
;               the method name is the part after the ::
;               If :: is not present, then the input is assumed to be a 
;               class and all it's methods are added.
;               Added method(s) will only apply during current IDL session
;
; Side Effects: New method will override existing method of target object
;
; History     : Written: 9 July 2000, D. Zarro (EIT/GSFC)
;               Modified: 24 August 2000, Zarro (EIT/GSFC) - vectorized
;               Modified: 3 Sept 2001, Zarro (EITI/GSFC) - added option
;               to add specific method via: class::method_name
;               Modified: 4 May 2002, Zarro (EER/GSFC) - switched to
;               using CD instead of adding temporary path when using
;               RESOLVE_ROUTINE. IDL 6.0 has a problem with this.
;
; Contact     : dzarro@solar.stanford.edu
;-

pro add_method,method,object,err=err,verbose=verbose,override=override


err=''
verbose=keyword_set(verbose)

;-- input checks

syntax='add_method,method,object'

if size(method,/tname) ne 'STRING' then begin
 err='First input argument must be a string method name'
 message,err,/cont
 pr_syntax,syntax
 return
endif

;--check method input

method=trim(method)
cc_pos=strpos(method,'::')
all_methods=cc_pos eq -1
if all_methods then begin
 method_name=trim(method)
 method_class=trim(method)
endif else begin
 method_name=strmid(method,cc_pos+2,strlen(method))
 method_class=strmid(method,0,cc_pos)
endelse

;-- check for METHOD__DEFINE

method_define=method_class+'__define.pro'

if (not have_proc(method_define,out=method_file)) then begin
 err='Could not locate - '+method_define
 message,err,/cont
 return
endif

np=n_elements(object)
otype=size(object,/tname)
if (otype ne 'STRING') and (otype ne 'OBJREF') then begin
 err='Second input argument must be an object reference or class name'
 message,err,/cont
 return
endif

;-- loop thru each object to determine valid class names

class_names=strarr(np)
if otype eq 'OBJREF' then begin
 for i=0,np-1 do begin
  if obj_valid(object[i]) then class_names[i]=strlowcase(obj_class(object[i]))
 endfor
endif else class_names=strlowcase(trim(object))

chk=valid_class(class_names,index,count=count)
if count eq 0 then return
valid_class_names=get_uniq(class_names[index])

;-- check if method has been added to these class names

np=n_elements(valid_class_names)
if not keyword_set(override) then begin
 for i=0,np-1 do begin
  added=is_method_added(method_name,valid_class_names[i])
  if (not added) then not_added=append_arr(not_added,valid_class_names[i])
 endfor
 if exist(not_added) then valid_class_names=not_added else begin
  if verbose then message,'Method "'+method_name+'" already added to "'+arr2str(valid_class_names)+'" class',/cont
  return
 endelse
 np=n_elements(valid_class_names)
endif


;-- read the method file into memory and replace all function/pro calls
;   as follows:
;
;   METHOD::METHOD_NAME -> METHOD_CLASS::METHOD_NAME.
;
;   However, we don't replace any INIT or CLEANUP methods so as not to 
;   interfere with these methods in the original target object.
;     

marray=rd_ascii(method_file)
if all_methods then begin
 smethod=strlowcase(method_name)
 method_calls=smethod+'::'
endif else begin
 smethod=strlowcase(method_class)
 method_calls=strlowcase(method)
endelse

method_class=strlowcase(method_class)

init_call=method_class+'::init'
;init_call_arg=method_class+'::init '
cleanup_call=method_class+'::cleanup'

tarray=strlowcase(marray)

chk=(strpos(tarray,method_calls) gt -1) and $
    (strpos(tarray,init_call) eq -1) and $
;    (strpos(tarray,init_call_arg) eq -1) and $
    (strpos(tarray,cleanup_call) eq -1)
ok=where(chk,count)

if count eq 0 then begin
 err='Could not locate any '+method_name+' method calls in '+method_file
 message,err,/cont
 return
endif

temp=tarray[ok]

;-- now do the replace 
                   
np=n_elements(valid_class_names)
for i=0,np-1 do begin
 class_name=valid_class_names[i]
 array=marray                  
                                                
 if verbose then message,'Adding "'+method_name+'" method(s) to '+class_name,/cont

 init_call=class_name+'::init'
 cleanup_call=class_name+'::cleanup'

 if all_methods then mtemp='' else mtemp=method_name
 temp=str_replace(temp,method_calls,class_name+'::'+mtemp)

 array[ok]=temp

;--  write to a temporary file 

 temp_dir=get_temp_dir()
 temp_name=method_name+'.pro'
 temp_file=mk_temp_file(temp_name,direct=temp_dir,/random)
 break_file,temp_file,dsk,dir,pro_name

 new_array=append_arr(array,['pro '+pro_name,'end'],/no_copy)

 file_append,temp_file,new_array,/new

;-- compile new added method 

 cd,temp_dir,current=current
 qsave=!quiet
 !quiet=1
 resolve_routine,pro_name,/either
 !quiet=qsave
 cd,current

 error=0
 catch,error
 if error ne 0 then begin
  catch,/cancel
  cd,current
 endif

;-- cleanup

 file_delete,temp_file,/quiet

endfor

;-- compile original method file to get it's methods

obj_compile,method_class,/quiet


return & end