; GETTAGS - Helper function: Recursively get the tag names of an IDL struct.
;           gettags, struct, vartyp=vartyp, /expand
;
; REFPTRA - Helper function: Dereference an array of pointers.
;           refptra, arrptr, arrmin
;
; STRCTXT - Print out an IDL structure as an ASCII table
;           strctxt, struct, tagnam, maxwid=40, filnam='output.txt'


;+
; NAME:
;       GETTAGS
;
; PURPOSE:
;       Recursively get the tag names of an IDL struct.
;
; CATEGORY:
;       General.
;
; CALLING SEQUENCE:
;       gettags, struct [, vartyp=vartyp] [, /expand]
;
; REQUIRED INPUTS:
;       struct      an IDL structure
;
; OPTIONAL INPUTS:
;       none
;
; KEYWORDS:
;       expand      recursively expand nested structures
;
; OUTPUTS:
;       result      the tag names of the structure
;       vartyp      type of variable in the respective tag 
;
; DESCRIPTION AND EXAMPLE:
;       This function determines the tag names and variable types for the 
;       elements in an IDL structure. If the keyword EXPAND is set, then tag 
;       names are also determined for nested structures. The program is 
;       called by:
;       result = gettags(struct)
;
; CALLED BY:
;       strctxt.
;
; CALLING:
;       None.
;
; MODIFICATION HISTORY:
;       2009-06-26 Written by Konrad R. Tristram
;

FUNCTION GETTAGS, STRUCT, VARTYP=VARTYP, EXPAND=EXPAND

; GET THE TAG NAMES IN THE STRUCT
;-------------------------------------------------------------------------------
tagnam = tag_names(struct)

; DETERMINE THE VARIABLE TYPES
;-------------------------------------------------------------------------------
vartyp = make_array(n_elements(tagnam), /byte)
for i=0,n_elements(tagnam)-1 do begin
	didrun = execute('vartyp[i] = size(struct.' + tagnam[i] + ', /type)')
	; REFERENCE POINTERS IF PRESENT
	;-----------------------------------------------------------------------
	if (vartyp[i] eq 10) then begin
		didrun = execute('vartyp[i] = size(*struct[0].' + $
		                 tagnam[i] + ', /type)')
	endif
endfor

; EXPAND NESTED STRUCTURES
;-------------------------------------------------------------------------------
idxnum = (where(vartyp eq 8))[0]
while keyword_set(expand) and (idxnum ne -1) do begin
	didrun = execute('tmptag = gettags(struct.' + tagnam[idxnum] + $
	                 ', vartyp=vartmp, expand=expand)')
	tmptag = tagnam[idxnum] + '.' + tmptag
	if idxnum eq 0 then begin
		tagnam = [tmptag, tagnam[idxnum+1:*]]
		vartyp = [vartmp, vartyp[idxnum+1:*]]
	endif else if idxnum ge n_elements(tagnam)-1 then begin
		tagnam = [tagnam[0:idxnum-1], tmptag]
		vartyp = [vartyp[0:idxnum-1], vartmp]
	endif else begin
		tagnam = [tagnam[0:idxnum-1], tmptag, tagnam[idxnum+1:*]]
		vartyp = [vartyp[0:idxnum-1], vartmp, vartyp[idxnum+1:*]]
	endelse
	idxnum = (where(vartyp eq 8))[0]
endwhile

; RETURN THE TAG NAMES AS A RESULT
;-------------------------------------------------------------------------------
return, tagnam

END

;+
; NAME:
;       REFPTRA
;
; PURPOSE:
;       Dereference an array of pointers.
;
; CATEGORY:
;       General.
;
; CALLING SEQUENCE:
;       refptra, arrptr [, arrmin]
;
; REQUIRED INPUTS:
;       arrptr      an array of pointers
;
; OPTIONAL INPUTS:
;       arrmin      number of elements to read from each dereferenced variable
;
; KEYWORDS:
;       none
;
; OUTPUTS:
;       result      an array containing the dereferenced variables
;
; DESCRIPTION AND EXAMPLE:
;       This function automatically dereferences an array of pointers. For this 
;       to work the pointers in the array must all point to the same type of 
;       variable and the pointers must not point to structures. The variable 
;       type for the result is defined by the variable type of the dereference 
;       for the first element in ARRPTR. If the dereferenced variables are 
;       arrays, all arrays are cropped to the shortest one. The program is 
;       called by:
;       result = refptra(arrptr)
;
; CALLED BY:
;       strctxt.
;
; CALLING:
;       None.
;
; MODIFICATION HISTORY:
;       2009-06-26 Written by Konrad R. Tristram
;

