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


; Reads a record from a config file as an integer value.
; Returns:
;   CF=1 if the record was not found or when the record contains no number.
;     eax = -1; the record was not found.
;     eax = 0; the record exists, but contains no valid number.
;   CF=0; eax=number if the record was found and contains a number.
proc GetConfigInt, .hConfig, .dir, .key
begin
        mov     eax, -1
        stdcall GetConfigRecord, [.hConfig], [.dir], [.key]
        jc      .finish
        push    eax
        stdcall StrToNumEx, eax
        stdcall StrDel ; from the stack
        clc
.finish:
        return
endp


; Writes a record with the given 32bit integer number
; Returns:
;   Nothing
proc SetConfigInt, .hConfig, .dir, .key, .int
begin
        stdcall NumToStr, [.int], ntsDec
        push    eax
        stdcall SetConfigRecord, [.hConfig], [.dir], [.key], eax
        stdcall StrDel ;from the stack
        return
endp


; Search for the given parameter in the database and return its value.
;
; arguments:
;   .hConfig - handle of a string with the config database.
;   .path    - pointer or handle to the string with full path to the database element.
;
; returns:
; CF=0; eax - string handle with the value of the record. if eax==0 the value is NULL.
; CF=1; the record does not exists.
;
; GetConfigRecord.__info.codesize

proc GetConfigRecord, .hConfig, .dir, .key
.path rb 1024
begin
        pushad

        lea     edi, [.path]
        mov     ebx, [.dir]
        mov     ecx, [.key]
        call    __prepare_path

        stdcall StrPtr, [.hConfig]
        mov     esi, eax
        lea     edx, [.path]

        call    __SearchData
        jc      .exit

        mov     dword [esp+4*regEAX], 0

        mov     ecx, esi

.end_loop:
        lodsb
        cmp     al, ' '
        jae     .end_loop

        dec     esi
        cmp     esi, ecx
        je      .exit_ok        ; NULL
        sub     esi, ecx

        stdcall StrExtract, ecx, 0, esi
        mov     [esp+4*regEAX], eax

.exit_ok:
        clc

.exit:
        popad
        return
endp




; SetConfigRecord.__info.codesize
; Set the value of the given record. If the record does not exists, creates it
; with all parent directories if needed.
; Arguments:
;   .hConfig - handle of the string with the database.
;   .path    - full path to the record.
;   .hData   - pointer or handle to the string with the value for the record.
;              if == 0, cdtNULL will be written.
; Returns:
;   Nothing
proc SetConfigRecord, .hConfig, .dir, .key, .hData
.path rb 1024
begin
        pushad

        lea     edi, [.path]
        mov     ebx, [.dir]
        mov     ecx, [.key]
        call    __prepare_path

        stdcall StrPtr, [.hConfig]
        mov     esi, eax
        push    eax

        lea     edx, [.path]

        call    __SearchData
        pop     edi
        jnc     .record_found

; The record with this path is not found, so it need to be created.

; search back the new line.
.back_loop:
        cmp     esi, edi
        je      .create_here

        dec     esi
        mov     cl, [esi]
        test    cl, cl
        jz      .create_here
        cmp     cl, ' '
        ja      .back_loop

        inc     esi

.create_here:
        sub     esi, edi          ; position inside the string.
        push    esi               ; store the insert position in the stack.

        stdcall StrNew
        mov     edi, eax
        dec     edx

        test    cl, cl
        jnz     .scan_path

        stdcall StrCharCat, edi, $0a0d

.scan_path:
        inc     edx

        inc     ebx     ; the next level of indent
        jz      .next_indent
        inc     ebx
.next_indent:

        mov     esi, edx          ; the remainder of the path

.key_loop:
        lodsb
        cmp     al, ':'
        je      .record
        test    al, al
        jnz     .key_loop

; record found
.record:
        dec     esi
        sub     esi, edx

        mov     ecx, ebx
        jecxz   .indent_ok

.indent_loop:
        stdcall StrCharCat, edi, ' '
        dec     ecx
        jnz     .indent_loop

.indent_ok:                      ; add the subdirectory/record name. ECX==0 here
        xchg    cl, [edx+esi]
        stdcall StrCat, edi, edx
        mov     [edx+esi], cl

        lea     edx, [edx+esi]

        mov     eax, '='
        jecxz   .suffix_ok       ; cl contains the separator char
        mov     eax, ':'+$0a0d00
.suffix_ok:

        stdcall StrCharCat, edi, eax
        test    ecx, ecx
        jnz     .scan_path

        cmp     [.hData], 0
        je      .ins_ready

        stdcall StrCat, edi, [.hData]

.ins_ready:
        stdcall StrCharCat, edi, $0a0d
        mov     [.hData], edi
        pop     edx             ; position where to insert the content.
        xor     esi, esi        ; there is no old record.
        mov     [.dir], esi    ; the [.hData] must be freed after use.
        jmp     .insert_data


