/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or  http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/* Portions Copyright 2026 RISC OS Developments */
 
/*
 *   Portions Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

// #include "MemCheck:MemCheck.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include "swis.h"
#include "LanMan98SWIs.h"
#include "kernel.h"
#include "LanMan98PGLib/PGwin.h"
#include "LanMan98PGLib/PGicn.h"
#include "LanMan98PGLib/PGmnu.h"
#include "LanMan98PGLib/PGerr.h"
#include "lm98enum.h"

/* What this task calls itself, and so how another copy is recognised */
#define TASK_NAME "LanMan98 Filer"

#define REG_SEARCH (50*1024)
#define MAX_LINE   (256)

static int ci_strcmp(char *s1, char *s2)
{
    while(*s1 && toupper(*s1) == toupper(*s2))
    {
        s1++;
        s2++;
    }
    return toupper(*s1) - toupper(*s2);
}

/* RISC OS pathnames do not distinguish case, so neither can this */
static int ci_strncmp(const char *s1, const char *s2, size_t n)
{
    while(n > 0 && *s1 && toupper(*s1) == toupper(*s2))
    {
        s1++;
        s2++;
        n--;
    }
    return (n == 0) ? 0 : (toupper(*s1) - toupper(*s2));
}

static char *find_reg(char *base)
{
    int i, j;
    char key[] = "unencrypted  reg";

    i = j = 0;
    while(i < REG_SEARCH && key[j] != 0)
        if(key[j++] != base[i++])
            j = 0;
    if(key[j] != 0)
        return NULL;
    else
        return base + i;
}

static void set_info_icons(win_window win)
{
    icn_icon *icon;
    _kernel_swi_regs regs;
    _kernel_oserror *err;
    char *help, buf[256], *cptr;

    icon = IcnButtons(win);
    regs.r[0] = 18;
    regs.r[1] = (int) "LanMan98";
    err = _kernel_swi(OS_Module, &regs, &regs);
    if(err != NULL)
        ErrorFatal("LanMan98 module not loaded");
//    MemCheck_RegisterMiscBlock((void *) regs.r[3], REG_SEARCH);
//    MemCheck_RegisterMiscBlock((void *) regs.r[4], REG_SEARCH);
    help = (char *) (regs.r[3] + *((int *)(regs.r[3] + 20)));
    cptr = strstr(help, "\t");
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    strcpy(buf, cptr + 1);
    cptr = strstr(buf, ")");
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    cptr[1] = '\0';
    IcnSetText(icon[6], buf);
    cptr = find_reg((char *) regs.r[4]);
    if(cptr == NULL)
        Error("LanMan98 module has corrupted help string");
    IcnSetText(icon[7], cptr);
//    MemCheck_UnRegisterMiscBlock((void *) regs.r[3]);
//    MemCheck_UnRegisterMiscBlock((void *) regs.r[4]);
}

static win_window info_window(void)
{
    win_template info_tplt;
    win_window info;

    info_tplt = WinLdTplt("<LanMan98$Dir>.Templates", "info");
    if(info_tplt == NULL)
        ErrorFatal("Can't find \"info\" in \"<LanMan98$Dir>.Templates\"");
    info = WinCreate(info_tplt);
    if(info == NULL)
        ErrorFatal("Can't create \"Info\" window");
    set_info_icons(info);
    return info;
}

typedef struct mount_rec_s *mount_rec_t;

struct mount_rec_s
{
    icn_icon icn;
    int mount;
    int notypes;
    int webdav;
    int working;
    mount_rec_t next;
};

static mount_rec_t rec_list = NULL;

static mount_rec_t rec_construct(void)
{
    mount_rec_t rec;

    rec = malloc(sizeof(*rec));
    rec->next = rec_list;
    rec_list = rec;
    return rec;
}

static void rec_destruct(mount_rec_t rec)
{
    mount_rec_t *recp;

    for(recp = &rec_list; *recp; recp = &(*recp)->next)
    {
        if(*recp == rec)
        {
            *recp = (*recp)->next;
            free(rec);
            return;
        }
    }
    ErrorFatal("Attempt to destruct non-existant mount record");
}

static mount_rec_t rec_find(char *name)
{
    mount_rec_t rec;

    for(rec = rec_list; rec; rec = rec->next)
        if(ci_strcmp(IcnText(rec->icn), name) == 0)
            return rec;
    return NULL;
}

static mnu_menu menu;
static mnu_item item_free, item_dismount, item_share_status;
static win_window connect_window;
static char link_fname[256];
static char link_server[64];     /* the server the link named, if any */

/*
    The server last logged on to from the connect window, kept in memory for
    as long as this runs, so that a share it has just listed can be opened
    without typing the password a second time.
*/
static char logon_server[64];
static char logon_user[64];
static char logon_password[64];