FUNCTION REFPTRA, ARRPTR, ARRMIN

; PRINT ERROR MESSAGE AND EXIT IF ARRPTR IS NO ARRAY OF POINTERS
;-------------------------------------------------------------------------------
if size(arrptr, /type) ne 10 then begin
	print, 'Argument must be an array of pointers.'
	return, -1
endif

; GET NUMBER OF ELEMENTS IN ARRPTR
;-------------------------------------------------------------------------------
numele = n_elements(arrptr)

; DETERMINE NUMBER OF ELEMENTS IN THE DEREFERENCED VARIABLES IF NOT PROVIDED
;-------------------------------------------------------------------------------
if not keyword_set(arrmin) then begin
	arrmin = 1d99
	for i=0,numele-1 do arrmin = arrmin < n_elements(*arrptr[i])
endif
arrmin = arrmin > 1

; CREATE THE RESULT ARRAY
;-------------------------------------------------------------------------------
vartyp = size(*arrptr[0], /type)
result = make_array(numele, arrmin, type=vartyp)

; LOOP OVER THE ELEMENTS IN ARRPTR AND COPY THE DATA INTO THE RESULT ARRAY
;-------------------------------------------------------------------------------
for i=0,numele-1 do begin
	tmpvar = *arrptr[i]
	if n_elements(tmpvar) ge arrmin then begin
		result[i,*] = tmpvar[0:arrmin-1]
	endif else begin
		result[i,0:n_elements(tmpvar)-1] = tmpvar
	endelse
endfor

; RETURN THE RESULT
;-------------------------------------------------------------------------------
return, result

END


;+
; NAME:
;       STRCTXT
;
; PURPOSE:
;       Print out an IDL structure as an ASCII table.
;
; CATEGORY:
;       General.
;
; CALLING SEQUENCE:
;       strctxt, struct [, tagnam] [, maxwid=maxwid] [, filnam=filnam]
;
; REQUIRED INPUTS:
;       struct      an IDL structure
;
; OPTIONAL INPUTS:
;       tagnam      the names of the tags to be printed out as columns
;
; KEYWORDS:
;       maxwid      maximum with of a (text) column
;       filnam      file name for output into a text file
;
; OUTPUTS:
;       a table on screen
;
; DESCRIPTION AND EXAMPLE:
;       This procedure prints out the contents of a struct array in form of a
;       table of fixed size, with each column corresponding to one tag in the
;       struct. The size of the column is determined from the data type and
;       the space needed by the largest element in this column. For strings,
;       the width of the column is limited by the keyword MAXWID; its default
;       value is 40. The names of the tags and hence columns have to be
;       specified by the string array TAGNAM. If TAGNAM is not set then all
;       tags of the struct are printed as columns. The program is called by:
;       midi_strc2t, struct
;
; CALLED BY:
;       midi_strc2t.
;
; CALLING:
;       gettags, refptra.
;
; MODIFICATION HISTORY:
;       2009-06-26 Generalised from midi_strc2t by Konrad R. Tristram
;       2011-07-28 K. Tristram: Fixed problem with new lines in header row.
;       2012-08-27 K. Tristram: Added key word FILNAM.
;

PRO STRCTXT, STRUCT, TAGNAM, MAXWID=MAXWID, FILNAM=FILNAM

; DO VARIABLE CHECK AND INITIALISE THE STRING VARIABLES FOR THE PRINT COMMAND
;-------------------------------------------------------------------------------
if ~ keyword_set(maxwid) then maxwid = 40
if size(filnam, /type) eq 7 then begin
	openw, unit, filnam, /get_lun
endif else unit = -1
format = '(I3, " | "'
commnd = 'printf, unit, format=format, i'
header = '(A3, " | "'
seltag = 0

; RECURSIVELY GET THE TAG NAMES AND VARIABLE TYPES IN THE STRUCT
;-------------------------------------------------------------------------------
if n_elements(tagnam) lt 1 then tagnam = gettags(struct, /expand)

