CHAT_MAX_USER_NAME = 20
CHAT_MAX_MESSAGE = 1000
CHAT_BACKLOG_DEFAULT = 100


sqlSelectChat  text "select id, time, user, original, message from ChatLog where id in (select id from chatlog order by id desc limit ?1)"

proc SendMessageLog, .session
.stmt dd ?
begin
        pushad

        xor     ebx, ebx       ; the count of the fetched messages.

        lea     eax, [.stmt]
        cinvoke sqlitePrepare_v2, [hMainDatabase], sqlSelectChat, sqlSelectChat.length, eax, 0

        mov     eax, CHAT_BACKLOG_DEFAULT
        stdcall GetParam, 'chat_backlog_length', gpInteger
        cinvoke sqliteBindInt, [.stmt], 1, eax

        stdcall StrDupMem, <txt '{ "msgs": [ '>         ; start of the messages data set.
        mov     edi, eax

.fetch_loop_msg:

        cinvoke sqliteStep, [.stmt]
        cmp     eax, SQLITE_ROW
        jne     .finish_msg_set

; is there previous record?

        test    ebx, ebx
        jz      .comma_ok1

        stdcall StrCat, edi, txt ", "

.comma_ok1:

        stdcall StrCat, edi, '{ "id": '

        cinvoke sqliteColumnText, [.stmt], 0
        stdcall StrCat, edi, eax
        stdcall StrCat, edi, txt ', "time": '

        cinvoke sqliteColumnText, [.stmt], 1
        stdcall StrCat, edi, eax
        stdcall StrCat, edi, txt ', "user": "'

        cinvoke sqliteColumnText, [.stmt], 2
        test    eax, eax
        jz      @f
        stdcall StrEncodeJS, eax
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
@@:
        stdcall StrCat, edi, txt '", "originalname": "'
        cinvoke sqliteColumnText, [.stmt], 3
        test    eax, eax
        jz      @f
        stdcall StrEncodeJS, eax
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
@@:
        stdcall StrCat, edi, txt '", "text": "'
        cinvoke sqliteColumnText, [.stmt], 4
        stdcall StrEncodeJS, eax
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, txt '" }'

        inc     ebx
        jmp     .fetch_loop_msg

.finish_msg_set:

        cinvoke sqliteFinalize, [.stmt]

        test    ebx, ebx
        jz      .messages_ok

        stdcall StrCat, edi, txt " ] }"
        stdcall AddEvent, evMessage, edi, [.session]

.messages_ok:
        stdcall StrDel, edi
        popad
        return
endp



sqlPostChatMessage text "insert into chatlog (time, user, original, message) values (?1, ?2, ?3, ?4);"
sqlGetChatUser     text "select username from EventSessions where session = ?1;"

proc ChatPage, .pSpecial
.stmt dd ?
begin
        pushad

        mov     esi, [.pSpecial]

; the user permissions

        stdcall ChatPermissions, esi
        jc      .error_no_permissions

        cmp     [esi+TSpecialParams.post_array], 0
        jne     .post_new_message

; opening the chat only by navigation

        stdcall CheckSecMode, [esi+TSpecialParams.params]
        cmp     eax, secNavigate
        ja      .error_no_permissions

        mov     eax, [esi+TSpecialParams.userLang]
        stdcall StrCat, [esi+TSpecialParams.page_title], [cChatTitle+8*eax]
        stdcall LogUserActivity, esi, uaChatting, 0

        stdcall AddActivitySimple, cActivityChat, atChatting, esi

        stdcall RenderTemplate, 0, txt "chat.tpl", 0, [.pSpecial]

        clc
        mov     [esp+4*regEAX], eax
        popad
        return

.error_no_permissions:
        stdcall TextCreate, sizeof.TText
        stdcall AppendError, eax, "403 Forbidden", esi
        jmp     .finish_replace


.post_new_message:

        xor     edi, edi
        stdcall GetQueryParam, esi, txt 'session='
        mov     edi, eax
        test    edi, edi
        jz      .error_no_permissions