static icn_icon icn_local, icn_server, icn_share, icn_user,
                icn_password, icn_connect, icn_cancel, icn_notypes,
                icn_keep_pw, icn_notypes_label;

static mount_rec_t menu_rec, null_rec = NULL;

static void win_error(char *s);
static void log_tls_issue(const char *fmt, ...);

static void show_discs(void)
{
    _kernel_oscli("Filer_OpenDir -LargeIcons LM98:Discs");
}

static void bar_icon_clickS(void)
{
    char buf[256];
    mount_rec_t rec;

    rec = IcnData();
    if(rec->mount)
    {
        sprintf(buf, "Filer_OpenDir LanMan98%s::%s.$",
                     rec->notypes ? "#notypes" : "",
                     IcnText(rec->icn));

        _kernel_oscli(buf);
    }
    else
    {
        show_discs();
    }
}

static void bar_icon_clickA(void)
{
    mount_rec_t rec;

    rec = IcnData();
    show_discs();
}

static void bar_icon_clickM(void)
{
    menu_rec = IcnData();
    MnuGrey(item_dismount, menu_rec->mount == 0);
    MnuGrey(item_free, menu_rec->mount == 0);
    MnuGrey(item_share_status, menu_rec->mount == 0);
    MnuOpen(menu);
}

static void add_disc(char *name, int mount, int notypes, int webdav)
{
    mount_rec_t rec;

    rec = rec_find(name);
    if(rec == NULL)
    {
        rec = rec_construct();
        rec->icn = IcnBarDisc(webdav ? "webdav" : "!lanman98", name);
        IcnSetData(rec->icn, rec);
        IcnClickS(rec->icn, bar_icon_clickS);
        IcnClickA(rec->icn, bar_icon_clickA);
        IcnClickM(rec->icn, bar_icon_clickM);
    }
    rec->mount = mount;
    rec->notypes = notypes;
    rec->webdav = webdav;
    rec->working = 1;
    if(mount == 0)
        null_rec = rec;
}

static void new_login(int webdav)
{
    link_fname[0] = '\0';
    IcnSetText(icn_local, "");
    IcnSetText(icn_server, webdav ? "https://" : "");
    IcnSetText(icn_share, "");
    IcnSetText(icn_user, "");
    IcnSetText(icn_password, "");
    IcnSetSel(icn_notypes, 0);
    IcnSetGrey(icn_notypes_label, webdav);
    IcnSetGrey(icn_notypes, webdav);
    IcnSetSel(icn_keep_pw, 0);
    WinTitle(connect_window, webdav ? "WebDAV login" : "SMB login");
    WinOpen(connect_window);
    IcnGiveFocus(webdav ? icn_server : icn_local);
}

static void new_smb(void)
{
    new_login(0);
}

static void new_webdav(void)
{
    _kernel_oserror *err;

    /* Run the nested application so its !Run performs the RISC OS and
       CryptRandom checks before loading TLSManager. */
    err = _swix(Wimp_StartTask, _IN(0),
                "Filer_Run <LanMan98$Dir>.!TLSManager");
    if(err)
    {
        WinError(err);
        return;
    }
    new_login(1);
}

static void tls_manager(void)
{
    _kernel_oserror *err;

    /* Start nested applications through the Wimp.  Running Filer_Run
       synchronously from a menu callback is unreliable on some Wimp/Filer
       combinations and previously discarded the error, making the menu
       item appear to do nothing. */
    err = _swix(Wimp_StartTask, _IN(0),
                "Filer_Run <LanMan98$Dir>.!TLSManager");
    if(err) WinError(err);
}

static void dismount_disc(void)
{
    _kernel_oserror *err;

    if(menu_rec)
    {
        err = _swix(LanMan98_OmniOp, _IN(0)|_IN(1), 1, menu_rec->mount);
        if(err) WinError(err);
        IcnBarDelete(menu_rec->icn);
        rec_destruct(menu_rec);
        menu_rec = NULL;
        if(rec_list == NULL)
            add_disc("", 0, 0, 0);
        MnuOpen(NULL);
    }
}

static void show_free(void)
{
    char buf[256];

    sprintf(buf, "ShowFree -fs LanMan98 %s", IcnText(menu_rec->icn));
    _kernel_oscli(buf);

}

/*
    What this share negotiated.  The module writes the lines; this only
    shows them.  Grey until a disc is mounted, the same as Free.
*/
#define STATUS_BUFLEN (200)

static void show_status(void)
{
    _kernel_oserror *err;
    char buf[STATUS_BUFLEN];

    if(!menu_rec || menu_rec->mount == 0)
        return;
    buf[0] = '\0';
    err = _swix(LanMan98_OmniOp, _INR(0,3),
                10, menu_rec->mount, buf, (int) sizeof(buf));
    if(err)
        WinError(err);
    else
        win_error(buf);
}

