; $Id: ini_file.pro,v 1.3 2005/09/21 13:21:29 Harald Exp $
;
;+
; Read INI-file and determine number of sections and tags.
;
; @file_comments
;
; An INI-file is an ASCII text file containing one or more sections
; (indicated by '[section_name]'. Each section contains one or more tags
; of the form 'tag_name=tag_value.' Lines containing only) whitespace
; characters are ignored.
;
; @param file {in}{required}{type=string} filepath to INI-file
;
; @keyword start {out}{optional}{type=lonarr} section start indices
; @keyword ntags {out}{optional}{type=lonarr} list of section tag counts
;
; @returns string array contaning INI-file content (empty lines removed)
;-

FUNCTION ini_data, file, START=start, NTAGS=ntags
COMPILE_OPT HIDDEN

    line = ''
    data = '[]'

    IF (FILE_TEST(file)) THEN BEGIN
        OPENR, unit, file, /GET_LUN
        WHILE NOT EOF(unit) DO BEGIN
            READF, unit, line
            line = STRTRIM(line, 2)
            IF (line EQ '') THEN $
                CONTINUE

            data = [TEMPORARY(data), line]
        ENDWHILE
        FREE_LUN, unit
    ENDIF

    idx = [WHERE(STRMATCH(data, '\[*\]'), cnt), N_ELEMENTS(data)]
    start = idx[0:cnt-1]
    ntags = idx[1:cnt] - idx[0:cnt-1] - 1

    RETURN, data

END ; ini_data