;        stdcall FileWriteString, [STDERR], "Chat request session: "
;        stdcall FileWriteString, [STDERR], edi
;        stdcall FileWriteString, [STDERR], <txt "<<<", 13, 10>

        stdcall GetPostString, [esi+TSpecialParams.post_array], txt "cmd", 0
        test    eax, eax
        jz      .finish

        mov     ecx, .post_message
        stdcall StrCompCase, eax, txt "message"
        jc      .do_it

        mov     ecx, .rename_user
        stdcall StrCompCase, eax, txt "rename"
        jc      .do_it

        mov     ecx, .change_status
        stdcall StrCompCase, eax, txt "status"
        jc      .do_it

        mov     ecx, .finish

.do_it:
        stdcall StrDel, eax
        jmp     ecx


.post_message:

locals
  .id       dd ?
  .time     dd ?
  .user     dd ?
  .original dd ?
  .text     dd ?
endl

        stdcall GetTime
        xor     ecx, ecx
        mov     [.id], ecx
        mov     [.time], eax
        mov     [.user], ecx
        mov     [.original], ecx
        mov     [.text], ecx

        stdcall GetPostString, [esi+TSpecialParams.post_array], txt "chat_message", 0
        mov     [.text], eax
        test    eax, eax
        jz      .finish

; message text sanitation

        stdcall StrByteUtf8, [.text], CHAT_MAX_MESSAGE
        stdcall StrTrim, [.text], eax

        stdcall StrClipSpacesR, [.text]
        stdcall StrClipSpacesL, [.text]

        stdcall StrLen, [.text]
        test    eax, eax
        jz      .finish

        lea     eax, [.stmt]
        cinvoke sqlitePrepare_v2, [hMainDatabase], sqlGetChatUser, sqlGetChatUser.length, eax, 0

        stdcall StrPtr, edi
        cinvoke sqliteBindText, [.stmt], 1, eax, [eax+string.len], SQLITE_STATIC
        cinvoke sqliteStep, [.stmt]
        cmp     eax, SQLITE_ROW
        jne     .username_ok

        cinvoke sqliteColumnText, [.stmt], 0
        stdcall StrDupMem, eax
        mov     [.user], eax

.username_ok:
        cinvoke sqliteFinalize, [.stmt]

        stdcall EventUserName, [.pSpecial]
        mov     [.original], eax

        cinvoke sqliteExec, [hMainDatabase], sqlBegin, 0, 0, 0

        lea     eax, [.stmt]
        cinvoke sqlitePrepare_v2, [hMainDatabase], sqlPostChatMessage, sqlPostChatMessage.length, eax, 0

        cinvoke sqliteBindInt, [.stmt], 1, [.time]

        cmp     [.user], 0
        je      .username_ok2

        stdcall StrPtr, [.user]
        cinvoke sqliteBindText, [.stmt], 2, eax, [eax+string.len], SQLITE_STATIC

.username_ok2:
        stdcall StrPtr, [.original]
        cinvoke sqliteBindText, [.stmt], 3, eax, [eax+string.len], SQLITE_STATIC

        stdcall StrPtr, [.text]
        cinvoke sqliteBindText, [.stmt], 4, eax, [eax+string.len], SQLITE_STATIC

        cinvoke sqliteStep, [.stmt]
        mov     ebx, sqlRollback
        cmp     eax, SQLITE_DONE
        jne     .finish_query

        mov     ebx, sqlCommit
        cinvoke sqliteLastInsertRowID, [hMainDatabase]
        mov     [.id], eax

.finish_query:

        cinvoke sqliteFinalize, [.stmt]
        cinvoke sqliteExec, [hMainDatabase], ebx, 0, 0, 0

        cmp     [.id], 0        ; the insert in the chatlog has been failed and the ID is unknown.
        je      .finish         ; ... so, don't create an event.

        stdcall FormatJsonMessage, [.id], [.time], [.user], [.original], [.text]

        stdcall AddEvent, evMessage, eax, 0
        stdcall StrDel, eax

.finish:
        stdcall StrDel, [.user]
        stdcall StrDel, [.original]
        stdcall StrDel, [.text]
        stdcall StrDel, edi     ; the chatsid string.

        stdcall TextCreate, sizeof.TText
        stdcall TextCat, eax, <"Content-type: text/plain", 13, 10, 13, 10, "OK", 13, 10>

.finish_replace:
        stc
        mov     [esp+4*regEAX], edx
        popad
        return


.rename_user:
        stdcall GetPostString, [esi+TSpecialParams.post_array], "username", txt "  "
        mov     edx, eax

        stdcall EventUserName, esi
        stdcall RenameEventUser, edi, edx, eax

        stdcall StrDel, eax
        stdcall StrDel, edx
        jmp     .finish


