;+
; NAME:
;       NCDF_VARIABLE
;
; PURPOSE:
;
;       The pupose of this NCDF_Variable object is to store information about
;       a netCDF variable. The object is principally used as a utility routine 
;       for the NCDF_FILE object. Given the variable name, the object will acquire 
;       additional information about the variable from the netCDF file containing 
;       the variable.
;
; AUTHOR:
;
;       FANNING SOFTWARE CONSULTING
;       David Fanning, Ph.D.
;       1645 Sheely Drive
;       Fort Collins, CO 80526 USA
;       Phone: 970-221-0438
;       E-mail: david@idlcoyote.com
;       Coyote's Guide to IDL Programming: http://www.idlcoyote.com
;
; CATEGORY:
;
;       File I/O
;
; CALLING SEQUENCE:
;
;       IDL> varObj = Obj_New('NCDF_VARIABLE', varName, parent)
;
; ARGUMENTS:
;
;       varName:   The case sensitive name of a netCDF variable that is stored in the 
;                  netCDF file. (Input and required.)
;
;       parent:    The object reference (NCDF_FILE object) of the netCDF file. In other words, the
;                  object reference of the file that contains this attribute. (Input and required.)
;
; KEYWORD PARAMETERS:
;       
;       None.
;
; METHODS:
;
;     The following methods are available. Each is documented in front of the method.
;
;     varObject -> AddAttr
;     varAttrNames = varObject -> GetAttrNames()
;     dimIDs = varObject -> GetDimIDs()
;     dimNames = varObject -> GetDimNames()
;     varAttrValue = varObject -> GetAttrValue()
;     varID = varObject -> GetID()
;     varName = varObject -> GetName()
;     propertyValue = varObject -> GetProperty(attrProperty)
;     varValue = varObject -> GetValue()
;     varObject -> ParseVariable
;     
;
; MODIFICATION HISTORY:
;       Written by:  David W. Fanning, 3 Feb 2010.
;       Changes to the way dimensions of length 0 are handled. 11 Feb 2010. DWF.
;       Added GetInfo method. 20 Mar 2010. DWF.
;       Added MISSINGINDICES and FILLVALUE keywords to GetValue method to return the indices 
;           and the value of missing data. 20 Mar 2010. DWF.
;       Modified the GetValue method so that if the data returned is scaled and/or offset
;           then the "missing" data value is preserved, although its data type may change.
;           In other words, the "missing" data is not scaled or offset. 20 Mar 2010. DWF.
;       Added output keywords SCALE_FACTOR, ADD_OFFSET, and DATATYPE to the GetValue method
;           so that these values can be returned to the caller at run-time. 29 April 2010. DWF.
;       Modified AddAttr method to allow for additional data types in attributes, specifically
;           INT data type. 3 June 2013. DWF.
;       Had to modify AddAttr method again to allow compatibility with IDL
;           versions below 8.1. 26 July 2013. DWF.
;       Set Default attribute searching to case insensitive via
;       idl-coyote/coyote#13 21 Oct 2017. MHS (mandrakos) 
;
;-
;******************************************************************************************;
;  Copyright (c) 2010, by Fanning Software Consulting, Inc.                                ;
;  All rights reserved.                                                                    ;
;                                                                                          ;
;  Redistribution and use in source and binary forms, with or without                      ;
;  modification, are permitted provided that the following conditions are met:             ;
;                                                                                          ;
;      * Redistributions of source code must retain the above copyright                    ;
;        notice, this list of conditions and the following disclaimer.                     ;
;      * Redistributions in binary form must reproduce the above copyright                 ;
;        notice, this list of conditions and the following disclaimer in the               ;
;        documentation and/or other materials provided with the distribution.              ;
;      * Neither the name of Fanning Software Consulting, Inc. nor the names of its        ;
;        contributors may be used to endorse or promote products derived from this         ;
;        software without specific prior written permission.                               ;
;                                                                                          ;
;  THIS SOFTWARE IS PROVIDED BY FANNING SOFTWARE CONSULTING, INC. ''AS IS'' AND ANY        ;
;  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES    ;
;  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT     ;
;  SHALL FANNING SOFTWARE CONSULTING, INC. BE LIABLE FOR ANY DIRECT, INDIRECT,             ;
;  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED    ;
;  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         ;
;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND             ;
;  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT              ;
;  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS           ;
;  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                            ;
;******************************************************************************************;

