; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: OS independent micro configuration files library.
;
;  Target OS: Any
;
;  Dependencies: strlib.asm; arrays.asm;
;
;  Notes:
;
;_________________________________________________________________________________________
module "uConfig library"

struct TCfgRecord
  .key   dd ?
  .value dd ?
ends

iglobal
  var procCfgMatchPattern = StrMatchPattern ; StrMatchPattern or StrMatchPatternNoCase
endg


proc CfgFreeSection, .ptrRecord
begin
        push    eax
        mov     eax, [.ptrRecord]

        cmp     [eax+TArray.itemsize], 8
        jne     .error

        shl     [eax+TArray.count], 1   ; make
        stdcall ListFree, eax, StrDel
        clc
        pop     eax
        return

.error:
        stc
        pop     eax
        return
endp


;******************************************************************
; Returns CF=0 and eax=pointer to TArray with TCfgRecord elements.
;         CF=1 only if the file does not exists.
;
; Notse:
;   1. if the section does not exists, empty array will be returned.
;   2. The user must free the array after use passing the pointer
;      to CfgFreeSection - it will free the array and the contained
;      strings as well.
;
;******************************************************************

proc CfgGetSection, .cfgFile, .section
begin
        pushad

        mov     eax, [.cfgFile]
        test    eax, $ffff0000
        jz      .file_ok

        stdcall FileOpenAccess, eax, faReadOnly
        jc      .finish

.file_ok:
        mov     ebx, eax
        stdcall FileSeek, ebx, 0, fsFromBegin

        stdcall CreateArray, 8  ; TCfgSectionRecord
        jc      .finish

        mov     edi, eax

        stdcall StrDup, [.section]
        mov     esi, eax
        stdcall StrCharCat, esi, '.*'

.loop:
        stdcall _CfgScanForKey, ebx, esi
        jc      .end_of_file

        push    eax
        push    ecx

        stdcall AddArrayItems, edi, 1
        mov     edi, edx
        mov     edx, eax
        pop     [edx+TCfgRecord.key]
        pop     [edx+TCfgRecord.value]

        stdcall [procCfgMatchPattern], [.section], [edx+TCfgRecord.key]
        jc      .loop   ;???
        inc     eax
        stdcall StrSplit, [edx+TCfgRecord.key], eax
        xchg    [edx+TCfgRecord.key], eax
        stdcall StrDel, eax
        jmp     .loop

.end_of_file:
        stdcall StrDel, esi

        test    [.cfgFile], $ffff0000
        jz      .close_ok

        stdcall FileClose, ebx

.close_ok:
        mov     [esp+4*regEAX], edi
        clc

.finish:
        popad
        return

endp



proc CfgSaveSection, .cfgFile, .section, .ptr_array
begin
        pushad

; first clean the whole section:

        stdcall CfgGetSection, [.cfgFile], [.section]
        jc      .error

        mov     esi, eax

; clean all items:

        mov     ecx, [esi+TArray.count]

.clean:
        dec     ecx
        js      .end_clean

        stdcall CfgSetStr, [.cfgFile], [.section], [esi+TArray.array+8*ecx+TCfgRecord.key], 0
        jmp     .clean

.end_clean:
        mov     esi, [.ptr_array]
        test    esi, esi
        jz      .end_loop

        cmp     [esi+TArray.itemsize], 8        ; two dwords TArray
        jne     .error
        xor     ecx, ecx

.loop:
        cmp     ecx, [esi+TArray.count]
        je      .end_loop

        stdcall CfgSetStr, [.cfgFile], [.section], [esi+8*ecx+TArray.array+TCfgRecord.key], [esi+8*ecx+TArray.array+TCfgRecord.value]
        inc     ecx
        jmp     .loop

.end_loop:
        clc
        popad
        return

.error:
        stc
        popad
        return
endp




;_________________________________________________________________
; proc CfgGetInt
;
; Reads an integer number from the config file.
;
; If the key is missing, the value is not a number or some
; other error occurs, returns [.default] and CF=1
;
; If the value has been found and contains a number
; encoded in FASM format, this number is returned and CF=0
;_________________________________________________________________

proc CfgGetInt, .cfgFile, .section, .key, .default
begin
        stdcall CfgGetStr, [.cfgFile], [.section], [.key]
        jc      .get_default

        push    eax
        stdcall StrToNumEx, eax
        stdcall StrDel ; from the stack
        jc      .get_default

        clc
        return

.get_default:
        mov     eax, [.default]
        return
endp



proc CfgSetInt, .cfgFile, .section, .key, .value
begin
        push    eax
        stdcall NumToStr, [.value], ntsDec or ntsSigned
        push    eax
        stdcall CfgSetStr, [.cfgFile], [.section], [.key], eax
        stdcall StrDel ; from the stack
        pop     eax
        return
endp


;___________________________________________________________________
; proc CfgGetStr
;
; Reads a string value from the config file.
;
; Arguments:
;   .cfgFile - handle of an open file, or string of filename string.
;   .section - string with the section of the value
;   .key     - string with the key of the value.
;
; Returns:
;   CF=0; eax - string with the value of the key.
;   CF=1; eax=0 - The key is missing or something is wrong with the file,
;___________________________________________________________________