; The record with this path was found, so, the old value have to be deleted and
; new one to be recorded.

.record_found:

; search the bounds of the record...
        mov     edx, esi
        sub     edx, edi        ; position of the record start.

.end_loop:
        lodsb
        cmp     al, ' '
        jae     .end_loop

        dec     esi
        sub     esi, edi
        sub     esi, edx        ; length of the record

; here, edx == position, where the value should be inserted.
;       esi == length of the old record value, that have to be deleted.

.insert_data:

        stdcall StrSplit,[.hConfig], edx

        test    esi, esi
        jz      .record_ok      ; no need to delete the old record.

        push    eax
        stdcall StrSplit, eax, esi
        stdcall StrDel ; from the stack

.record_ok:
        mov     edx, eax        ; the part of the string after the record.

        cmp     [.hData], 0
        je      .restore

        stdcall StrCat, [.hConfig], [.hData]

.restore:
        stdcall StrCat, [.hConfig], edx

        stdcall StrDel, eax
        stdcall StrDel, edx

        cmp     [.dir], 0
        jne     .exit_ok

        stdcall StrDel, [.hData]

.exit_ok:
        popad
        return
endp





; __SearchData.__info.codesize
;
; Arguments:
;   esi - pointer to the text of the database
;   edx - pointer to the path string
; Returns:
;   CF=0 if the path is found.
;     esi - pointer to the value.
;     ah=":" if the path is directory.
;     ah="=" if the path is data field.
;
;   CF=1 the path was not found.
;     esi - points to the first row after the end of the block where the path
;           is supposed to be (but is not).
;     edx - points to the remainder of the path that was not found.
;     ebx - the indent of the block where the record should be placed.
proc __SearchData
begin
        mov     edi, edx
        xor     ebx, ebx        ; current block indent starts with -1
        dec     ebx

.outer_loop:
; compute the line indent
        mov     ecx, esi

.scan_indent:
        lodsb
        test    al, al
        jz      .end_of_file

        cmp     al, ' '
        je      .scan_indent
        jb      .outer_loop

        cmp     al, ';'
        jne     .indent_ok

; skips to the end of the line.
.SkipIt:
        lodsb
        cmp     al, ' '
        jb      .outer_loop
        test    al, al
        jnz     .SkipIt

.end_of_file:
        dec      esi

.error:
        stc
        return

.indent_ok:
        dec     esi

        sub     ecx, esi
        neg     ecx

; Here the indent of the current line is in ECX.

        cmp     ecx, ebx
        jle     .error          ; if the current indent is less of equal than the block
                                ; indent, then the needed element can not be found.
        cmp     ecx, edi
        ja      .SkipIt         ; the current indent is above the current upper limit, so
                                ; the whole line have not to be processed.

        mov     edi, edx        ; the start of the current path (it is always greater than the current indent)

.key_loop:
        mov     al, [edi]
        inc     edi
        mov     ah, [esi]
        inc     esi

        test    al, al
        jz      .maybe_end_of_path
        cmp     al, ':'
        je      .key_maybe_found

        test    ah, ah
        jz      .end_of_file

        cmp     al, ah
        je      .key_loop

; not this, so check the current line whether it is a directory.
.not_this:
        dec     esi

.dir_loop:
        lodsb
        test    al, al
        jz      .end_of_file

        cmp     al, ' '
        jb      .outer_loop
        je      .SkipIt
        cmp     al, '='
        je      .SkipIt
        cmp     al, ':'
        jne     .dir_loop

.skip_dir:
        mov     edi, ecx        ; it is a subdirectory, that is not in the path,
                                ; so set the upper limit.
        jmp     .SkipIt

.maybe_end_of_path:
        cmp     ah, '='
        je      .found

.key_maybe_found:
        cmp     ah, ':'
        jne     .not_this

        test    al, al
        jz      .skip_dir       ; search for another key...

        mov     edx, edi        ; the subdirectory was found. Set new path and block indent.
        mov     ebx, ecx
        jmp     .SkipIt

.found:                         ; the whole path was found.
        clc
        return
endp




; __prepare_path.__info.codesize
; arguments:
;   ebx - pointer/handle to directory.
;   ecx - pointer/handle to key
;   edi - buffer
proc __prepare_path
begin
        stdcall StrPtr, ebx
        mov     esi, eax

.copy1:
        lodsb
        stosb
        test    al, al
        jnz     .copy1

        jecxz   .path_ok

        mov     al, ':'
        dec     edi

        cmp     [edi-1], al
        je      .sep_ok
        stosb
.sep_ok:

        stdcall StrPtr, ecx
        mov     esi, eax

.copy2:
        lodsb
        stosb
        test    al, al
        jnz     .copy2

.path_ok:
        return
endp




endmodule
