; docformat = 'rst'

;+
;
; Class to hold and access the data and properties in a parsed
; database.
;
; Updated versions of the NASA Ames PAH IR Spectroscopic Database and
; more information can be found at: `www.astrochemistry.org/pahdb <https://www.astrochemistry.org/pahdb>`.
;
; :Examples:
;   Creating, setting and printing Species info::
;
;     IDL> species = OBJ_NEW('AmesPAHdbIDLSuite_Species')
;     IDL> species->Set,data
;     IDL> species->Print
;     IDL> OBJ_DESTROY,info
;
; :Uses:
;    AmesPAHdbIDLSuite_Species,AmesPAHdbIDLSuite_Transitions,AmesPAHdbIDLSuite_Laboratory_Spectrum,AmesPAHdbIDLSuite_Geometry
;
; :Author:
;   Dr. Christiaan Boersma
;
; :Copyright:
;   BSD licensed
;
; :History:
;   Changes::
;
;     11-17-2023
;     Add GETUIDS. Christiaan Boersma.
;     05-08-2019
;     In the absence of Michael Galloy's STR_REPLACE function
;     FormatFormulae will print a message and return. Christiaan
;     Boersma.
;     10-17-2017
;     Fixed units definition in TRANSITIONS.
;     Christiaan Boersma.
;     02-01-2015
;     First version of the file. Christiaan Boersma.
;
;-

;+
; Make the formulae look pretty by embedding formatting commands.
;
; :Uses:
;   STR_REPLACE by Michael Galloy
;
; :Categories:
;   MARKUP
;-
PRO AmesPAHdbIDLSuite_Species::FormatFormulae

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF FILE_WHICH("str_replace.pro", /INCLUDE_CURRENT_DIR) EQ "" THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"          FORMATFORMULAE REQUIRES STR_REPLACE            "
     PRINT,"========================================================="
     PRINT
     RETURN
  ENDIF

  FOR i = 0, self.nuids - 1 DO BEGIN

     (*self.data)[i].formula = STR_REPLACE((*self.data)[i].formula, "([A-Z])([0-9]+)", "$1!L$2!N", /GLOBAL)

     (*self.data)[i].formula = STR_REPLACE((*self.data)[i].formula, "((\+)+|(\+[0-9])|(-)+|(-[0-9]))", "!U$1!N")
  ENDFOR
END

;+
;  Output species description.
;
;  :Params:
;    UID: in, optional, type=long
;      UID of the species
;
;   :Keywords:
;     Str: out, optional, type="string array"
;       Ouput to Str
;
; :Categories:
;   INFORMATIVE
;-
PRO AmesPAHdbIDLSuite_Species::Print,UID,Str=Str

  COMPILE_OPT IDL2

  ON_ERROR,2

  nselect1 = self.nuids

  IF N_PARAMS() EQ 0 THEN select1 = ULINDGEN(nselect1) $
  ELSE select1 = WHERE((*self.data).uid EQ UID, nselect1)

  IF nselect1 EQ 0 THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"                     UID NOT FOUND                       "
     PRINT,"========================================================="
     PRINT
     RETURN
  ENDIF

  FOR i = 0, nselect1 - 1 DO BEGIN

     tags = TAG_NAMES((*self.data)[select1[i]])

     ntags = N_TAGS((*self.data)[select1[i]])

     IF KEYWORD_SET(Str) THEN BEGIN

        FOR j = 0, ntags - 1 DO Str = [Str, STRING(FORMAT='(A-10,":",4X,A-0)',tags[j],STRTRIM((*self.data)[select1[i]].(j), 2))]

        IF SIZE(*self.comments, /TNAME) EQ 'STRUCT' THEN BEGIN

           select2 = WHERE((*self.comments).uid EQ (*self.data)[select1[i]].uid, nselect2)

           FOR j = 0, nselect2 - 1 DO Str = [Str, STRING(FORMAT='(A-10,":",4X,A-0)',"COMMENT",STRTRIM((*self.comments)[select2].str, 2))]
        ENDIF

        IF SIZE(*self.references, /TNAME) EQ 'STRUCT' THEN BEGIN

           select2 = WHERE((*self.references).uid EQ (*self.data)[select1[i]].uid, nselect2)

           FOR j = 0, nselect2 - 1 DO Str = [Str, STRING(FORMAT='(A-10,":",4X,A-0)',"REFERENCE",STRTRIM((*self.references)[select2].str, 2))]
        ENDIF
     ENDIF ELSE BEGIN

        PRINT
        PRINT,"========================================================="

        FOR j = 0, ntags - 1 DO PRINT,FORMAT='(A-10,":",4X,A-0)',tags[j],STRTRIM(STRING((*self.data)[select1[i]].(j)), 2)

        IF SIZE(*self.comments, /TNAME) EQ 'STRUCT' THEN BEGIN

           select2 = WHERE((*self.comments).uid EQ (*self.data)[select1[i]].uid, nselect2)

           FOR j = 0, nselect2 - 1 DO PRINT,FORMAT='(A-10,":",4X,A-0)',"COMMENT",STRTRIM((*self.comments)[select2].str, 2)
        ENDIF

        IF SIZE(*self.references, /TNAME) EQ 'STRUCT' THEN BEGIN

           select2 = WHERE((*self.references).uid EQ (*self.data)[select1[i]].uid, nselect2)

           FOR j = 0, nselect2 - 1 DO PRINT,FORMAT='(A-10,":",4X,A-0)',"REFERENCE",STRTRIM((*self.references)[select2].str, 2)
        ENDIF

        PRINT,"========================================================="
        PRINT
     ENDELSE
  ENDFOR

  IF KEYWORD_SET(Str) THEN Str = Str[1:*]