;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::AddAttr                                                                   
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Adds a variable attribute to the object.                                  
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    varObject -> AddAttr, attrName, attrValue, DATATYPE=datatype              
;                                                                              
; Auguments:                                                                   
;                                                                              
;    attrName:     A case sensitive name of a variable attribute.              
;    attrValue:    The value of the variable attribute.                        
;                                                                              
; Keywords:                                                                    
;                                                                              
;    DATATYPE:    An input keyword that contains the netCDF data type of       
;                 the variable attribute. If not provided the data type is     
;                 determined from the attribute value.                           
;                                                                              
;------------------------------------------------------------------------------------------;
PRO NCDF_Variable::AddAttr, attrName, attrValue, DATATYPE=datatype

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorlogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN
    ENDIF
    
    ; Check parameters.
    IF N_Elements(attrName) EQ 0 THEN Message, 'The attribute name is required.'
    IF N_Elements(attrValue) EQ 0 THEN Message, 'The attribute value is required.'
   
    ; Set the appropriate netCDF data type keyword.
    IF N_Elements(datatype) EQ 0 THEN datatype = NCDF_CastDataType(attrValue)
    CASE StrUpCase(datatype) OF
        'BYTE': tbyte = 1
        'CHAR': tchar = 1
        'INT': tshort = 1
        'DOUBLE': tdouble = 1
        'FLOAT': tfloat = 1
        'LONG': tlong = 1
        'SHORT': tshort = 1
        'STRING': tstring = 1
        'UBYTE': tubyte = 1
        'UINT64': tuint64 = 1
        'ULONG': tulong = 1
        'UINT':  tushort = 1
        ELSE: Message, 'Unknown DATATYPE for netCDF files: ' + datatype
    ENDCASE
    
    ; The file has to be writable to add an attribute.
    IF ~self.parent -> GetProperty('WRITABLE') THEN $
        Message, 'Cannot add a attribute to a READ-ONLY file.'
    
    ; Put the file into define mode
    self.parent -> SetMode, /DEFINE
    
    ; Check to see if this attribute in _FillValue. If it is,
    ; make sure the data type of the attribute matches the 
    ; data type of the variable, or there can be lots of trouble
    ; later on.
    IF (attrName EQ '_FillValue') THEN BEGIN
        IF self.datatype NE datatype THEN BEGIN
            Message, "The _FillValue attribute's datatype (" + $
                datatype + ") does not match the datatype (" + $
                self.datatype + ") of the variable."
        ENDIF
    ENDIF
    
    ; Add the attribute to the file. Some attributes added only after IDL 7.2.
    fileID = self.parent -> GetFileID()
    IF (Float(!Version.Release) GE 8.1) THEN BEGIN
        NCDF_AttPut, fileID, self.ID, attrName, attrValue, $
            BYTE=tbyte, $
            CHAR=tchar, $
            DOUBLE=tdouble, $
            FLOAT=tfloat, $
            LENGTH=length, $
            LONG=tlong, $
            SHORT=tshort, $
            STRING=tstring, $
            UBYTE=tubyte, $
            UINT64=tuint64, $
            ULONG=tulong, $
            USHORT=tushort
    ENDIF ELSE BEGIN
        NCDF_AttPut, fileID, self.ID, attrName, attrValue, $
            BYTE=tbyte, $
            CHAR=tchar, $
            DOUBLE=tdouble, $
            FLOAT=tfloat, $
            LENGTH=length, $
            LONG=tlong, $
            SHORT=tshort
    ENDELSE
        
    ; Add the attribute to this object's attribute list. Use "self" rather
    ; then the variable name to avoid excess trips through ParseFile from the
    ; HASVAR method.
    attrObj = Obj_New('NCDF_Attribute', attrName, self.parent, VARNAME=self)
    self.attrs -> Add, attrObj