proc CfgGetStr, .cfgFile, .section, .key
begin
        push    ebx ecx edx

        mov     eax, [.cfgFile]         ; name or handle
        test    eax, $ffff0000
        jz      .file_ok

        if defined options.DebugMode & options.DebugMode
           pushad
           stdcall StrPtr, eax
           stdcall Output, "Config file path: "
           stdcall Output, eax
           stdcall Output, cCRLF
           popad
        end if

        stdcall FileOpenAccess, eax, faReadOnly
        jc      .error

        OutputValue "CfgGetStr file open success. Handle = ", eax, 16, 8

.file_ok:
        mov     ebx, eax

        stdcall FileSeek, ebx, 0, fsFromBegin

        stdcall StrDup, [.section]
        stdcall StrCharCat, eax, '.'
        stdcall StrCat, eax, [.key]
        push    eax

        stdcall _CfgScanForKey, ebx, eax
        stdcall StrDel ; from the stack
        pushf

        test    [.cfgFile], $ffff0000
        jz      .close_ok

        push    eax
        stdcall FileClose, ebx
        pop     eax

.close_ok:
        popf

.finish:
        pop     edx ecx ebx
        return

.error:
        DebugMsg "CfgGetStr open file error!"
        jmp      .finish

endp



;___________________________________________________________________
; proc CfgSetStr
;
; If the specified key exists in the config file, its value is
; replaced with [.value].
;
; If the key does not exists, it is appended at the end of the file.
;
; If some error occurs - CF=1;
;___________________________________________________________________

proc CfgSetStr, .cfgFile, .section, .key, .value
begin
        push    ebx ecx edx edi

        mov     eax, [.cfgFile]
        test    eax, $ffff0000
        jz      .file_ok

        stdcall FileOpenAccess, eax, faReadWrite + faOpenAlways
        jc      .exit

.file_ok:
        mov     ebx, eax

        stdcall FileSeek, ebx, 0, fsFromBegin

        xor     edi, edi

        stdcall StrDup, [.section]
        stdcall StrCharCat, eax, '.'
        stdcall StrCat, eax, [.key]
        push    eax

        stdcall _CfgScanForKey, ebx, eax
        jnc     .read_remaining

        pop     ecx
        jmp     .append

.read_remaining:
        stdcall StrDel ; from the stack

        stdcall StrNew
        mov     edi, eax

.loop:
        stdcall FileReadLine, ebx
        jc      .end_read
        test    eax, eax
        jz      .end_read

        stdcall StrCat, edi, eax
        stdcall StrCharCat, edi, $0a0d  ;???
        stdcall StrDel, eax
        jmp     .loop

.end_read:
        stdcall FileSeek, ebx, edx, fsFromBegin

.append:
        cmp     [.value], 0
        je      .end_append     ; if [.value]==0 then the key is simply deleted.

        stdcall StrSetQuotes, ecx, '='
        stdcall StrCharCat, ecx, ' = '

        stdcall StrDup, [.value]
        stdcall StrSetQuotes, eax, '='
        stdcall StrCat, ecx, eax
        stdcall StrDel, eax

        stdcall StrCharCat, ecx, $0a0d
        stdcall FileWriteString, ebx, ecx

.end_append:
        test    edi, edi
        jz      .finish

        stdcall FileWriteString, ebx, edi       ; remaining part of the file...
        stdcall StrDel, edi

.finish:
        stdcall FileTruncate, ebx
        stdcall StrDel, ecx

        test    [.cfgFile], $ffff0000
        jz      .close_ok

        stdcall FileClose, ebx

.close_ok:
        clc
.exit:
        pop     edi edx ecx ebx
        return
endp




proc CfgDelKey, .cfgFile, .section, .key
begin
        stdcall CfgSetStr, [.cfgFile], [.section], [.key], 0
        return
endp



;___________________________________________________________________________________
; proc CfgScanForKey
;
; This is "low-level" procedure that searches already open file for
; a key with the given name.
;
; if the line has been found, returns:
;  eax - string handle of the value of the line (the right part of key=value pair).
;  ecx - string handle of the key of the line (the left part of key=value pair).
;  edx - position in the file where the key/value are located.
;  the file is positioned on the start of the next line in the file.
;
; The returned string handle has to be free when not needed.
;___________________________________________________________________________________

proc _CfgScanForKey, .hFile, .fullkey
begin
.loop:
        stdcall FileSeek, [.hFile], 0, fsFromCurrent
        mov     edx, eax ; the current possition.

        stdcall FileReadLine, [.hFile]
        jc      .not_found
        test    eax, eax
        jz      .not_found

        mov     ecx, eax

        stdcall StrClipSpacesR, ecx
        stdcall StrClipSpacesL, ecx

        stdcall StrPtr, ecx
        cmp     byte [eax], ';'
        jne     .not_comment

        stdcall StrDel, ecx
        jmp     .loop

.not_comment:
        stdcall StrSplitList, ecx, '=', TRUE
        stdcall StrDel, ecx

        mov     ecx, eax
        cmp     [ecx+TArray.count], 2
        jne     .next

        stdcall StrClipQuotes, [ecx+TArray.array]

        stdcall [procCfgMatchPattern], [.fullkey], [ecx+TArray.array]
        jc      .found

.next:
        stdcall ListFree, ecx, StrDel
        jmp     .loop

; found, eax is a hangle of the value.
.found:
        push    dword [ecx+TArray.array+4]
        push    dword [ecx+TArray.array]
        stdcall FreeMem, ecx
        pop     ecx
        pop     eax
        stdcall StrClipQuotes, eax
        clc
        return

.not_found:
        xor     eax, eax
        stc
        return
endp







endmodule
