(**************************************************************************)
(*                                                                        *)
(*  Setup for Weasel mail server                                          *)
(*  Copyright (C) 2024   Peter Moylan                                     *)
(*                                                                        *)
(*  This program is free software: you can redistribute it and/or modify  *)
(*  it under the terms of the GNU General Public License as published by  *)
(*  the Free Software Foundation, either version 3 of the License, or     *)
(*  (at your option) any later version.                                   *)
(*                                                                        *)
(*  This program is distributed in the hope that it will be useful,       *)
(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
(*  GNU General Public License for more details.                          *)
(*                                                                        *)
(*  You should have received a copy of the GNU General Public License     *)
(*  along with this program.  If not, see <http://www.gnu.org/licenses/>. *)
(*                                                                        *)
(*  To contact author:   http://www.pmoylan.org   peter@pmoylan.org       *)
(*                                                                        *)
(**************************************************************************)

IMPLEMENTATION MODULE StatsPage;

        (****************************************************************)
        (*                                                              *)
        (*                      PM Setup for Weasel                     *)
        (*             The statistics page of the notebook              *)
        (*                                                              *)
        (*        Started:        23 January 2024                       *)
        (*        Last edited:    2 April 2024                          *)
        (*        Status:         Complete but poorly tested            *)
        (*                                                              *)
        (****************************************************************)


IMPORT OS2, DID, CommonSettings;
FROM Names IMPORT ServiceType;
FROM SYSTEM IMPORT ADDRESS, CAST, ADR;

FROM Languages IMPORT
    (* type *)  LangHandle,
    (* proc *)  StrToBuffer;

FROM Conversions IMPORT
    (* proc *)  LongRealToString;

FROM LongMath IMPORT
    (* proc *)  sqrt;

FROM WSUINI IMPORT
    (* proc *)  OpenINIFile, CloseINIFile;

FROM RINIData IMPORT
    (* proc *)  INIFetch, INIPut;

FROM PMInit IMPORT
    (* proc *)  OurHab;

FROM TaskControl IMPORT
    (* proc *)  CreateTask;

FROM Timer IMPORT
    (* proc *)  Sleep;

FROM LowLevel IMPORT
    (* proc *)  EVAL;

(**************************************************************************)

CONST
    MemName = "\SHAREMEM\WEASEL\STATS";
    MutexName = "\SEM32\WEASEL\STATS";

TYPE
    DisplayDataType =
            ARRAY ServiceType OF
                    RECORD
                        lastarrival: CARDINAL;
                        lambdaest: LONGREAL;
                        muinvest: LONGREAL;
                    END (*RECORD*);

    MemPtr = POINTER TO DisplayDataType;

TYPE
    IDarray = ARRAY ServiceType OF CARDINAL;

CONST
    lambdalabelfield = IDarray {DID.POPlambdaLabel, DID.SMTPlambdaLabel, DID.MSAlambdaLabel, DID.OUTlambdaLabel, 0};
    muinvlabelfield  = IDarray {DID.POPmuinvLabel, DID.SMTPmuinvLabel, DID.MSAmuinvLabel, DID.OUTmuinvLabel, 0};
    reclabelfield  = IDarray {DID.RPOPMaxLabel, DID.RSMTPMaxLabel, DID.RMSAMaxLabel, DID.OUTnumLabel, 0};

    lambdafield = IDarray {DID.POPlambda, DID.SMTPlambda, DID.MSAlambda, DID.OUTlambda, 0};
    muinvfield  = IDarray {DID.POPmuinv, DID.SMTPmuinv, DID.MSAmuinv, DID.OUTmuinv, 0};
    recfield  = IDarray {DID.RPOPMax, DID.RSMTPMax, DID.RMSAMax, DID.OUTnum, 0};

    lambdaunitfield = IDarray {DID.POPlambdaunit, DID.SMTPlambdaunit, DID.MSAlambdaunit, DID.OUTlambdaunit, 0};
    muinvunitfield  = IDarray {DID.POPmuinvunit, DID.SMTPmuinvunit, DID.MSAmuinvunit, DID.OUTmuinvunit, 0};

VAR
    OurPageID: CARDINAL;
    statsenable, ChangeInProgress, shutdown: BOOLEAN;

    (* Handle to the window that belongs to this page.  We can save     *)
    (* this as a static value because there is never more than one      *)
    (* instance of this page.                                           *)

    pagehandle: OS2.HWND;
    notebookhandle: OS2.HWND;

    (* Address of shared memory, and critical section protection for    *)
    (* the shared data.                                                 *)

    BaseAddr: MemPtr;
    hmtx: OS2.HMTX;

    (* The stats data that we use if we can't get it via shared memory. *)

    state: DisplayDataType;

    tid: OS2.TID;
    hab_display: OS2.HAB;

(************************************************************************)
(*                    OPERATIONS ON DIALOGUE LABELS                     *)
(************************************************************************)

PROCEDURE SetLanguage (lang: LangHandle);

    (* Relabels this page in the new language. *)

    VAR stringval: ARRAY [0..511] OF CHAR;

    BEGIN
        StrToBuffer (lang, "StatsPage.tab", stringval);
        OS2.WinSendMsg (notebookhandle, OS2.BKM_SETTABTEXT,
                        CAST(ADDRESS,OurPageID), ADR(stringval));
        StrToBuffer (lang, "StatsPage.statsenable", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.statsenable, stringval);

        StrToBuffer (lang, "Page1.MessageSubmission", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.MSAbox, stringval);

        StrToBuffer (lang, "StatsPage.lambda", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.SMTPlambdaLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.POPlambdaLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.MSAlambdaLabel, stringval);
        StrToBuffer (lang, "StatsPage.outrate", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.OUTlambdaLabel, stringval);

        StrToBuffer (lang, "StatsPage.muinv", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.SMTPmuinvLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.POPmuinvLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.MSAmuinvLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.OUTmuinvLabel, stringval);

        StrToBuffer (lang, "StatsPage.MaxUsers", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.RSMTPMaxLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.RPOPMaxLabel, stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.RMSAMaxLabel, stringval);
        StrToBuffer (lang, "StatsPage.nthreads", stringval);
        OS2.WinSetDlgItemText (pagehandle, DID.OUTnumLabel, stringval);

    END SetLanguage;

(************************************************************************)
(*                     SHOW OR HIDE DIALOGUE ELEMENTS                   *)
(************************************************************************)

PROCEDURE ShowHide (hwnd: OS2.HWND);

    (* Shows or hides subwindows, depending on value of statsenable. *)

    (**********************************************************************)

    PROCEDURE DoIt (ID: CARDINAL;  value: BOOLEAN);

        BEGIN
            IF value THEN
                OS2.WinShowWindow (OS2.WinWindowFromID (hwnd, ID), TRUE);
            ELSE
                OS2.WinShowWindow (OS2.WinWindowFromID (hwnd, ID), FALSE);
            END (*IF*);
        END DoIt;

    (**********************************************************************)

    VAR j: ServiceType;

    BEGIN
        DoIt (DID.SMTPbox, statsenable);
        DoIt (DID.POPbox, statsenable);
        DoIt (DID.MSAbox, statsenable);
        DoIt (DID.OUTbox, statsenable);
        FOR j := MIN(ServiceType) TO IMAP DO
            DoIt (lambdalabelfield[j], statsenable);
            DoIt (muinvlabelfield[j], statsenable);
            DoIt (reclabelfield[j], statsenable);
            DoIt (lambdafield[j], statsenable);
            DoIt (muinvfield[j], statsenable);
            DoIt (recfield[j], statsenable);
            DoIt (lambdaunitfield[j], statsenable);
            DoIt (muinvunitfield[j], statsenable);
        END (*FOR*);

        (* Also refresh the checkbox, in case we get out of synch. *)

        OS2.WinSendDlgItemMsg (hwnd, DID.statsenable, OS2.BM_SETCHECK,
                         OS2.MPFROMSHORT(ORD(statsenable)), NIL);
    END ShowHide;

(************************************************************************)
(*                           DISPLAYING THE DATA                        *)
(*  We prefer to get the data from shared memory, but that is not       *)
(*  always possible.  If Weasel is not running, or it is running on     *)
(*  a different machine, we can't open shared memory.  In that case, we *)
(*  get the data from the INI file, but only once.                      *)
(************************************************************************)

PROCEDURE OpenSharedMemory(): BOOLEAN;

    (* Obtains access to the shared memory and its critical section protection.  *)

    TYPE CharSet = SET OF CHAR;

    VAR ulrc: CARDINAL;  success: BOOLEAN;
        name: ARRAY [0..63] OF CHAR;

    BEGIN
        BaseAddr := NIL;

        (* Open shared memory. *)

        name := MemName;
        ulrc := OS2.DosGetNamedSharedMem (BaseAddr, name, OS2.PAG_READ);
        success := ulrc = 0;

        (* Critical protection mutex.  *)

        IF success THEN
            name := MutexName;
            ulrc := OS2.DosOpenMutexSem (name, hmtx);
            IF ulrc <> 0 THEN
                success := FALSE;
            END (*IF*);
        END (*IF*);

        RETURN success;

    END OpenSharedMemory;

(************************************************************************)

PROCEDURE LoadINIData (hwnd: OS2.HWND);

    (* Loads some data from the INI file.  This includes the numerical  *)
    (* data, which we might or might not end up using.                  *)

    VAR SYSapp: ARRAY [0..5] OF CHAR;
        s: ServiceType;

    BEGIN
        SYSapp := "$SYS";
        OpenINIFile;
        IF NOT INIFetch (SYSapp, "statsenable", statsenable) THEN
            statsenable := FALSE;
        END (*IF*);
        OS2.WinSendDlgItemMsg (hwnd, DID.statsenable, OS2.BM_SETCHECK,
                         OS2.MPFROMSHORT(ORD(statsenable)), NIL);

        IF NOT INIFetch (SYSapp, "stats", state) THEN
            FOR s := MIN(ServiceType) TO IMAP DO
                state[s].lambdaest := 0.0;
                state[s].muinvest := 0.0;
            END (*FOR*);
        END (*IF*);
        CloseINIFile;

    END LoadINIData;

(************************************************************************)

PROCEDURE DisplayLongReal (hwnd: OS2.HWND;  id: CARDINAL;  val: LONGREAL);

    VAR text: ARRAY [0..31] OF CHAR;
        rc: CARDINAL;

    BEGIN
        LongRealToString (val, text, 10);
        IF NOT OS2.WinSetDlgItemText (hwnd, id, text) THEN
            rc := OS2.WinGetLastError (hab_display);
            OS2.WinSetDlgItemShort (hwnd, id, rc, FALSE);
        END (*IF*);
    END DisplayLongReal;

(************************************************************************)

PROCEDURE ["SysCall"] DisplayData (hwnd: OS2.HWND);

    (* Fills the dialogue elements on this page with data from the INI  *)
    (* file, or loads default values if they're not in the INI file.    *)

    VAR hmq: OS2.HMQ;
        mean: LONGREAL;
        recommended, rc: CARDINAL;
        p: MemPtr;
        s: ServiceType;
        usingshared: BOOLEAN;

    BEGIN
        hab_display := OS2.WinInitialize(0);
        hmq := OS2.WinCreateMsgQueue (hab_display, 0);
        IF hmq = 0 THEN
            rc := OS2.WinGetLastError (hab_display);
            OS2.WinSetDlgItemShort (hwnd, lambdafield[SMTP], rc, FALSE);
        END (*IF*);

        (* Show or hide the dialogue display fields. *)

        ShowHide (hwnd);

        (* The data come from either shared memory, or from the "state" *)
        (* record that was loaded from INI data.                        *)

        usingshared := OpenSharedMemory();
        IF usingshared THEN p := BaseAddr;
        ELSE p := ADR(state);
        END (*IF*);

        (* We want to execute the loop body repeatedly if usingshared   *)
        (* is TRUE, but only once otherwise.  We fill the fields even   *)
        (* if statsenabled is FALSE, so that the values are there if    *)
        (* the user later sets statsenabled.                            *)

        REPEAT
            FOR s := MIN(ServiceType) TO IMAP DO
                DisplayLongReal (hwnd, lambdafield[s], p^[s].lambdaest);
                DisplayLongReal (hwnd, muinvfield[s], p^[s].muinvest);
                mean := p^[s].lambdaest * p^[s].muinvest;

                (* The 2 in the following calculation is to give us a   *)
                (* rounding up rather than truncation, and also to give *)
                (* a reasonable minimum for the case of zero traffic.   *)

                recommended := TRUNC (mean + 3*sqrt(mean) + 2.0);
                OS2.WinSetDlgItemShort (hwnd, recfield[s], recommended, FALSE);
            END (*FOR*);
            IF usingshared THEN
                Sleep (2000);
            END (*IF*);
        UNTIL NOT usingshared;

    END DisplayData;

(************************************************************************)
(*                   WRITING DATA BACK TO THE INI FILE                  *)
(************************************************************************)

PROCEDURE SaveEnableFlag;

    BEGIN
        OpenINIFile;
        INIPut ("$SYS", "statsenable", statsenable);
        CloseINIFile;

        (* No other fields are editable, so we need not save them. *)

    END SaveEnableFlag;

(************************************************************************)
(*                        THE DIALOGUE PROCEDURE                        *)
(************************************************************************)

PROCEDURE ["SysCall"] DialogueProc (hwnd: OS2.HWND;  msg: OS2.ULONG;
                                      mp1, mp2: OS2.MPARAM): OS2.MRESULT;

    VAR ButtonID, NotificationCode: CARDINAL;

    BEGIN
        IF msg = OS2.WM_INITDLG THEN

            OS2.WinSetWindowPos (hwnd, 0, 0, 0, 0, 0, OS2.SWP_MOVE);
            shutdown := FALSE;
            pagehandle := hwnd;
            LoadINIData (hwnd);
            RETURN NIL;

        ELSIF msg = OS2.WM_PRESPARAMCHANGED THEN

            IF ChangeInProgress THEN
                RETURN OS2.WinDefDlgProc(hwnd, msg, mp1, mp2);
            ELSE
                ChangeInProgress := TRUE;
                CommonSettings.UpdateFontFrom (hwnd, CommonSettings.MainNotebook);
                ChangeInProgress := FALSE;
                RETURN NIL;
           END (*IF*);

        ELSIF msg = OS2.WM_COMMAND THEN

            RETURN OS2.WinDefDlgProc (hwnd, msg, mp1, mp2);

        ELSIF msg = OS2.WM_CONTROL THEN

            NotificationCode := OS2.ULONGFROMMP(mp1);
            ButtonID := NotificationCode MOD 65536;
            NotificationCode := NotificationCode DIV 65536;
            IF (ButtonID = DID.statsenable)
                             AND (NotificationCode = OS2.BN_CLICKED) THEN
                statsenable := NOT statsenable;
                ShowHide (hwnd);
                SaveEnableFlag;       (* this saves only the button state *)
                RETURN NIL;
            END (*IF*);

        END (*IF*);

        RETURN OS2.WinDefDlgProc (hwnd, msg, mp1, mp2);

    END DialogueProc;

(**************************************************************************)

PROCEDURE StartDisplayThread;

    VAR ulrc: CARDINAL;

    BEGIN
        ulrc := OS2.DosCreateThread (tid, DisplayData, pagehandle,
                        OS2.CREATE_READY + OS2.STACK_SPARSE, 4192);
    END StartDisplayThread;

(**************************************************************************)

PROCEDURE CreatePage (notebook: OS2.HWND;  VAR (*OUT*) PageID: CARDINAL): OS2.HWND;

    (* Creates the statistics page and adds it to the notebook. *)

    VAR Label: ARRAY [0..31] OF CHAR;

    BEGIN
        notebookhandle := notebook;
        pagehandle := OS2.WinLoadDlg(notebook, notebook,
                       DialogueProc,    (* dialogue procedure *)
                       0,                   (* use resources in EXE *)
                       DID.stats,                (* dialogue ID *)
                       NIL);                 (* creation parameters *)
        PageID := OS2.ULONGFROMMR (OS2.WinSendMsg (notebook, OS2.BKM_INSERTPAGE,
                         NIL, OS2.MPFROM2SHORT (OS2.BKA_MAJOR+OS2.BKA_AUTOPAGESIZE, OS2.BKA_LAST)));
        OurPageID := PageID;
        Label := "Statistics";
        OS2.WinSendMsg (notebook, OS2.BKM_SETTABTEXT,
                        CAST(ADDRESS,PageID), ADR(Label));
        OS2.WinSendMsg (notebook, OS2.BKM_SETPAGEWINDOWHWND,
                        CAST(ADDRESS,PageID), CAST(ADDRESS,pagehandle));
        StartDisplayThread;
        RETURN pagehandle;
    END CreatePage;

(**************************************************************************)

PROCEDURE SetFont (VAR (*IN*) name: CommonSettings.FontName);

    (* Sets the font of the text on this page. *)

    CONST bufsize = CommonSettings.FontNameSize;

    BEGIN
        OS2.WinSetPresParam (pagehandle, OS2.PP_FONTNAMESIZE, bufsize, name);
    END SetFont;

(**************************************************************************)

BEGIN
    ChangeInProgress := FALSE;  shutdown := FALSE;
    pagehandle := OS2.NULLHANDLE;
FINALLY
    shutdown := TRUE;
END StatsPage.