END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetAttrNames                                                              
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns a string array with the names of all the variable's attributes.   
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    attrNames = GetAttrNames(COUNT=attrCount)                                 
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    COUNT:      An output keyword containing the number of attribute names found.           
;                                                                              
;                                                                              
; Return Value:                                                                
;                                                                              
;    attrNames:  A string array with the names of all the variable's attributes.           
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetAttrNames, COUNT=attrCount

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF

    ; Count the number of attribute objects.
    attrCount = self.attrs -> Count()
    
    ; If there are no attributes, return null string.
    IF attrCount EQ 0 THEN RETURN, ""
    
    attrNames = StrArr(attrCount)
    FOR j=0,attrCount-1 DO BEGIN
        thisObj = self.attrs -> Get(POSITION=j)
        attrNames[j] = thisObj -> GetName()
    ENDFOR
    
    ; Return a scalar if necessary.
    IF N_Elements(attrNames) EQ 1 THEN attrNames = attrNames[0]
    
    RETURN, attrNames

END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetAttrValue                                                              
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the value of a variable attribute.                                
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    attrValue = GetAttrValue(attrName, DATATYPE=datatype)                     
;                                                                              
; Arguments:                                                                   
;                                                                              
;    attrName:     A case name of a variable attribute. Case sensitivity
;                  determined by self.caseSensitiveAttributes
;                                                                              
; Keywords:                                                                    
;                                                                              
;    DATATYPE:    An ouput keyword that contains the netCDF data type of       
;                 the variable attribute.                                        
;                                                                              
; Return Value:                                                                
;                                                                              
;    attrValue:  The value of the variable's attribute.                        
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetAttrValue, attrName, DATATYPE=datatype

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF
    
    IF N_Elements(attrName) EQ 0 THEN Message, 'An attribute name or object reference is required.'

    ; Were you passed the name of a attribute or an attribute object?
    CASE Size(attrName, /TNAME) OF
    
        'STRING': BEGIN
            attrObj = self.attrs -> FindByName(attrName, COUNT=attrcount,  CASE_SENSITIVE = self.caseSensitiveAttributes)
            IF attrcount EQ 0 THEN Message, 'Cannot find an attribute object with name ' + attrName + '.'
            IF ~Obj_Valid(attrObj) THEN Message, 'Invalid object with name "' + attrName + '" has been found.'
            END
            
         'OBJREF': BEGIN
            attrObj = attrName
            END
    
        ELSE: Message, 'Input attribute name or object is the wrong data type.'
    ENDCASE

    ; Get the data.
    attrData = attrObj -> GetValue()
    datatype = attrObj -> GetProperty('DATATYPE')
    
    RETURN, attrData
    
END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetDimIDs                                                                 
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the dimension IDs of the dimensions associated with this image.   
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    dimIDs = GetDimIDs(COUNT=dimCount)                                        
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    COUNT:     An ouput keyword that contains number of dimension IDs returned.           ;  
;                                                                              
; Return Value:                                                                
;                                                                              
;    dimIDs:    A long arry of dimension identifiers.                          
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetDimIDs, COUNT=dimCount

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF
    
    ; Get the dimension IDs.
    IF Ptr_Valid(self.dims) THEN BEGIN
        dimIDs = *self.dims 
        dimCount = self.ndims
    ENDIF ELSE BEGIN
        dimIDs = -1
        dimCount = 0
    ENDELSE
    
    RETURN, dimIDs