/*
    Find what is out there and leave an icon for each share.

    The work is done by the module, in a task window so that whoever asked
    can watch it happen - it takes a few seconds and talks to every server
    that answers, which is worth showing rather than appearing to hang.
    What it leaves behind is a file per share in Discs, which is the same
    thing this application writes when a share is connected to by hand, so
    double clicking one opens the connect window with everything but the
    password already in it.
*/
static void discover_shares(void)
{
    /*
        Done here rather than in a task window of its own.  A window would
        have to be watched and then dismissed, and what it has to say is
        already said by the icons appearing: the directory opens when this
        finishes and whatever was found is in it.

        It takes a few seconds - every machine that answers is asked what
        it shares - so the pointer says so while it runs.
    */
    char buf[288];
    char *found;

    _swix(Hourglass_On, 0);
    _kernel_oscli("Discover98 { > null: }");
    _swix(Hourglass_Off, 0);

    /*
        What was found is shown from where the module put it, which is the
        scrap directory rather than Discs: a share that has merely been
        seen is not one the user has asked to keep.  It becomes a kept icon
        in Discs if it is double clicked and the connection succeeds.
    */
    found = getenv("LanMan98$Found");
    if(found && *found && strlen(found) + 24 < sizeof(buf))
    {
        sprintf(buf, "Filer_OpenDir -LargeIcons %s", found);
        _kernel_oscli(buf);
    }
    else
    {
        show_discs();
    }
}

/*
    Only one copy at a time.

    A second copy puts a second icon on the bar, a second of everything on
    its menu and a second of any alert, and it is easily done: installing a
    newer version means running it while the old one is still there.

    The task list is read before this copy announces itself to the Wimp, so
    everything found is somebody else and this does not have to know its own
    handle to avoid asking itself to go.  The message is then sent once
    there is a task to send it from.

    Nothing is lost by the older copy going.  The mounts belong to the
    module rather than to the front end, so they stay up, and this copy
    reads them back as it starts.
*/
static int other_copy[8];
static int other_copies = 0;

static void find_other_copies(void)
{
    _kernel_swi_regs r;
    int buf[64];
    int ctx;

    ctx = 0;
    do
    {
        r.r[0] = ctx;
        r.r[1] = (int) buf;
        r.r[2] = sizeof(buf);
        if(_kernel_swi(TaskManager_EnumerateTasks, &r, &r) != NULL)
            return;                 /* no task manager: never mind */
        ctx = r.r[0];
        {
            int *p = buf;
            int *end = (int *) r.r[1];

            while(p + 4 <= end)
            {
                char *nm = (char *) p[1];

                if(nm && (ci_strcmp(nm, TASK_NAME) == 0)
                      && (other_copies < 8))
                    other_copy[other_copies++] = p[0];
                p += 4;
            }
        }
    }
    while(ctx >= 0);
}

static void quit_other_copies(void)
{
    _kernel_swi_regs r;
    int msg[5];
    int i;

    for(i = 0; i < other_copies; i++)
    {
        msg[0] = sizeof(msg);       /* size */
        msg[1] = 0;                 /* sender, filled in by the Wimp */
        msg[2] = 0;                 /* my ref */
        msg[3] = 0;                 /* your ref: not a reply */
        msg[4] = 0;                 /* Message_Quit */
        r.r[0] = 17;                /* User_Message */
        r.r[1] = (int) msg;
        r.r[2] = other_copy[i];
        _kernel_swi(Wimp_SendMessage, &r, &r);
    }
}

static void mk_menu(void)
{
    mnu_item item[8];
    mnu_item login_item[3];
    mnu_item status_item[3];

    login_item[0] = MnuAction("SMB login...", new_smb);
    login_item[1] = MnuAction("WebDAV login...", new_webdav);
    login_item[2] = NULL;
    status_item[0] = item_share_status = MnuAction("Share status", show_status);
    status_item[1] = MnuAction("TLS Manager...", tls_manager);
    status_item[2] = NULL;
    item[0] = MnuSubmenu("Info", MnuWindow(info_window()));
    item[1] = MnuAction("Show discs", show_discs);
    item[2] = MnuAction("Discover", discover_shares);
    item[3] = MnuSetAct(MnuSubmenu("Login...", MnuMenu("Login", login_item)), new_smb);
    item[4] = item_dismount = MnuAction("Dismount", dismount_disc);
    item[5] = MnuSubmenu("Status", MnuMenu("Status", status_item));
    item[6] = item_free = MnuAction("Free", show_free);
    item[7] = NULL;
    menu = MnuMenu("LanMan98", item);
}