; LOOP OVER ALL TAGNAMES TO BE PRINTED
;-------------------------------------------------------------------------------
for i=0,n_elements(tagnam)-1 do begin
	; DETERMINE THE VARIABLE TYPE OF THE CURRENT TAG
	;-----------------------------------------------------------------------
	didrun = execute('vartyp = size(struct[0].' + tagnam[i] + ', /type)')
	if (vartyp eq 10) then begin
		didrun = execute('vartyp = size(*struct[0].' + tagnam[i] + $
		                 ', /type)')
		ptrstr = '*'
	endif else ptrstr = ''
	; CHECK IF THE TAG ACTUALLY EXISTS
	;-----------------------------------------------------------------------
	if didrun and (total(vartyp eq [0,8,10,11]) eq 0) then begin
		; ADD THE TAG TO THE PRINT COMMAND AND THE LIST OF GOOD TAGS
		;---------------------------------------------------------------
		commnd += ', (' + ptrstr + 'struct[i].' + tagnam[i] + ')[0]'
		seltag = [seltag, i]
		; ADD A SPECIFIC FORMAT TO THE FORMAT STRING
		;---------------------------------------------------------------
		if ptrstr eq '*' then begin
			didrun = execute('length = refptra(struct.' + $
			                 tagnam[i] + ', 1)')
		endif else begin
			didrun = execute('length = struct.' + tagnam[i])
		endelse
		; ADD A SPECIFIC FORMAT TO THE FORMAT STRING
		;---------------------------------------------------------------
		case vartyp of
			   1: begin ; BYTE
				length = '3'
				format += ', I' + length + ', " | "'
			      end
			   2: begin ; INTEGER
				length = alog10(max(abs(length)))
				length = strtrim(fix(length)+2 > 5, 1)
				format += ', I' + length + ', " | "'
			      end
			   3: begin ; LONG
				length = alog10(max(abs(length)))
				length = strtrim(fix(length)+2 > 7, 1)
				format += ', I' + length + ', " | "'
			      end
			   4: begin ; FLOAT
				length = max(abs(alog10(abs(length))))
				length = strtrim(fix(length)+5 > 8, 1)
				if fix(length) gt 12 then begin
					length = strtrim(10, 1)
					format += ', E' + length + '.4, " | "'
				endif else begin
					format += ', F' + length + '.3, " | "'
				endelse
			      end
			   5: begin ; DOUBLE
				length = max(abs(alog10(abs(length))))
				length = strtrim(fix(length)+7 > 10, 1)
				if fix(length) gt 12 then begin
					length = strtrim(12, 1)
					format += ', E' + length + '.6, " | "'
				endif else begin
					format += ', F' + length + '.5, " | "'
				endelse
			      end
			   6: begin ; COMPLEX
				length = alog10(max(abs(length)))
				length = strtrim(fix(length)+5 > 8, 1)
				format += ', F' + length + '.3, "+"' + $
				          ', F' + length + '.3, "i | "'
				length = strtrim(2*fix(length)+2, 1)
			      end
			   7: begin
				length = max(strlen(length)) < maxwid > 3
				length = strtrim(length, 1)
				format += ', A-' + length + ', " | "'
			      end
			else: begin
				length = strtrim(max(abs(length)) > 3, 1)
				format += ', A' + length + ', " | "'
			      end
		endcase
		; PRINT THE COLUMN HEADER FOR THIS TAG
		;---------------------------------------------------------------
		header+=', A' + length + ', " | "'
	endif else message, 'Skipping this tag.', /infor, /noname
endfor

; ADD THE END OF THE FORMAT STRING AND START A NEW LINE
;-------------------------------------------------------------------------------
printf, unit, ['NUM', tagnam[seltag[1:*]]], format=header+')'

; ADD THE END OF THE FORMAT STRING AND START A NEW LINE
;-------------------------------------------------------------------------------
format += ')'
printf, unit, ''

; LOOP OVER ALL ELEMENTS IN STRUCT AND EXECUTE THE PRINT COMMAND
;-------------------------------------------------------------------------------
for i=0,n_elements(struct)-1 do didrun = execute(commnd)

; CLOSE THE FILE
;-------------------------------------------------------------------------------
if size(filnam, /type) eq 7 then free_lun, unit

END


