; docformat = 'rst'

;+
; Abstract base class for a hash-based object system, including a
; generalized getproperty function and getproperty procedure, and
; a setproperty procedure. Requires IDL 8 or above.
;
; The object inherits idl_object, which allows it to get and set
; properties within objects using a nice . notation. For example,
; one can write o.property=6 to create a property and set its
; value or change the value of the existing property to 6.
; x=o.property will retrieve a value for property.
;
; :History:
;   (2013-May-16) JTM: first working test version.
;   (2013-Jun-27) HPW: renamed to nrl_object__define.pro
;   (2014-Aug-01) JTM: fixed error in getproperty function
;   (2015-Dec-17) IUU: return sorted list in get_tags
;-

;+
; Base class which just instantiates the hash. A class that
; has nothing it wants to do in its init can leave out that
; function and this one should be found.
;-
function nrl_object::init
    compile_opt idl2

    self.d = hash()

    return, obj_valid(self.d)

end

;+
; Not much to clean up. Also, since we are by definition in IDL 8
; or above, automatic garbage collection should be taking place.
;-
pro nrl_object::cleanup
    compile_opt idl2

    if obj_valid(self.d) then obj_destroy, self.d

end

;+
; Generalized set procedure.
;
; :Keywords:
;   _extra:
;     Keyword/value pair(s) to set
;-
pro nrl_object::setproperty, _extra=extra
    compile_opt idl2

    d = self.d
    if (~n_elements(extra)) then return
    n = n_elements((tags=tag_names(extra)))

    for i=0, n-1 do begin
        d[tags[i]] = extra.(i)
    endfor

end

;+
; Generalized get function.
;
;   :Returns: A value for one property only. Probabaly not
;   needed, since the getproperty procedure below can function in
;   more or less the same way. This one would allow for a
;   different error checking strategy, though.
;
;   :Keywords:
;     _extra:
;       Keyword for variable to return. Watch out for case issues.
;-
function nrl_object::getproperty, _extra=extra
    compile_opt idl2, logical_predicate

    d = self.d
    if n_elements(extra) gt 1 then begin
        message, "Can only return one property."
        return, !null
    endif

    tag = tag_names(extra)
    if d.haskey(tag) then begin
        value = d[tag[0]]
    endif else begin
        message,' requested tag not found: '+tag_names(extra),/info
        value = !null
    endelse

    return, value
end


;+
; Generalized getproperty procedure.
;
;   Keywords in call are updated with property, any number.
;
;   :Keywords:
;     _extra:
;       Keywords for properties to return. Watch out for case issues.
;-
pro nrl_object::getproperty, _ref_extra=extra
    compile_opt idl2, logical_predicate

    d = self.d
    if (~n_elements(extra)) then return

    for i=0, n_elements(extra)-1 do begin
        if d.haskey(extra[i]) then begin
            (scope_varfetch(extra[i], /ref_extra)) = d[extra[i]]
        endif else begin
            (scope_varfetch(extra[i], /ref_extra)) = !null
        endelse
    endfor

end

;+
; Remove a tag in the object, use o.removeProperty,'varname'
;
;-
pro nrl_object::removeproperty,input_var
  compile_opt idl2, logical_predicate

  if (~n_elements(input_var)) then return
  var = strupcase(input_var)

  d = self.d
  for i=0,n_elements(var)-1 do begin
    if d.haskey(var[i]) then begin
      d.remove,var[i]
    endif
  endfor

end

;+
; List the tags in the object.
;
;  :Returns: A string array of the keys in the hash.
;-
function nrl_object::get_tags
    compile_opt idl2

    d = self.d
    tags = d.keys()

    return, tags.sort(/overwrite)

end


;+
; A quick and dirty way to debug things. Allows the user to look
; into the self structure. Be sure to .continue afterwards.
;-
pro nrl_object::debug
    stop

end


;+
; Base class contains only one item--the hash. Inherits
; idl_object so that getproperty and setproperty procedures
; work as advertised.
;
;  d:
;    An object that will become the hash.
;-
pro nrl_object__define
    compile_opt idl2

    void = {nrl_object, inherits idl_object, $
            d: obj_new() $
            }

end