PRO ini_save, file, data, section
COMPILE_OPT HIDDEN

    ; create section block

    hook = data
    list = TAG_NAMES(data)
    blck = STRARR(N_TAGS(data))

    FOR i = 0L, N_TAGS(data) - 1 DO BEGIN
        type = SIZE(data.(i), /TNAME)
        nval = STRTRIM(N_ELEMENTS(data.(i)), 2)

        SWITCH (type) OF
        'BYTE':
        'INT':
        'LONG':
        'LONG64':
        'UINT':
        'ULONG':
        'ULONG64': BEGIN
            form = 'I0'
            BREAK
        END
        'FLOAT':
        'DOUBLE': BEGIN
            form = 'F0'
            BREAK
        END
        'STRING' : BEGIN
            ; escape charcters ',' and '\'
            data.(i) = Escape(data.(i), '\,', '\')
            form = 'A'
            BREAK
        END
        ELSE : MESSAGE, 'Type ' + type + ' not handled.', /CONTINUE
        ENDSWITCH

        form = '(A,"=",' + nval +'(' + form + ',:,","))'
        blck[i] = STRING(list[i], data.(i), FORMAT=form)
    ENDFOR

    blck = ['[' + STRUPCASE(section) + ']', blck]

    ; read INI file from disk and merge section block

    info = ini_data(file, START=start, NTAGS=ntags)
    expr = '\[' + section + '\]'

    n = N_ELEMENTS(start)
    idx = (WHERE(STRMATCH(info[start], expr, /FOLD_CASE)))[0]

    CASE (idx) OF
     -1  : info = [info, blck]
      0  : info = (n GT 1 ? [blck, info[start[idx+1]:*]] : blck)
    n-1  : info = [info[0:start[idx]-1], blck]
    ELSE : info = [info[0:start[idx]-1], blck, info[start[idx+1]:*]]
    ENDCASE

    ; write INI file to disk (insert space between sections)

    idx = [WHERE(STRMATCH(info, '\[*\]'), cnt), N_ELEMENTS(info)]
    data = info[0:idx[1]-1]
    FOR i = 1, cnt - 1 DO $
        data = [TEMPORARY(data), ' ', info[idx[i]:idx[i+1]-1]]

    OPENW, unit, file, /GET_LUN
    PRINTF, unit, data[1 + (idx[1] EQ 1):*], FORMAT='(A)'
    FREE_LUN, unit

    data = hook

END ; ini_save

PRO ini_setval, data, index, value

    CATCH, error
    IF (error NE 0) THEN BEGIN
        MESSAGE, !ERROR_STATE.MSG, /CONTINUE
        RETURN
    ENDIF

    list = TAG_NAMES(data)
    ndat = N_ELEMENTS(data.(index))
    nval = N_ELEMENTS(value)

    IF (nval NE ndat) THEN BEGIN
        ; MESSAGE, 'Tag ' + list[index] + ': Dimension mismatch.', /INFO
        FOR i = 0, N_TAGS(data) - 1 DO BEGIN
            dval = (i EQ index ? REPLICATE((data.(i))[0], nval) : data.(i))
            IF (N_ELEMENTS(dnew) EQ 0) THEN $
                dnew = CREATE_STRUCT(list[i], dval) $
            ELSE $
                dnew = CREATE_STRUCT(dnew, list[i], dval)
        ENDFOR
        data = dnew
    ENDIF

    data.(index) = value

END ; ini_setval

PRO ini_load, file, data, section
COMPILE_OPT HIDDEN

    ; read INI file from disk and search for section block

    info = ini_data(file, START=start, NTAGS=ntags)
    expr = '\[' + section + '\]'

    n = N_ELEMENTS(start)
    idx = (WHERE(STRMATCH(info[start], expr, /FOLD_CASE)))[0]

    IF (idx EQ -1) THEN $
        MESSAGE, 'Section ' + section + ' not found.'

    IF (ntags[idx] EQ 0) THEN $
        MESSAGE, 'Section ' + section + ' contains no tags.'

    info = info[start[idx]+1:start[idx]+ntags[idx]]
    ;PRINT, info, FORMAT='(A)'

    ; read into data structure

    list = TAG_NAMES(data)

    FOR i = 0L, N_TAGS(data) - 1 DO BEGIN

        idx = (WHERE(STRMATCH(info, list[i]+'=*')))[0]
        IF (idx EQ -1) THEN BEGIN
            MESSAGE, 'Tag ' + list[i] + ' not found.', /INFO
            CONTINUE
        ENDIF

        type = SIZE(data.(i), /TNAME)
        vals = STRMID(info[idx], STRLEN(list[i])+1)
        vals = STRSPLIT(vals, ',', ESCAPE='\', /EXTRACT)
;       escape character already removed by STRSPLIT
;       vals = Escape(vals, '\,', '\', /UNDO)

        CASE (type) OF
        'BYTE'    : ini_setval, data, i, BYTE(FIX(vals))
        'INT'     : ini_setval, data, i, FIX(vals)
        'LONG'    : ini_setval, data, i, LONG(vals)
        'LONG64'  : ini_setval, data, i, LONG64(vals)
        'UINT'    : ini_setval, data, i, UINT(vals)
        'ULONG'   : ini_setval, data, i, ULONG(vals)
        'ULONG64' : ini_setval, data, i, ULONG64(vals)
        'FLOAT'   : ini_setval, data, i, FLOAT(vals)
        'DOUBLE'  : ini_setval, data, i, DOUBLE(vals)
        'STRING'  : ini_setval, data, i, vals
        ELSE : MESSAGE, 'Type ' + type + ' not handled.', /INFO
        ENDCASE
    ENDFOR

END ; ini_load

PRO ini_file, data, section, SAVE=isSave, LOAD=isLoad, ERROR=error, FILE=file

    CATCH, error
    IF (error NE 0) THEN BEGIN
        MESSAGE, !ERROR_STATE.MSG, /CONTINUE
        RETURN
    ENDIF

    IF (N_PARAMS() LT 1) OR (N_PARAMS() GT 2) THEN $
        MESSAGE, 'Usage: ini_file, data [, section] /LOAD|SAVE.', /NONAME

    IF (SIZE(data, /TNAME) NE 'STRUCT') THEN $
        MESSAGE, 'Data parameter requires structure.', /NONAME

    IF (N_ELEMENTS(section) EQ 0) THEN $
        section = ''

    isSave = KEYWORD_SET(isSave)
    isLoad = KEYWORD_SET(isLoad)

    IF ((isSave + isLoad) NE 1) THEN $
        MESSAGE, 'Use either /SAVE or /LOAD.', /NONAME

    IF N_ELEMENTS(file) EQ 0 THEN BEGIN
        path = GETENV('HOME')
        IF (path EQ '') THEN $
            path = GETENV('USERPROFILE')
        IF (path EQ '') THEN BEGIN
            MESSAGE, 'Environment variable HOME|USERPROFILE not found, ' + $
                     'using current directory.', /INFORMATIONAL
            CD, CURRENT=path
        ENDIF
        file = FILEPATH('idl_user.ini', ROOT_DIR=path)
    ENDIF

    IF (isLoad) THEN $
        ini_load, file, data, STRUPCASE(section)

    IF (isSave) THEN $
        ini_save, file, data, STRUPCASE(section)

END ; ini_file