END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetDimNames                                                               
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the dimension names of the dimensions associated with this image. 
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    dimNames = GetDimNames(COUNT=dimCount)                                    
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    COUNT:     An ouput keyword that contains number of dimension names returned.          
;                                                                              
; Return Value:                                                                
;                                                                              
;    dimNames:    A string array containing the dimension names.               
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetDimNames, COUNT=dimCount

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF
    
    ; Get the dimension names.
    IF Ptr_Valid(self.dimNames) THEN BEGIN
        dimNames = *self.dimNames
        dimCount = self.ndims
     ENDIF ELSE BEGIN
        dimNames = ""
        dimCount = 0
    ENDELSE
    
    RETURN, dimNames

END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetDimSizes                                                               
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the dimension sizes of the dimensions associated with this image. 
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    dimSizes = GetDimSizes(COUNT=dimCount)                                    
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    COUNT:     An ouput keyword that contains number of dimension sizes returned.           
;                                                                              
; Return Value:                                                                
;                                                                              
;    dimSizes:    A long array of dimension sizes.                             
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetDimSizes, COUNT=dimCount

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF
    
    ; Get the dimension sizes.
    IF Ptr_Valid(self.dimensions) THEN BEGIN
        dimSizes = *self.dimensions
        dimCount = N_Elements(dimSizes)
    ENDIF ELSE BEGIN
        dimSizes = 0
        dimCount = 0
    ENDELSE
    
    RETURN, dimSizes

END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetID                                                                     
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the netCDF variable ID.                                           
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    varID = GetID()                                                           
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    None.                                                                       
;                                                                              
; Return Value:                                                                
;                                                                              
;    varID:    A long integer containing the netCDF variable ID.               
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetID
    RETURN, self.ID
END


;------------------------------------------------------------------------------------------;
;                                                                           
; NAME:                                                                     
;    NCDF_Variable::GetInfo                                                                
;                                                                           
; Purpose:                                                                  
;                                                                           
;    Returns information about the variable from the file.                               
;                                                                           
; Method Syntax:                                                            
;                                                                           
;    info = obj -> GetInfo()       
;                                                                           
; Auguments:                                                                
;                                                                           
;    None.   
;                                                                           
; Keywords:                                                                 
;                                                                           
;    None.
;         
; Return Value:                                                             
;                                                                           
;    info:       A structure contains the following fields.
;    
;                    info = { dims: varObj -> GetDimSizes(), $
;                             dimNames: varObj -> GetDimNames(), $
;                             attrNames: varObj -> GetAttrNames(), $
;                             dataType: varObj -> GetProperty('datatype'), $
;                             nattrs: varObj -> GetProperty('nattrs'), $
;                             ndims: varObj -> GetProperty('ndims') }
;                            
;                In addition, the structure will contain the fields "scale_factor," "add_offset,"
;                and "_FillValue" if these attributes are available for the variable.
;                                                                           
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetInfo

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF
    

    ; Collect and return information about the variable.
    info = { dims: self -> GetDimSizes(), $
             dimNames: self -> GetDimNames(), $
             attrNames: self -> GetAttrNames(), $
             dataType: self -> GetProperty('datatype'), $
             nattrs: self -> GetProperty('nattrs'), $
             ndims: self -> GetProperty('ndims') }
             
    ; Add additional information to the info structure, if it is available.
    ; This will include scale_factor, add_offset, and _FillValue attribute values.
    attrNames = self -> GetAttrNames()
    attrIndex = Where(attrNames EQ 'scale_factor', count)
    IF count GT 0 THEN info = Create_Struct(info, 'scale_factor', $
        self -> GetAttrValue('scale_factor'))
    attrIndex = Where(attrNames EQ 'add_offset', count)
    IF count GT 0 THEN info = Create_Struct(info, 'add_offset', $
        self -> GetAttrValue('add_offset'))
    attrIndex = Where(attrNames EQ 'missing_value', count)
    IF count GT 0 THEN info = Create_Struct(info, '_FillValue', $
        self -> GetAttrValue('missing_value'))
    attrIndex = Where(attrNames EQ '_FillValue', count)
    IF count GT 0 THEN info = Create_Struct(info, '_FillValue', $
        self -> GetAttrValue('_FillValue'))
    
    RETURN, info
    