static void win_error(char *s)
{
    _kernel_oserror err;

    err.errnum = 0;
    strcpy(err.errmess, s);
    WinError(&err);
}

static void save_share(void)
{
    static const char link_fname_fmt[]  = "LM98:Discs.%s";
    static const char settype_cmd_fmt[] = "SetType %s &069";
    char buf[256];
    FILE *f;

    /*
        Where the link gets written back.

        A share *Discover98 found is left in the scrap directory, and
        connecting to it is what promotes it into Discs: that is what makes
        a share which was merely seen into one that is kept.  So a link
        opened from there is written to Discs under its local name rather
        than back to where it came from, which would not outlive the next
        discovery anyway.

        Anything else is written back where it was.  This used to promote a
        link opened from anywhere that was not Discs, which meant a share
        the user had filed somewhere of their own - moved onto another disc,
        or into a directory with the rest of a group - reappeared in Discs
        every time it was used.  With a handful of shares that is untidy;
        with dozens it makes them impossible to organise at all.
    */
    {
        char *found = getenv("LanMan98$Found");

        if(found && *found && ci_strncmp(link_fname, found, strlen(found)) == 0)
            link_fname[0] = '\0';
    }

    if (link_fname[0] == '\0')
    {
        const char *icn_text = IcnText(icn_local);

        if(sizeof(link_fname_fmt) - 2 + strlen(icn_text) <= sizeof(link_fname))
            sprintf(link_fname, link_fname_fmt, IcnText(icn_local));
    }
    f = fopen(link_fname, "w");
    if(f)
    {
        fprintf(f, "Server: %s\n", IcnText(icn_server));
        fprintf(f, "Share: %s\n", IcnText(icn_share));
        if(*IcnText(icn_user))
            fprintf(f, "User: %s\n", IcnText(icn_user));
        if(*IcnText(icn_password))
        {
            if(IcnSel(icn_keep_pw))
                fprintf(f, "Password: %s\n", IcnText(icn_password));
            else
                fprintf(f, "Password: *****\n");
        }
        if(IcnSel(icn_notypes))
            fprintf(f, "Notypes: Y\n");
        fclose(f);
        if(sizeof(settype_cmd_fmt) - 2 + strlen(link_fname) <= sizeof(buf))
        {
            sprintf(buf, settype_cmd_fmt, link_fname);
            _kernel_oscli(buf);
        }
    }
}

static void str2up(char *s, char *t, int size)
{
    int len, i;

    len = strlen(t) + 1;
    if(len > size)
        len = size;
    for(i = 0; i < len; i++)
        s[i] = toupper(t[i]);
    s[size-1] = 0;
}

#define MAX_SHARE (64)
/*
    Log on to a server and show what it shares.

    Connecting needs a share to connect to, and a server that says nothing
    until somebody has logged on cannot be asked which ones it has.  So
    Connect with no share named logs on with the name and password given,
    has the module list that server's shares into its folder of the
    discovery window, and opens the folder: the share wanted is then double
    clicked like any other.
*/
static void list_shares(void)
{
    char cmd[256];
    char into[64];
    char *server, *user, *password, *found, *folder;
    _kernel_oserror *err;

    server = IcnText(icn_server);
    user = IcnText(icn_user);
    password = IcnText(icn_password);
    if(strchr(server, '"') || strchr(user, '"') || strchr(password, '"'))
    {
        win_error("A server, name or password with \" in it cannot be used here");
        return;
    }

    /* The folder the LogOn icon was opened from, if it was one of them */
    into[0] = '\0';
    found = getenv("LanMan98$Found");
    if(link_fname[0] && found && *found &&
       (ci_strncmp(link_fname, found, strlen(found)) == 0) &&
       (link_fname[strlen(found)] == '.') &&
       (ci_strcmp(server, link_server) == 0))
    {
        char *leaf = link_fname + strlen(found) + 1;
        char *end = strchr(leaf, '.');

        if(end && ((end - leaf) < (int) sizeof(into)))
        {
            memcpy(into, leaf, end - leaf);
            into[end - leaf] = '\0';
        }
    }

    if(strlen(server) + strlen(into) + strlen(user) + strlen(password) + 64 > sizeof(cmd))
    {
        win_error("The server, name and password are too long to use here");
        return;
    }
    if(into[0])
        sprintf(cmd, "Discover98 -server \"%s\" -into %s \"%s\" \"%s\" { > null: }",
                server, into, user, password);
    else
        sprintf(cmd, "Discover98 -server \"%s\" \"%s\" \"%s\" { > null: }",
                server, user, password);

    _swix(Hourglass_On, 0);
    err = _swix(OS_CLI, _IN(0), cmd);
    _swix(Hourglass_Off, 0);
    if(err)
    {
        if(err->errnum != ERR_SERVER_UNREACHABLE)
        {
            WinOpen(connect_window);
            IcnGiveFocus(icn_password);
        }
        WinError(err);
        return;
    }

    if((strlen(server) < sizeof(logon_server)) &&
       (strlen(user) < sizeof(logon_user)) &&
       (strlen(password) < sizeof(logon_password)))
    {
        strcpy(logon_server, server);
        strcpy(logon_user, user);
        strcpy(logon_password, password);
    }

    WinClose(connect_window);
    folder = getenv("LanMan98$FoundServer");
    if(folder && *folder && (strlen(folder) + 28 < sizeof(cmd)))
    {
        sprintf(cmd, "Filer_OpenDir -LargeIcons %s", folder);
        _kernel_oscli(cmd);
    }
}

