HELP_SERVER_MIN_PORT = 7000
HELP_SERVER_MAX_PORT = 9999

uglobal
  fHelpQuit   dd ?
;  cfgHelpRoot dd ?
  cfgHelpURL  dd ?

  idHelpThread dd ?
  hHelpSocket  dd ?
endg


proc StartHelpServer
begin
;        stdcall ConvertPath, '%Fresh%/doc/'
;        mov     [cfgHelpRoot], eax

        stdcall ThreadCreate, HelpServerThread, fHelpQuit
        jc      .finish
        mov     [idHelpThread], eax

.finish:
        return
endp



proc StopHelpServer
begin
        xor     eax, eax
        xchg    eax, [hHelpSocket]
        test    eax, eax
        jz      .socket_ok

        stdcall SocketClose, eax

.socket_ok:
        mov     [fHelpQuit], 1

; wait for the threads to end...
        xor     eax, eax

.wait:
        stdcall Sleep, 100
        cmp     [fHelpQuit], eax
        jne     .wait

        xchg    eax, [idHelpThread]
        stdcall FreeThreadID, eax

;        xor     eax, eax
;        xchg    [cfgHelpRoot], eax
;        stdcall StrDel, eax
        return
endp




; This procedure provides the main server loop.
; It listens for connections, accept it and then
; creates a thread that serves the connection.

proc HelpServerThread, .ptrQuitFlag
.addr TSocketAddressIn
.hSocket dd ?
begin
        mov     eax, [.ptrQuitFlag]
        mov     dword [eax], 0

        stdcall SocketCreate, PF_INET, SOCK_STREAM, 0
        jc      .sockerr
        mov     [.hSocket], eax     ; save socket handle
        mov     [hHelpSocket], eax

        stdcall SocketSetOption, [.hSocket], soReuseAddr, TRUE
        stdcall SocketSetOption, [.hSocket], soRecvTimeout, 3000
        stdcall SocketSetOption, [.hSocket], soSendTimeout, 3000

; Bind the socket to the address

; fillup sockaddr
        lea     ecx, [.addr]

        mov     ebx, HELP_SERVER_MIN_PORT

        mov     [ecx+TSocketAddressIn.saFamily], AF_INET
        mov     [ecx+TSocketAddressIn.saAddress], $0100007f     ; local host

.bind_try:
        mov     edx, ebx
        xchg    dl, dh
        mov     [ecx+TSocketAddressIn.saPort], dx

        stdcall  SocketBind, [.hSocket], ecx
        jnc      .bind_ok

        inc     ebx
        cmp     ebx, HELP_SERVER_MAX_PORT
        ja      .binderr

        jmp     .bind_try

.bind_ok:
        stdcall StrDup, 'http://127.0.0.1:'
        mov     ecx, eax

        stdcall NumToStr, ebx, ntsDec or ntsUnsigned
        stdcall StrCat, ecx, eax
        stdcall StrDel, eax
        stdcall StrCharCat, ecx, '/'

        mov     [cfgHelpURL], ecx

; Set the socket to listen for the incomming connections.
.doListen:
        stdcall SocketListen, [.hSocket], 50
        jc      .listenerr

; Accept connection - Here is the main loop of the web server!
.doAccept:
        mov     eax, [.ptrQuitFlag]
        cmp     dword [eax], 0
        jne     .exit_s

        stdcall  SocketAccept, [.hSocket], 0
        jc      .accepterr

        mov     ebx, eax

        stdcall ThreadCreate, ConnectionHandler, ebx
        jc      .thread_error

        stdcall FreeThreadID, eax

        inc     [ThreadCount]
        jmp     .doAccept

.thread_error:
        stdcall  SocketClose, ebx
        jmp     .doAccept

uglobal
  ThreadCount dd ?
endg

; ------------------------------------------------------------

.exit_s:
        xor     eax, eax
        xchg    eax, [cfgHelpURL]
        stdcall StrDel, eax

.exit2:
        stdcall SocketClose, [.hSocket]

        mov     eax, [.ptrQuitFlag]
        mov     dword [eax], 0
        return

; ------------------------------------------------------------

; These are just to print an error message to stderr should
; one of our socket calls fail.
.sockerr:
        stdcall FileWriteString, [STDERR], cSocksBroke
        jmp     .exit2

.binderr:
        stdcall FileWriteString, [STDERR], cBindsBroke
        jmp     .exit2

.listenerr:
        stdcall FileWriteString, [STDERR], cListensBroke
        jmp     .exit2

.accepterr:
        cmp     eax, serrTimeout
        je      .doAccept
        stdcall FileWriteString, [STDERR], cAcceptsBroke
        jmp     .doAccept
endp


cSocksBroke   text  'ERROR: SocketCreate failed!', 10
cBindsBroke   text  'ERROR: SocketBind failed!', 10
cListensBroke text  'ERROR: SocketListen failed!', 10
cAcceptsBroke text  'ERROR: SocketAccept failed!', 10








cStreamBufferSize = 256 ; keep this constant small in order to test the proper work
                        ; of the stream processing code. Set big (4096) for better
                        ; performance.