END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetName                                                                   
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the variable name.                                                
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    varName = GetName()                                                       
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    None.                                                                       
;                                                                              
; Return Value:                                                                
;                                                                              
;    varName:    A string variable containing the variable name.               
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetName
    RETURN, self.name
END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetProperty                                                               
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns various properties of the object one at a time. This is a shorthand and       
;    generic way to get the value of an object's "properties", which are defined as        
;    the IDL variables in the object's class structure.                        
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    propertyValue = obj -> GetProperty(thisProperty)                          
;                                                                              
; Auguments:                                                                   
;                                                                              
;    thisProperty:   A string variable that is equivalent to a field in the object's       
;                    class structure. See the *__DEFINE procedure for which properties     
;                    can be returned. The property is case insensitive.        
;                                                                              
; Keywords:                                                                    
;                                                                              
;    None.                                                                     
;                                                                              
; Return Value:                                                                
;                                                                              
;    propertyValue:  The value of a particular object property. Note that pointer          
;                    properties will return the variable the pointer points to.
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetProperty, thisProperty

    Catch, theError
    IF theError NE 0 THEN BEGIN
       Catch, /CANCEL

        ; Log the error.
        (self.parent -> GetProperty('ERRORLOGGER')) -> AddError

        ; No success finding the property requested.
        RETURN, 0
    ENDIF
    
    ; Get the self structure as a structure, rather than as an object.
    Call_Procedure, StrLowCase(Obj_Class(self)) + '__define', classStruct

    ; Find the property in this class structure.
    index = Where(StrPos(Tag_Names(classStruct), StrUpCase(thisProperty)) EQ 0, count)
    index = index[0]
    
    ; What happened?
    CASE count OF
        0: Message, 'Property ' + StrUpCase(thisProperty) + ' could not be found.'
        1: propertyValue = self.(index)
        ELSE: Message, 'Ambiguous property. Use more characters to specify it.'
    ENDCASE

    ; If this is a pointer, you want the thing pointed to.
    IF Size(propertyValue, /TNAME) EQ 'POINTER' THEN propertyValue = *propertyValue
    RETURN, propertyValue
    