static void log_tls_issue(const char *fmt, ...)
{
    char log_path[256];
    int log_len;
    va_list ap;
    FILE *f;

    if(_swix(OS_ReadVarVal, _INR(0,4) | _OUT(2), "LanMan98$TLSLog",
             log_path, sizeof(log_path) - 1, 0, 0, &log_len))
        return;
    log_path[log_len] = '\0';
    f = fopen(log_path, "a");
    if(!f) return;
    va_start(ap, fmt);
    vfprintf(f, fmt, ap);
    va_end(ap);
    fputc('\n', f);
    fclose(f);
}

/*
    A saved WebDAV mount connects as soon as its link file is opened, without
    going through WebDAV Login first.  Run the nested application's startup
    file synchronously here so its UtilityModule and CryptRandom checks, and
    the TLSManager load itself, finish before LanMan98 is asked to connect.
*/
static int ensure_tls_manager(void)
{
    _kernel_oserror *err;

    if(!_swix(OS_Module, _INR(0,1), 18, "TLSManager"))
        return 1;

    log_tls_issue("loading TLSManager from !LanMan98 before HTTPS connect");
    err = _swix(OS_CLI, _IN(0),
                "Obey <LanMan98$Dir>.!TLSManager.!Run");
    if(err)
    {
        log_tls_issue("TLSManager startup: %s",
                      err->errmess[0] ? err->errmess : "no error given");
        WinError(err);
        return 0;
    }
    if(_swix(OS_Module, _INR(0,1), 18, "TLSManager"))
    {
        log_tls_issue("TLSManager startup completed without loading the module");
        win_error("TLSManager 0.07 could not be loaded");
        return 0;
    }
    log_tls_issue("TLSManager loaded before HTTPS connect");
    return 1;
}

static void connect(void)
{
    _kernel_oserror *err;
    _kernel_swi_regs r;
    char buf[MAX_SHARE];
    int dav = !ci_strncmp(IcnText(icn_server), "https://", 8) ||
              !ci_strncmp(IcnText(icn_server), "http://", 7);

    log_tls_issue("connect requested for server '%s', share '%s', local '%s', user '%s'",
                  IcnText(icn_server), IcnText(icn_share), IcnText(icn_local), IcnText(icn_user));
    if(*IcnText(icn_server) == 0)
    {
        log_tls_issue("connect: \"Server\" not specified");
        win_error("\"Server\" not specified");
    }
    else if(!dav && *IcnText(icn_share) == 0)
        list_shares();
    else if(*IcnText(icn_local) == 0)
    {
        log_tls_issue("connect: \"Local name\" not specified");
        win_error("\"Local name\" not specified");
    }
    else
    {
        if(dav)
        {
            /* A generic WebDAV server has no native RISC OS catalogue.
               LanMan98 must retain types in its reversible ,xxx names. */
            IcnSetSel(icn_notypes, 0);
            if(strlen(IcnText(icn_share)) >= sizeof(buf))
            {
                log_tls_issue("connect: WebDAV collection path is too long");
                win_error("WebDAV collection path is too long; put it in the server URL");
                return;
            }
            strcpy(buf, IcnText(icn_share));
        }
        else str2up(buf, IcnText(icn_share), MAX_SHARE);
        if(!ci_strncmp(IcnText(icn_server), "https://", 8)
           && !ensure_tls_manager())
            return;
        r.r[0] = 0;
        r.r[1] = (int) IcnText(icn_server);
        r.r[2] = (int) IcnText(icn_user);
        r.r[3] = (int) IcnText(icn_password);
        r.r[4] = (int) IcnText(icn_local);
        r.r[5] = (int) buf;
        err = _kernel_swi(LanMan98_OmniOp, &r, &r);
        if(err)
        {
            log_tls_issue("connect to %s: %s", IcnText(icn_server),
                          err->errmess[0] ? err->errmess : "no error given");

            /*
                A failure that is about the machine rather than about the
                user is only reported.

                Opening the connect window with the password ready to be
                retyped is the right answer to a refused logon and the
                wrong one to a server that is switched off: it says the
                password was the problem when the message says it was not,
                and invites the user to type it again to no purpose.
            */
            if(err->errnum != ERR_SERVER_UNREACHABLE)
            {
                WinOpen(connect_window);
                IcnGiveFocus(icn_password);
            }
            WinError(err);
        }
        else
        {
            save_share();
            add_disc(IcnText(icn_local), r.r[1], IcnSel(icn_notypes), dav);
            if(null_rec)
            {
                IcnBarDelete(null_rec->icn);
                rec_destruct(null_rec);
                null_rec = NULL;
            }
            WinClose(connect_window);
        }
    }
}