END

;+
; Retrieves species comments.
;
; :Returns:
;   AmesPAHdb_Comment_S array
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::Comments

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,*self.comments
END

;+
; Retrieves species references.
;
; :Returns:
;   AmesPAHdb_Reference_S array
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::References

  COMPILE_OPT IDL2

  ON_ERROR,2

  RETURN,*self.references
END

;+
; Retrieves species transitions.
;
; :Returns:
;   AmesPAHdbIDLSuite_Transitions-instance
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::Transitions

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self.database) THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"              VALID DATABASE POINTER NEEDED              "
     PRINT,"========================================================="
     PRINT
     self.state = 0
     RETURN, OBJ_NEW()
  ENDIF

  RETURN,OBJ_NEW('AmesPAHdbIDLSuite_Transitions', $
                 Type=(*self.database).type ,$
                 Version=(*self.database).version, $
                 Data=self->GetTagByUID('transitions', *self.uids, Count), $
                 PAHdb=self.database, $
                 Uids=*self.uids, $
                 Model={type:'AMESPAHDBIDLSUITE_MODEL_ZEROKELVIN_S',$
                        Temperature:0D, $
                        description:STRING(FORMAT='(A-11,":",X,A-0)', "model", "ZeroKelvin")}, $
                 Units={AmesPAHdb_Data_Units_S, $
                 abscissa:{AmesPAHdb_Unit_S, $
                    unit:1, $
                    str:'frequency [cm!U-1!N]'}, $
                 ordinate:{AmesPAHdb_Unit_S, $
                    unit:2, $
                    str:'integrated cross-section [km/mol]'}})
END

;+
; Retrieves species geometry.
;
; :Returns:
;   AmesPAHdbIDLSuite_Geometry-instance
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::Geometry

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self.database) THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"              VALID DATABASE POINTER NEEDED              "
     PRINT,"========================================================="
     PRINT
     self.state = 0
     RETURN, OBJ_NEW()
  ENDIF

  RETURN,OBJ_NEW('AmesPAHdbIDLSuite_Geometry', $
                 Data=self->GetTagByUID('geometries', *self.uids, Count), $
                 Uids=*self.uids)
END