END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::GetValue                                                                  
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Returns the the value of the variable. That is, the variable's data.      
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    varValue = GetValue(COUNT=count, OFFSET=offset, STRIDE=stride, MISSING=missing)            
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Input Keywords:                                                                    
;                                                                              
;    COUNT:      An optional vector containing the counts to be used in reading the        
;                variable. Count is a 1-based vector with an element for each dimension.    
;                The default matches the size of the variable so that all data is          
;                written out.                                                    
;    OFFSET:     An optional vector containing the starting position for the read.         
;                The default start position is [0, 0, ...].      
;    STRIDE:     An optional vector containing the strides, or sampling intervals,         
;                between accessed values of the netCDF variable. The default stride        
;                vector is that for a contiguous read, [1, 1, ...].              
;                                                                              
; Output Keywords:   
;
;    ADD_OFFSET:  The add_offset value for the variable, if there is one.
;
;    DATATYPE:    The data type of the variable, before the scale and offset are applied.
;                 The same as what comes back from datatype = Size(rawVariable, /TNAME).
;
;    FILLVALUE:   The value that is being used for the "missing" value in this variable.
;                                                                              
;    MISSINGINDICES: A vector containing the missing indices in the returned data. Missing
;                 data is identified by either the depreciated "missing_value" attribute
;                 or the approved "_FillValue" attribute.  
;       
;    SCALE_FACTOR: The scale factor for the variable, if there is one.
; 
; Return Value:                                                                
;                                                                              
;    varValue:    The actual data of the variable. If scale_factor and add_offset          
;                 attributes are present, the data is scaled and offset before it          
;                 is returned to the caller. The "missing" data (see the MISSING 
;                 keyword) value is not changed, although its data type might change
;                 in the scaling and offset process.                                  
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::GetValue, $
    ADD_OFFSET=add_offset, $
    COUNT=count, $
    DATATYPE=datatype, $
    FILLVALUE=missingvalue, $
    OFFSET=offset, $
    SCALE_FACTOR=scale_factor, $
    STRIDE=stride, $
    MISSINGINDICES=missingIndices

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        errorLogger -> AddError
        RETURN, ""
    ENDIF

    ; Read the data for this variable.
    NCDF_VarGet, self.parent->GetFileID(), self.id, data, COUNT=count, OFFSET=offset, STRIDE=stride
    dataType = Size(data, /TNAME)
    
    ; Does this variable contain "missing" values. If so, identify and return
    ; the missing data indices so they can be identified after scaling.
    missingCount = 0
    IF self -> HasAttr('missing_value') THEN BEGIN
        missingValue = self -> GetAttrValue('missing_value')
        missingIndices = Where(data EQ missingValue, missingCount)
    ENDIF
    IF self -> HasAttr('_FillValue') THEN BEGIN
        missingValue = self -> GetAttrValue('_FillValue')
        missingIndices = Where(data EQ missingValue, missingCount)
    ENDIF
    
    ; If there are scale_factor and add_offset attributes defined for this variable, the
    ; data will be scaled and offset before it is returned. Take care to preserve missing
    ; values in the data. (Data type may change!) 
    IF (self -> HasAttr('scale_factor') OR self -> HasAttr('add_offset')) THEN BEGIN
        IF self -> HasAttr('scale_factor') THEN scale_factor = self -> GetAttrValue('scale_factor') ELSE scale_factor = 1B
        IF self -> HasAttr('add_offset') THEN add_offset = self -> GetAttrValue('add_offset') ELSE add_offset = 0B
        data = (data * scale_factor) + add_offset
        IF missingCount GT 0 THEN data[missingIndices] = missingValue
    ENDIF
    
    ; Is this CHAR data? If so, convert it back to a string.
    IF self.datatype EQ 'CHAR' THEN data = String(Temporary(data))
    
    ; Return the data.
    RETURN, data
    
END 


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::HasAttr                                                                   
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Indicates, by returning a 1, that this particular variable attribute is found in the  ;
;    file. If not found, this function returns a 0.                                                                      
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    isFound = obj -> HasAttr(attrName, OBJECT=object)                         
;                                                                              
; Auguments:                                                                   
;                                                                              
;    attrName:     The case sensitive name of a variable attribute.            
;                                                                              
; Keywords:                                                                    
;                                                                              
;    OBJECT:        If the attribute exists, this keyword returns the attribute's          
;                   object reference.                                          
;                                                                              
; Return Value:                                                                
;                                                                              
;    isFound:       If a variable attribute with this name is found, this variable         
;                   is set to 1. It is set to 0 otherwise.                     
;                                                                              
;------------------------------------------------------------------------------------------;
FUNCTION NCDF_Variable::HasAttr, attrName, OBJECT=object

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling.
    Catch, theError
    IF theError NE 0 THEN BEGIN
        Catch, /CANCEL
        errorLogger = self.parent -> GetProperty('ErrorLogger')
        self.errorLogger -> AddError
        RETURN, ""
    ENDIF

    ; Can you find an attribute object with this name?
    object = self.attrs -> FindByName(attrName, COUNT=count)
    
    IF count GT 0 THEN RETURN, 1 ELSE RETURN, 0
    
END