.change_status:
        stdcall GetPostString, [esi+TSpecialParams.post_array], "status", 0
        test    eax, eax
        jz      .finish

        push    eax
        stdcall StrToNumEx, eax
        stdcall StrDel ; from the stack
        stdcall SetEventUserStatus, edi, eax
        jmp     .finish
endp





proc ChatPermissions, .pSpecial
begin
        push    eax esi

        mov     esi, [.pSpecial]

        stdcall GetParam, "chat_enabled", gpInteger
        jc      .not_ok

        test    eax, eax
        jz      .not_ok

        test    [esi+TSpecialParams.userStatus], permChat or permAdmin
        jnz     .permissions_ok

.not_ok:
        stc
        pop     esi eax
        return

.permissions_ok:
        clc
        pop     esi eax
        return
endp




proc FormatJsonMessage, .id, .time, .user, .original, .text
begin
        pushad

        stdcall StrDupMem, txt '{ "msgs": [ '
        mov     edi, eax

        stdcall StrCat, edi, '{ "id": '
        stdcall NumToStr, [.id], ntsDec or ntsUnsigned
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, txt ', "time": '

        stdcall NumToStr, [.time], ntsDec or ntsUnsigned
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, txt ', "user": "'
        cmp     [.user], 0
        je      @f
        stdcall StrEncodeJS, [.user]
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
@@:
        stdcall StrCat, edi, txt '", "originalname": "'
        stdcall StrEncodeJS, [.original]
        stdcall StrCat, edi, eax
        stdcall StrDel, eax

        stdcall StrCat, edi, txt '", "text": "'
        stdcall StrEncodeJS, [.text]
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, txt '" } ] }'

        mov     [esp+4*regEAX], edi
        popad
        return
endp




cCRLF2 text 13, 10, 13, 10

if defined options.DebugWebSSE & options.DebugWebSSE

cContentTypeEvent2 text 'Content-Type: text/event-stream', 13, 10, "X-Accel-Buffering: yes", 13, 10, 13, 10, "retry: 1000", 13, 10, 13, 10


proc EchoRealTime, .pSpecialParams
.bytes dd ?
begin
        pushad

        DebugMsg "Started echo long life thread!"

        mov     esi, [.pSpecialParams]

        cmp     [fEventsTerminate], 0
        jne     .finish_socket

        mov     edi, cContentTypeEvent
        mov     ecx, cContentTypeEvent.length
        cmp     [esi+TSpecialParams.page_num], 0
        je      @f

        mov     edi, cContentTypeEvent2
        mov     ecx, cContentTypeEvent2.length

@@:
        stdcall FCGI_output, [esi+TSpecialParams.hSocket], [esi+TSpecialParams.requestID], edi, ecx, FALSE

        stdcall StrDupMem, <txt 'event: message', 13, 10, 'data: '>        ; echo message
        mov     edi, eax

        mov     ebx, cContentTypeEvent.length

.event_loop_msg:

        stdcall StrPtr, edi
        stdcall FCGI_output, [esi+TSpecialParams.hSocket], [esi+TSpecialParams.requestID], eax, [eax+string.len], FALSE
        jc      .finish

        stdcall NumToStr, ebx, ntsDec or ntsUnsigned or ntsFixedWidth + 6
        push    eax

        stdcall StrPtr, eax
        stdcall FCGI_output, [esi+TSpecialParams.hSocket], [esi+TSpecialParams.requestID], eax, [eax+string.len], FALSE
        stdcall StrDel ; from the stack
        jc      .finish

        stdcall FCGI_output, [esi+TSpecialParams.hSocket], [esi+TSpecialParams.requestID], cCRLF2, cCRLF2.length, FALSE
        jc      .finish

        cmp     [fEventsTerminate], 0
        jne     .finish_socket

        add     ebx, 32 ; one event lenght

        stdcall Sleep, 100
        jmp     .event_loop_msg


.finish_socket:

        stdcall FCGI_output, [esi+TSpecialParams.hSocket], [esi+TSpecialParams.requestID], 0, 0, TRUE

.finish:

        stdcall StrDel, edi

        DebugMsg "Finished echo long life thread!"

        popad
        xor     eax, eax
        stc
        return
endp

else
  EchoRealTime = 0
end if