static void cancel(void)
{
    WinClose(connect_window);
}

static int key_press(int k)
{
    if(k != '\r')
        return 0;
    connect();
    return 1;
}

static void mk_connect_window()
{
    win_template tplt;
    icn_icon *icn;

    tplt = WinLdTplt("<LanMan98$Dir>.Templates", "connect");
    if(tplt == NULL)
        ErrorFatal("Can't find \"connect\" in \"<LanMan98$Dir>.Templates\"");
    connect_window = WinCreate(tplt);
    if(connect_window == NULL)
        ErrorFatal("Can't create \"Connect\" window");
    WinPress(connect_window, key_press);
    icn = IcnButtons(connect_window);
    icn_local        = icn[5];
    icn_server	     = icn[6];
    icn_share	     = icn[7];
    icn_user	     = icn[8];
    icn_password     = icn[9];
    icn_connect	     = icn[10];
    icn_cancel	     = icn[11];
    icn_notypes_label = icn[12];
    icn_notypes	     = icn[13];
    icn_keep_pw	     = icn[15];
    IcnClickS(icn_connect, connect);
    IcnClickA(icn_connect, connect);
    IcnClickS(icn_cancel, cancel);
    IcnClickA(icn_cancel, cancel);
}

// static char *tag_read(char *tag, char *buf)
// {
//     return (memcmp(tag, buf, strlen(tag)) == 0)
//                ? strtok(buf + strlen(tag), " \t\n")
//                : NULL;
// }

static char *tag_read(char *tag, char *buf)
{
    char *ret;

    if(memcmp(tag, buf, strlen(tag)) != 0)
        return NULL;
    buf += strlen(tag);
    buf += strspn(buf, " \t");
    ret = strchr(buf, '\n');
    if(ret) *ret = 0;
    return buf;
}

static int data_open(int type, char *name)
{
    FILE *f;
    char buf[256], *s;
    int needs_password;

    if(type != 0x069)
        return 0;
    f = fopen(name, "r");
    if(f)
    {
        log_tls_issue("open saved share: %s", name ? name : "(null)");
        if(strlen(name) + 1 <= sizeof(link_fname))
            sprintf(link_fname, "%s", name);
        else
            link_fname[0] = '\0';
        link_server[0] = '\0';

        s = strrchr(name, '.');
        name = s ? s + 1 : name;
        s = strrchr(name, ':');
        name = s ? s + 1 : name;
        IcnSetText(icn_local, name);
        IcnSetText(icn_server, "");
        IcnSetText(icn_share, "");
        IcnSetText(icn_user, "");
        IcnSetText(icn_password, "");
        IcnSetSel(icn_notypes, 0);
        IcnSetGrey(icn_notypes_label, 0);
        IcnSetGrey(icn_notypes, 0);
        IcnSetSel(icn_keep_pw, 0);
        needs_password = 0;
        while(!feof(f))
        {
            if(fgets(buf, 256, f))
            {
                s = tag_read("Server:", buf);
                if(s)
                {
                    IcnSetText(icn_server, s);
                    if(strlen(s) < sizeof(link_server))
                        strcpy(link_server, s);
                }
                s = tag_read("Share:", buf);
                if(s) IcnSetText(icn_share, s);
                s = tag_read("User:", buf);
                if(s) IcnSetText(icn_user, s);
                s = tag_read("Password:", buf);
                if(s)
                {
                    needs_password = (strcmp(s, "*****") == 0);
                    IcnSetSel(icn_keep_pw, !needs_password);

                    if(!needs_password)
                        IcnSetText(icn_password, s);
                }
                s = tag_read("Notypes:", buf);
                if(s && (s[0] == 'y' || s[0] == 'Y'))
                    IcnSetSel(icn_notypes, 1);
            }
        }
        fclose(f);

        if(!ci_strncmp(IcnText(icn_server), "https://", 8) ||
           !ci_strncmp(IcnText(icn_server), "http://", 7))
        {
            IcnSetSel(icn_notypes, 0);
            IcnSetGrey(icn_notypes_label, 1);
            IcnSetGrey(icn_notypes, 1);
            WinTitle(connect_window, "WebDAV login");
        }
        else
        {
            WinTitle(connect_window, "SMB login");
        }

        /*
            A LogOn icon names no share, and its own name is no use as the
            local name of whatever gets connected to from it.  The folder it
            is in is named after its server, so that name is used instead.
        */
        if((*IcnText(icn_share) == 0) && link_fname[0] &&
           ci_strncmp(IcnText(icn_server), "https://", 8) != 0 &&
           ci_strncmp(IcnText(icn_server), "http://", 7) != 0)
        {
            char *last = strrchr(link_fname, '.');

            if(last)
            {
                char *first;

                *last = '\0';
                first = strrchr(link_fname, '.');
                IcnSetText(icn_local, first ? first + 1 : link_fname);
                *last = '.';
            }
        }

        /*
            A share of the server just logged on to from here connects with
            the name and password given for it, rather than asking for the
            password again straight away.
        */
        if(needs_password && (*IcnText(icn_share) != 0) && logon_server[0] &&
           (ci_strcmp(IcnText(icn_server), logon_server) == 0) &&
           ((*IcnText(icn_user) == 0) ||
            (ci_strcmp(IcnText(icn_user), logon_user) == 0)))
        {
            IcnSetText(icn_user, logon_user);
            IcnSetText(icn_password, logon_password);
            needs_password = 0;
        }

        if(needs_password)
        {
            WinOpen(connect_window);
            IcnGiveFocus(icn_password);
        }
        else
        {
            connect();
        }
    }
    return 1;
}