;------------------------------------------------------------------------------------------;
;                                                                              
; NAME:                                                                         
;    NCDF_Variable::ParseVariable                                                             
;                                                                              
; Purpose:                                                                     
;                                                                              
;    Gathers information about the variable from the netCDF file.              
;                                                                              
; Method Syntax:                                                               
;                                                                              
;    dimObject -> ParseVariable                                                
;                                                                              
; Auguments:                                                                   
;                                                                              
;    None.                                                                     
;                                                                              
; Keywords:                                                                    
;                                                                              
;    None.                                                                       
;                                                                              
;------------------------------------------------------------------------------------------;
PRO NCDF_Variable::ParseVariable

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    On_Error, 2

    ; Gather information about the variable.
    fileID = self.parent -> GetFileID()
    self.id = NCDF_VarID(fileID, self.name)
    varinfo = NCDF_VarInq(fileID, self.id)
    self.datatype = StrUpCase(varinfo.datatype)
    self.ndims = varinfo.ndims
    self.nattrs = varinfo.natts
    self.dims = Ptr_New(varinfo.dim)
    
    ; Get the actual dimensions of the variable.
    IF self.ndims GT 0 THEN BEGIN
        dimensions = LonArr(self.ndims)
        dimNames = StrArr(self.ndims)
        FOR j=0,self.ndims-1 DO BEGIN
            NCDF_DIMINQ, fileID, (*self.dims)[j], dimName, dimSize
            dimensions[j] = dimSize
            dimNames[j] = dimName
        ENDFOR
    ENDIF ELSE BEGIN
        dimensions = 0
        dimNames = ""
    ENDELSE
    self.dimensions = Ptr_New(dimensions)
    self.dimNames = Ptr_New(dimNames)
        
    ; If you have variable attributes, parse these, too.
    self.attrs -> Remove, /ALL
    FOR j=0,self.nattrs-1 DO BEGIN
        attrName = NCDF_AttName(fileID, self.id, j)
        attrObj = Obj_New('NCDF_Attribute', attrName, self.parent, VARNAME=self)
        self.attrs -> Add, attrObj
    ENDFOR
    
    
END    


PRO NCDF_Variable::CLEANUP

    Obj_Destroy, self.attrs
    Ptr_Free, self.dims
    Ptr_Free, self.dimensions
    Ptr_Free, self.dimNames

END



FUNCTION NCDF_Variable::INIT, varName, parent,  caseSensitivity = caseSensitivity

    ; Compiler options.
    Compile_Opt DEFINT32
    Compile_Opt STRICTARR
    Compile_Opt STRICTARRSUBS
    Compile_Opt LOGICAL_PREDICATE

    ; Error handling. Return 0 if can't finish.
    CATCH, theError
    IF theError NE 0 THEN BEGIN
       CATCH, /CANCEL
       errorLogger = parent -> GetProperty('ERRORLOGGER')
       errorLogger -> AddError
       RETURN, 0
    ENDIF

    ; Check parameters.
    IF N_Elements(varName) EQ 0 THEN Message, 'A variable name is required'
    IF Size(varName, /TNAME) NE 'STRING' THEN Message, 'The variable name must be a string.'
    self.name = varName
    IF N_Elements(parent) EQ 0 THEN Message, 'A parent object is required.'
    IF Size(parent, /TNAME) NE 'OBJREF' THEN Message, 'The parent parameter must be an object reference.'
    self.parent = parent
    self.attrs = Obj_New( 'NCDF_Container' )
    
    if keyword_set( caseSensitivity ) then self.caseSensitiveAttributes = 1
    ; Obtain information about the variable and fill out the variable object.
    self -> ParseVariable
    
    RETURN, 1
    
END

PRO NCDF_Variable__DEFINE, class
    
    class = { NCDF_VARIABLE, $
              name: "", $                 ; The variable name.
              ID: 0L, $                   ; The variable ID.
              caseSensitiveAttributes: 0, $ ; Should variable attribute searches be case sensitive 
              dimensions: Ptr_New(), $    ; The actual dimensions of the variable.
              dimNames: Ptr_New(), $      ; A vector of dimension names.
              dims: Ptr_New(), $          ; The dimension IDs of the dimensions
              nattrs: 0L, $               ; The number of attributes the variable has.
              ndims: 0L, $                ; The number of dimensions the variable has.
              datatype: "",  $            ; The netCDF data byte of this variable.
              parent: Obj_New(), $        ; The NCDF_FILE object where this variable resides.
              attrs: Obj_New() $          ; The variable attributes (objects).
            }
            
END
