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

; config data types.

cdtNULL      = 0
cdtInteger   = 1   ; 32bit integer value.
cdtString    = 2   ; utf-8 string.
cdtBlob      = 3   ; arbitraty sized array of bytes.
cdtDirectory = 4

cdtMaxAlowed = cdtDirectory


; Search for the given parameter in the database and return its value.
;
; arguments:
;   .hData   - 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=value of the element. edx - type of the element. id edx==cdtDirectory, the value of eax
;       is unchanged.
; CF=1; the element not found.
;
; GetConfigRecord.__info.codesize

proc GetConfigRecord, .hData, .path
begin
        pushad

        stdcall StrPtr, [.hData]
        mov     esi, eax

        stdcall StrPtr, [.path]
        mov     edx, eax

        call    __SearchData
        jc      .exit

        cmp     ah, ':'
        je      .is_dir

        call    .SkipSpaces  ; skip to the stard of the text
        jc      .is_null

        mov     al, [esi]

        cmp     al, '$'
        je      .is_number
        cmp     al, '0'
        jb      .not_number
        cmp     al, '9'
        jbe     .is_number

.not_number:
;        cmp     al, '#'
;        je      .is_blob

.is_string:
        mov     ecx, esi

.outer_end:
        mov     ebx, esi

.end_loop:
        lodsb
        cmp     al, $0d
        je      .crlf
        cmp     al, $0a
        je      .crlf
        test    al, al
        jz      .crlf

        cmp     al, ' '
        je      .end_loop
        jmp     .outer_end

.crlf:
        cmp     ebx, ecx
        je      .is_null

        mov     ah, [ebx-1]
        mov     al, [ecx]
        sub     ebx, ecx

        cmp     ebx, 1
        jbe     .extract

        cmp     al, '"'
        je      .quote
        cmp     al, "'"
        jne     .extract

.quote:
        cmp     al, ah
        jne     .extract

        sub     ebx, 2
        inc     ecx

.extract:
        stdcall StrExtract, ecx, 0, ebx
        mov     [esp+4*regEAX], eax
        mov     dword [esp+4*regEDX], cdtString
        jmp     .exit_ok

.is_number:
        stdcall StrToNumEx, esi
        jc      .is_string

        mov     [esp+4*regEAX], eax
        mov     dword [esp+4*regEDX], cdtInteger
        jmp     .exit_ok

.is_dir:
        mov     dword [esp+4*regEDX], cdtDirectory
        jmp     .exit_ok

.is_null:
        mov     dword [esp+4*regEDX], cdtNULL

.exit_ok:
        clc
.exit:
        popad
        return


.SkipSpaces:
        lodsb
        test    al, al
        jz      .end_skip

        cmp     al, ' '
        je      .SkipSpaces

.end_skip:
        dec     esi
        retn

endp


; SetConfigRecord.__info.codesize
proc SetConfigRecord, .hData, .path, .type, .data, .size
.ins_pos dd ?
begin
        pushad

        stdcall StrPtr, [.hData]
        mov     esi, eax

        stdcall StrPtr, [.path]
        mov     edx, eax

        call    __SearchData
        jc      .not_found

        stdcall StrPtr, [.hData]
        mov     edi, eax

; search the bounds of the record...

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

.end_loop:
        lodsb
        cmp     al, $0d
        je      .crlf
        cmp     al, $0a
        je      .crlf
        test    al, al
        jnz     .end_loop

.crlf:
        dec     esi
        sub     esi, edi
        sub     esi, edx        ; length of the record

        stdcall StrSplit,[.hData], edx
        push    eax
        stdcall StrSplit, eax, esi
        stdcall StrDel ; from the stack

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

        stdcall __CreateAsciiData, [.type], [.data], [.size]
        jc      .restore

        stdcall StrCat, [.hData], eax

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

        stdcall StrDel, eax
        stdcall StrDel, edx

        clc
        popad
        return

.not_found:
; search back the new line.
;        int3

        stdcall StrPtr, [.hData]
        mov     edi, eax

.back_loop:
        cmp     esi, edi
        je      .create_here

        dec     esi
        mov     cl, [esi]
        test    cl, cl
        jz      .create_here
        cmp     cl, $0a
        je      .create_here
        cmp     cl, $0d
        jne     .back_loop

.create_here:
        sub     esi, edi          ; position inside the string.
        mov     [.ins_pos], esi

        stdcall StrNew
        mov     edi, eax
        dec     edx

        test    cl, cl
        jnz     .scan_path

        stdcall StrCharCat, edi, $0a0d

.scan_path:
        inc     edx

        inc     ebx
        jz      @f
        inc     ebx
@@:

        mov     esi, edx          ; the remainer of the path

.key_loop:
        lodsb

        cmp     al, '/'
        je      .record
        cmp     al, '\'
        je      .record

        test    al, al
        jnz     .key_loop

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

        mov     ecx, ebx
        jecxz   .indent_ok

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

.indent_ok:
        stdcall StrNew
        stdcall StrCopyPart, eax, edx, 0, esi
        stdcall StrCat, edi, eax
        stdcall StrDel, eax

        lea     edx, [edx+esi]

        mov     eax, '='
        cmp     byte [edx], 0
        je      @f
        mov     eax, ':'+$0a0d00
@@:
        stdcall StrCharCat, edi, eax
        cmp     byte [edx], 0
        jne     .ins_ready

        stdcall __CreateAsciiData, [.type], [.data], [.size]
        stdcall StrCat, edi, eax
        stdcall StrDel, eax

.ins_ready:
        cmp     byte [edx], 0
        jne     .scan_path

        stdcall StrSplit,[.hData], [.ins_pos]
        stdcall StrCat, [.hData], edi
        stdcall StrCat, [.hData], eax
        stdcall StrDel, eax

        clc
        popad
        return
endp



proc __CreateAsciiData, .type, .data, .size
begin
        cmp     byte [.type], cdtInteger
        je      .number

        stdcall StrNew

        cmp     byte [.type], cdtNULL
        je      .finish

        cmp     byte [.type], cdtBlob
        je      .blob

        cmp     byte [.type], cdtString
        jne     .finish

        stdcall StrCopy, eax, [.data]

.finish:
        return

.number:
        push    ecx

        mov     ecx, [.type]
        and     ecx, $ffffff00
        stdcall NumToStr, [.data], ecx

        mov     cl, byte [.type+1]
        cmp     cl, $10
        jne     @f
        stdcall StrCharInsert, eax, '$', 0
@@:
        pop     ecx
        return

.blob:
        push    ecx edx esi

        mov     edx, eax
        mov     esi, [.data]
        mov     ecx, [.size]

        stdcall StrCharCat, edx, '#'
        jecxz   .end_blob

.loop:
        lodsb
        movzx   eax, al
        stdcall NumToStr, eax, ntsHex + ntsFixedWidth + 2
        stdcall StrCat, edx, eax
        stdcall StrDel, eax
        loop    .loop

.end_blob:
        mov     eax, edx
        pop     esi edx ecx
        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.
proc __SearchData
begin
        mov     edi, edx
        xor     ebx, ebx        ; current block indent
        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 indeent)

.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
        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

        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      .found          ; the searched path is subdirectory, not a record.

        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





endmodule