;+
; Retrieves species laboratory spectra.
;
; :Returns:
;   AmesPAHdbIDLSuite_Laboratory_Spectrum-instance
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::Laboratory

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self.database) THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"              VALID DATABASE POINTER NEEDED              "
     PRINT,"========================================================="
     PRINT
     self.state = 0
     RETURN, OBJ_NEW()
  ENDIF

  IF (*self.database).type NE 'experimental' THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"            EXPERIMENTAL DATABASE REQUIRED               "
     PRINT,"========================================================="
     PRINT
     RETURN, OBJ_NEW()
  ENDIF

  RETURN,OBJ_NEW('AmesPAHdbIDLSuite_Laboratory_Spectrum', $
                 Type=(*self.database).type ,$
                 Version=(*self.database).version, $
                 Data=self->GetTagByUID('laboratory', *self.uids, Count), $
                 PAHdb=self.database, $
                 Model={type:'AMESPAHDBIDLSUITE_RAW_LABORATORY_S',$
                        Temperature:0D, $
                        description:STRING(FORMAT='(A-11,":",X,A-0)', "model", "Laboratory")}, $
                 Uids=*self.uids, $
                 Units={AmesPAHdb_Data_Units_S, $
                        x:{AmesPAHdb_Unit_S, $
                           unit:1, $
                           str:'frequency [cm!U-1!N]'}, $
                        y:{AmesPAHdb_Unit_S, $
                           unit:1, $
                           str:'absorbance [-log(I/I!L0!N)]'}})
END

;+
; Updates Species to the Intersection with UIDs
;
; :Params:
;   UIDs: in, required, type="long or long array"
;     UIDs to consider for Difference
;   Count: out, optional, type=long
;     Number of UIDs
;
; :Categories:
;   SET OPERATIONS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_Species::Intersect,UIDs,Count

  COMPILE_OPT IDL2

  ON_ERROR,2

  nuids = N_ELEMENTS(UIDS)

  ndata = N_ELEMENTS(*self.data)

  idx = ULINDGEN(ndata, nuids)

  select = WHERE((*self.data)[idx MOD ndata].uid EQ UIDS[idx / ndata], nselect) MOD ndata

  IF nselect EQ 0 THEN BEGIN
     PRINT
     PRINT,"========================================================="
     PRINT,"                 NO INTERSECTION FOUND                   "
     PRINT,"========================================================="
     PRINT
     self.state = 0
     RETURN
  ENDIF

  *self.data = (*self.data)[select]

  *self.uids = (*self.data)[UNIQ((*self.data).uid, SORT((*self.data).uid))].uid

  self.nuids = N_ELEMENTS(*self.uids)

  Count = self.nuids

  PRINT
  PRINT,"========================================================="
  PRINT,"           INTERSECTION FOUND: "+STRING(FORMAT='(I-0)', Count)
  PRINT,"========================================================="
  PRINT

  ndata = N_ELEMENTS(*self.references)

  select = WHERE((*self.references)[idx MOD ndata].uid EQ UIDS[idx / ndata], nselect) MOD ndata

  IF nselect GT 0 THEN *self.references = (*self.references)[select]

  ndata = N_ELEMENTS(*self.comments)

  select = WHERE((*self.comments)[idx MOD ndata].uid EQ UIDS[idx / ndata], nselect) MOD ndata

  IF nselect GT 0 THEN *self.comments = (*self.comments)[select]

END

;+
; Returns the data on associated with the given Tag
;
; :Returns:
;  struct (variable)
;
; :Params:
;   Tag: in, required, type=string
;    Name of the Tag
;   UIDs: in, required, type="long array (1D)"
;     UIDs to consider; -1 for all
;   Count: out, optional, type=long
;     Number of species found
;
; :Categories:
;   RETRIEVAL
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_Species::GetTagByUID,Tag,UIDs,Count

  COMPILE_OPT IDL2

  ON_ERROR,2

  Count = 0

  itag = WHERE(STRLOWCASE(TAG_NAMES((*self.database).data)) EQ Tag, ntag)

  IF ntag NE 1 THEN RETURN,-1

  nuids = N_ELEMENTS(UIDs)

  IF SIZE(UIDs, /DIMENSIONS) EQ 0 AND UIDs EQ -1 THEN BEGIN

     Count = N_ELEMENTS((*self.database).data.(itag))

     UIDs = (*self.database).data.(itag)[UNIQ((*self.database).data.(itag).uid, SORT((*self.database).data.(itag).uid))].uid

     RETURN,(*self.database).data.(itag)
  ENDIF

  nitag = N_ELEMENTS((*self.database).data.(itag))

  idx = ULINDGEN(nitag, nuids)

  select = WHERE((*self.database).data.(itag)[idx MOD nitag].uid EQ UIDs[idx / nitag], Count) MOD nitag

  IF Count EQ 0 THEN RETURN,-1

  UIDs = (*self.database).data.(itag)[select[UNIQ((*self.database).data.(itag)[select].uid, SORT((*self.database).data.(itag)[select].uid))]].uid

  RETURN,(*self.database).data.(itag)[select]