; ------------------------------------------------------------
; Thread procedure that serves the connection - receives the
; requests and then calls the request processing procedure.

proc ConnectionHandler, .hSocket
  .buffer   dd ?
  .pHeaders dd ?
  .hCurrent dd ?
  .expect_next dd ?

  .post_data dd ?
begin
        pushad

        mov     [.post_data], 0
        mov     [.expect_next], 0

        stdcall StrNew
        mov     [.hCurrent], eax

        stdcall CreateArray, 4
        mov     [.pHeaders], eax

        stdcall GetMem, cStreamBufferSize+16
        jc      .finish2
        mov     [.buffer], eax

.recvloop:
        mov     esi, [.buffer]
        stdcall SocketReceive, [.hSocket], esi, cStreamBufferSize, 0
        jc      .finish
        test    eax, eax
        jz      .finish         ; the client closed connection.

        mov     ecx, eax
        mov     dword [esi+ecx], 0      ; zero terminator.

        mov     eax, [.expect_next]
        test    eax, eax
        jz      .outer

        cmp     [esi], al
        jne     .outer

        inc     esi
        dec     ecx
        js      .end_of_buffer

; now parse headers.
.outer:
        mov     ebx, esi

.inner:
        dec     ecx
        js      .end_of_buffer

        mov     al, [esi]
        lea     esi, [esi+1]

        cmp     al, $0d
        je      .line_end
        cmp     al, $0a
        jne     .inner

.line_end:
        mov     edx, esi
        xor     al, $0d xor $0a

        test    ecx, ecx
        jnz     .check_lf

        mov     [.expect_next], eax
        jmp     .next_line

.check_lf:
        mov     [.expect_next], 0
        cmp     [esi], al
        jne     .next_line
        inc     esi
        dec     ecx

.next_line:
        sub     edx, ebx
        dec     edx
        jz      .end_of_request       ; empty line ends the headers of the HTTP request.

        stdcall StrExtract, ebx, 0, edx
        stdcall StrCat, [.hCurrent], eax
        stdcall StrDel, eax

        stdcall AddArrayItems, [.pHeaders], 1
        mov     [.pHeaders], edx

        push    [.hCurrent]
        pop     dword [eax]

        stdcall StrNew
        mov     [.hCurrent], eax
        jmp     .outer

.end_of_buffer:         ; concatenate the last (possibly half) line to the hCurrent string.
        stdcall StrCat, [.hCurrent], ebx
        jmp     .recvloop

.end_of_request:        ; there is possibly more content in the buffer (request body)

        stdcall SearchHeaders, [.pHeaders], 'content-length'
        jc      .process_request            ; if not found

        push    eax
        stdcall StrToNumEx, eax
        stdcall StrDel ; from the stack
        mov     edx, eax

        lea     eax, [edx+16]   ; bigger buffer
        stdcall GetMem, eax
        lea     edi, [eax+8]
        mov     [.post_data], edi

        sub     edx, ecx
        rep movsb               ; copy the rest of the buffer to the begining of the post data buffer.

        test    edx, edx
        jz      .end_post
        jns     .post_loop

        xor     edx, edx
        jmp     .end_post

.post_loop:
        stdcall SocketReceive, [.hSocket], edi, edx, 0
        test    eax, eax
        jz      .finish         ; socket has been closed...

        add     edi, eax
        sub     edx, eax
        ja      .post_loop

.end_post:
        mov     dword [edi], 0

        stdcall StrURLDecode,  [.post_data]

.process_request:
        stdcall ProcessRequest, [.hSocket], [.pHeaders], [.post_data]

.finish:
        cmp     [.post_data], 0
        je      .post_ok

        mov     eax, [.post_data]
        sub     eax, 8
        stdcall FreeMem, eax

.post_ok:
        stdcall FreeMem,     [.buffer]

.finish2:
        stdcall StrDel,      [.hCurrent]
        stdcall ListFree,    [.pHeaders], StrDel
        stdcall SocketClose, [.hSocket]

        dec     [ThreadCount]
        popad
        return
endp




proc SearchHeaders, .pHeaders, .pKey
begin
        push    ecx edx esi edi

        mov     edi, [.pHeaders]

        mov     ecx, [edi+TArray.count]

.main_loop:
        dec     ecx
        js      .end_headers

        stdcall StrPtr, [edi+TArray.array+4*ecx]
        mov     esi, eax

        stdcall StrPtr, [.pKey]
        mov     edx, eax

.comp_loop:
        mov     al, [esi]
        mov     ah, [edx]
        lea     esi, [esi+1]
        lea     edx, [edx+1]

        test    al, al
        jz      .main_loop
        test    ah, ah
        jz      .maybe

        or      ax, $2020
        cmp     al, ah
        je      .comp_loop
       jmp      .main_loop

.maybe:
        cmp     al, ':'
        jne     .main_loop

        stdcall StrDup, esi     ; header found, return the value.
        stdcall StrClipSpacesR, eax
        stdcall StrClipSpacesL, eax
        clc
        pop     edi esi edx ecx
        return

.end_headers:
        xor     eax, eax
        stc
        pop     edi esi edx ecx
        return
endp