static void update_mounts(void)
{
    mount_rec_t rec, *recp;

    for(rec = rec_list; rec; rec = rec->next)
        rec->working = 0;

    for(LM98EnumBegin(); LM98EnumGoing(); LM98EnumNext())
    {
        rec = rec_find(LM98EnumName());
        if(rec)
        {
            rec->working = 1;
            rec->mount = LM98EnumMount();
            // Probably has that value already, but if it doesn't then
            // it should have, and the old value is stale (need not be closed)
        }
        else
        {
            add_disc(LM98EnumName(), LM98EnumMount(), 0, LM98EnumWebDAV());
            if(null_rec)
            {
                IcnBarDelete(null_rec->icn);
                rec_destruct(null_rec);
                null_rec = NULL;
            }
        }
    }

    if(null_rec)
        null_rec->working = 1;

    recp = &rec_list;
    while(*recp)
    {
        if((*recp)->working)
        {
            recp = &(*recp)->next;
        }
        else
        {
            rec = *recp;
            *recp = rec->next;
            IcnBarDelete(rec->icn);
            free(rec);
        }
    }

    if(rec_list == NULL)
        add_disc("", 0, 0, 0);
}

/*
    Saying that a server has appeared.

    The module asks the network what is on it on a timer and puts the name
    of anything answering for the first time in LanMan98$New.  That is read
    here on the same wake the mount list is already looked at on, and said
    in a window.

    Never an error box.  Wimp_ReportError is modal: it holds the whole
    desktop until somebody clicks it, and for something nobody asked for -
    arriving mid-drag, or while the machine is unattended - that is worse
    than saying nothing at all.  So this is an ordinary window that takes
    itself away again, either when clicked or after a while on screen.

    A !LanMan98 whose Templates predates this has no "alert" in it.  That
    must not stop the Filer running, so the window is built the first time
    there is something to say and, if it cannot be, the whole thing goes
    quiet for the rest of the session rather than complaining once per
    server for ever.
*/

#define ALERT_LINGER (1500)     /* centiseconds it stays on screen */
/*
    How much text the message icon will hold.

    Not arithmetic: the desktop draws in a proportional font whose size is
    the user's business, so the width of a string is not knowable from its
    length.  This is a deliberately cautious budget for the 1368 OS units
    the icon spans - about 34 OS units a character, which is roughly twice
    the fixed system font and leaves room for a wider one.  Beyond it the
    message counts the servers instead of naming them, which is short
    whatever the font.
*/
#define ALERT_TEXT_MAX (40)
#define ALERT_MARGIN (32)       /* gap from the top right of the screen */

static win_window alert_win = NULL;
static int alert_usable = 1;
static int alert_deadline = 0;  /* when to close; 0 while not showing */

static int monotonic(void)
{
    int t;

    t = 0;
    _swix(OS_ReadMonotonicTime, _OUT(0), &t);
    return t;
}

static void alert_hide(void)
{
    if(alert_deadline)
    {
        alert_deadline = 0;
        WinClose(alert_win);
    }
}

static void alert_click(int x, int y)
{
    (void) x;
    (void) y;
    alert_hide();
}