END

;+
; Retrieves the Species' UIDs held.
;
; :Returns:
;   long
;
; :Params:
;   Count: out, optional, type=long
;     Number of UIDs
;
; :Categories:
;   SET/GET
;
; :Private:
;-
FUNCTION AmesPAHdbIDLSuite_Species::GetUIDs,Count

  COMPILE_OPT IDL2

  ON_ERROR,2

  Count = self.nuids

  RETURN,*self.uids
END

;+
; Retrieves the AmesPAHdbIDLSuite_Species representation in a
; structure.
;
; :Returns:
;   Structure
;
; :Categories:
;   SET/GET
;-
FUNCTION AmesPAHdbIDLSuite_Species::Get

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF NOT PTR_VALID(self.data) THEN RETURN, 0

  RETURN,{type:OBJ_CLASS(self)+'_S', $
          database:self.type, $
          version:self.version, $
          data:*self.data, $
          uids:*self.uids, $
          nuids:self.nuids, $
          references:PTR_VALID(self.references) ? *self.references : PTR_NEW(), $
          comments:PTR_VALID(self.comments) ? *self.comments : PTR_NEW()}
END

;+
; Populates the AmesPAHdbIDLSuite_Species-instance.
;
; :Params:
;   Struct: in, optional, type=struct
;     Data structure
;
; :Keywords:
;   Type: in, optional, type=string
;     Type of Data
;   Version: in, optional, type=string
;    Versioning information
;   Data: in, optional, type=struct
;     Data structure
;   PAHdb: in, optional, type=pointer
;     Pointer to parsed database file
;   Uids: in, optional, type="long array (1D)"
;     UIDs in Data
;   References: in, optional, type="AmesPAHdb_Reference_S array"
;     References
;   Comments: in, optional, type="AmesPAHdb_Comment_S array"
;     Comments
;
; :Categories:
;   SET/GET
;-
PRO AmesPAHdbIDLSuite_Species::Set,Struct,Type=Type,Version=Version,Data=Data,PAHdb=PAHdb,Uids=Uids,References=References,Comments=Comments

  COMPILE_OPT IDL2, HIDDEN

  ON_ERROR,2

  IF N_PARAMS() GT 0 THEN BEGIN

     tag = WHERE(TAG_NAMES(Struct) EQ 'TYPE', ntype)

     IF ntype EQ 1 THEN BEGIN

        IF Struct.(tag) EQ OBJ_CLASS(self)+'_S' THEN BEGIN

           IF NOT KEYWORD_SET(Type) THEN self.type = Struct.database

           IF NOT KEYWORD_SET(Version) THEN self.version = Struct.version

           IF NOT KEYWORD_SET(Data) THEN BEGIN

              IF PTR_VALID(self.data) THEN PTR_FREE,self.data

              self.data = PTR_NEW(Struct.data)
           ENDIF

           IF NOT KEYWORD_SET(Uids) THEN BEGIN

              IF PTR_VALID(self.uids) THEN PTR_FREE,self.uids

              self.uids = PTR_NEW(Struct.uids)

              self.nuids = N_ELEMENTS(*self.uids)
           ENDIF

           IF NOT KEYWORD_SET(References) THEN BEGIN

              IF PTR_VALID(self.references) THEN PTR_FREE,self.references

              self.references = PTR_NEW(Struct.references)
           ENDIF

           IF NOT KEYWORD_SET(Comments) THEN BEGIN

              IF PTR_VALID(self.comments) THEN PTR_FREE,self.comments

              self.comments = PTR_NEW(Struct.comments)
           ENDIF
        ENDIF
     ENDIF
  ENDIF

  IF KEYWORD_SET(Type) THEN self.type = Type

  IF KEYWORD_SET(Version) THEN self.version = Version

  IF KEYWORD_SET(Data) THEN BEGIN

     IF PTR_VALID(self.data) THEN PTR_FREE,self.data

     self.data = PTR_NEW(Data)
  ENDIF

  IF KEYWORD_SET(PAHdb) THEN self.database = PAHdb

  IF KEYWORD_SET(Uids) THEN BEGIN

     IF PTR_VALID(self.uids) THEN PTR_FREE,self.uids

     self.uids = PTR_NEW(Uids)

     self.nuids = N_ELEMENTS(*self.uids)
  ENDIF

  IF KEYWORD_SET(References) THEN BEGIN

     IF PTR_VALID(self.references) THEN PTR_FREE,self.references

     self.references = PTR_NEW(References)
  ENDIF

  IF KEYWORD_SET(Comments) THEN BEGIN

     IF PTR_VALID(self.comments) THEN PTR_FREE,self.comments

     self.comments = PTR_NEW(Comments)
  ENDIF

  IF PTR_VALID(self.database) THEN BEGIN

     IF STRCMP((*self.database).type, self.type) EQ 0 THEN BEGIN
        PRINT
        PRINT,"========================================================="
        PRINT,"             DATABASE MISMATCH: "+(*self.database).type+" != "+self.type
        PRINT,"========================================================="
        PRINT
        self.state = 0
        RETURN
     ENDIF

     IF STRCMP((*self.database).version, self.version) EQ 0 THEN BEGIN
        PRINT
        PRINT,"========================================================="
        PRINT,"              VERSION MISMATCH: "+(*self.database).version+" != "+self.version
        PRINT,"========================================================="
        PRINT
        self.state = 0
        RETURN
     ENDIF
  ENDIF

  self.state = 1
END

;+
; Clean-up an AmesPAHdbIDLSuite_Species-instance
;
; :Categories:
;   CLASS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_Species::Cleanup

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF PTR_VALID(self.data) THEN PTR_FREE,self.data

  IF PTR_VALID(self.uids) THEN PTR_FREE,self.uids

  IF PTR_VALID(self.references) THEN PTR_FREE,self.references

  IF PTR_VALID(self.comments) THEN PTR_FREE,self.comments
END

;+
; Create an AmesPAHdbIDLSuite_Species-instance
;
; :Returns:
;   AmesPAHdbIDLSuite_Species-instance
;
; :Params:
;   Struct: in, optional, type=struct
;     Data structure
;
; :Keywords:
;   Type: in, optional, type=string
;     Type of Data
;   Version: in, optional, type=string
;    Versioning information
;   Data: in, optional, type=struct
;     Data structure
;   PAHdb: in, optional, type=pointer
;     Pointer to parsed database file
;   Uids: in, optional, type="long array (1D)"
;     UIDs in Data
;   References: in, optional, type="AmesPAHdb_Reference_S array"
;     References
;   Comments: in, optional, type="AmesPAHdb_Comment_S array"
;     Comments
;
; :Categories:
;   CLASS
;-
FUNCTION AmesPAHdbIDLSuite_Species::Init,Struct,Type=Type,Version=Version,Data=Data,PAHdb=PAHdb,Uids=Uids,References=References,Comments=Comments

  COMPILE_OPT IDL2

  ON_ERROR,2

  IF N_PARAMS() GT 0 THEN self->Set,Struct,Type=Type,Version=Version,Data=Data,PAHdb=PAHdb,Uids=Uids,References=References,Comments=Comments $
  ELSE self->Set,Type=Type,Version=Version,Data=Data,PAHdb=PAHdb,Uids=Uids,References=References,Comments=Comments

 RETURN, self.state
END

;+
; Defines the AmesPAHdbIDLSuite_Species Class
;
; :Fields:
;   state: type=int
;     Internal state
;   type: type=string
;     Type of Data
;   version: type=sting
;     Versioning information
;   data: type=pointer
;     Data pointer
;   database: type=pointer
;     Database pointer
;   uids: type=pointer
;     Pointer to the UIDs of Data
;   nuids: type=long
;     Number of UIDs
;   references: type=pointer
;     Pointer to the references
;   comments: type=pointer
;     Pointer to the comments
;
; :Categories:
;   CLASS
;
; :Private:
;-
PRO AmesPAHdbIDLSuite_Species__DEFINE

  COMPILE_OPT IDL2

  ON_ERROR,2

  void = {AmesPAHdbIDLSuite_Species, $
          state:0L, $
          type:'', $
          version:'', $
          data:PTR_NEW(), $
          database:PTR_NEW(), $
          uids:PTR_NEW(), $
          nuids:0L, $
          references:PTR_NEW(), $
          comments:PTR_NEW()}
END

; END OF amespahdbidlsuite_species__define.pro