static void alert_icon_click(void)
{
    alert_hide();
}

static int alert_build(void)
{
    win_template tplt;
    icn_icon *icon;

    if(alert_win)
        return 1;
    if(!alert_usable)
        return 0;
    tplt = WinLdTplt("<LanMan98$Dir>.Templates", "alert");
    if(tplt != NULL)
        alert_win = WinCreate(tplt);
    if(alert_win == NULL)
    {
        alert_usable = 0;
        return 0;
    }
    WinClickS(alert_win, alert_click);
    icon = IcnButtons(alert_win);
    IcnClickS(icon[0], alert_icon_click);
    IcnClickS(icon[1], alert_icon_click);
    return 1;
}

/*
    Put it in the top right corner, which is where something nobody asked
    for belongs: away from where the pointer is likely to be, and clear of
    the icon bar.
*/
static void alert_place(void)
{
    int x0, y0, x1, y1;
    int xwl, ywl, xeig, yeig;

    x0 = y0 = x1 = y1 = 0;
    WinExtent(alert_win, &x0, &y0, &x1, &y1);
    xwl = ywl = 0;
    xeig = yeig = 1;
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 11, &xwl);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 12, &ywl);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 4, &xeig);
    _swix(OS_ReadModeVariable, _INR(0,1)|_OUT(2), -1, 5, &yeig);
    WinPlace(alert_win,
             ((xwl + 1) << xeig) - (x1 - x0) - ALERT_MARGIN,
             ((ywl + 1) << yeig) - ALERT_MARGIN);
    /*
        WinPlace positions a window; it does not open one.  It opens the
        window to move it and then closes it again unless it was already
        open, so a window being shown for the first time has to be opened
        after being placed rather than by placing it.
    */
    WinOpen(alert_win);
}

static void alert_show(char *names)
{
    char buf[192];
    icn_icon *icon;

    if(!alert_build())
        return;
    icon = IcnButtons(alert_win);
    /*
        The module separates names with commas, so more than one name means
        more than one machine.

        Name them where they fit and count them where they do not: the
        window is a fixed width and the system font a fixed pitch, so a
        long enough list would otherwise be centred and clipped at both
        ends, which reads as neither a name nor a number.
    */
    {
        int several = (strchr(names, ',') != NULL);
        int n, i;

        n = 1;
        for(i = 0; names[i]; i++)
            if(names[i] == ',')
                n++;

        if((int) strlen(names) + (several ? 23 : 22) <= ALERT_TEXT_MAX)
            sprintf(buf, "%s %s now sharing files", names,
                    several ? "are" : "is");
        else
            sprintf(buf, "%d servers are now sharing files", n);
    }
    /*
        Close it first if it is already up.  Changing the text under an
        open window leaves the Wimp redrawing it at its own convenience,
        and a second server arriving while the first is still on screen
        would otherwise show the old message.
    */
    alert_hide();
    IcnSetText(icon[0], buf);
    alert_place();
    alert_deadline = monotonic() + ALERT_LINGER;
}

static void alert_poll(void)
{
    char *names;

    names = getenv("LanMan98$New");
    if(names != NULL && *names != 0)
    {
        alert_show(names);
        /*
            Taken, so the module can say it again for the next machine.
            Cleared even when there was no window to show it in, or an old
            Templates would leave it set and this would run every wake.
        */
        _kernel_oscli("Unset LanMan98$New");
    }
    else if(alert_deadline && monotonic() - alert_deadline >= 0)
    {
        alert_hide();
    }
}


int main()
{
    int *poll_word = NULL;
    _kernel_oserror *err;

//    MemCheck_Init();
//    MemCheck_RedirectToFilename("<LanMan98$Dir>.FilerMem");
//    MemCheck_InterceptSCLStringFunctions();
//    MemCheck_SetQuitting(0, 0);
//    MemCheck_OutputBlocksInfo();
    find_other_copies();        /* while this is not yet a task itself */
    WinInit(TASK_NAME);
    quit_other_copies();
    mk_menu();
    mk_connect_window();
    _kernel_oscli("IconSprites <LanMan98$Dir>.webdav");
    WinDataOpen(data_open);
    add_disc("", 0, 0, 0);
    err = _swix(LanMan98_PollWord, _OUT(0), &poll_word);
    if(err) ErrorFatal("LamMan98 module not present");
//    MemCheck_RegisterMiscBlock(poll_word, sizeof(int));
    update_mounts();
    while(1)
    {
        if(*poll_word)
        {
            err = _swix(LanMan98_PollWord, _OUT(0), &poll_word);
            if(err) ErrorFatal("LamMan98 module not present");
            update_mounts();
        }
        alert_poll();
        WinSleep(50);
    }
    return 0;
}
