/*
 *  LIST.C
 *
 *  Written on 10-Jul-94 by John Dennis and released to the public domain.
 *
 *  Lists the messages in the messagebase.
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include "addr.h"
#include "nedit.h"
#include "msged.h"
#include "memextra.h"
#include "specch.h"
#include "winsys.h"
#include "menu.h"
#include "main.h"
#include "strextra.h"
#include "keys.h"
#include "dosmisc.h"
#include "help.h"
#include "maintmsg.h"
#include "nshow.h"
#include "dlist.h"
#include "list.h"
#include "screen.h"
#include "charset.h"
#include "config.h"
#include "group.h"

static int long_subj;
static int display_address = 1;

static void getheader(unsigned long n, MLHEAD * h, int check_sel);
static void showit(MLHEAD * h, int y, int sel);

static DLIST *ulist;

static char *forgroupmenu[] =
{
    "Move Message(s)",
    "Copy Message(s)",
    "Redirect Message(s)",
    "Forward Message(s)",
    NULL
};

/*
 *  Checks to see if the message has been selected.
 */

static int ulistFindUid(unsigned long uid)
{
    DLISTNODE *p_node;
    if (ulist == NULL)
    {
        return 0;
    }
    p_node = dlistTravFirst(ulist);
    while (p_node != NULL)
    {
        unsigned long *puid;
        puid = dlistGetElement(p_node);
        if (*puid == uid)
        {
            return 1;
        }
        p_node = dlistTravNext(p_node);
    }
    return 0;
}

/*
 *  Removes a selection from the list.
 */

static void ulistDropUid(unsigned long uid)
{
    DLISTNODE *p_node;
    if (ulist == NULL)
    {
        return;
    }
    p_node = dlistTravFirst(ulist);
    while (p_node != NULL)
    {
        unsigned long *puid;
        puid = dlistGetElement(p_node);
        if (*puid == uid)
        {
            dlistDropNode(ulist, p_node);
        }
        p_node = dlistTravNext(p_node);
    }
}

/*
 *  Adds a new message to the list of selected messages.
 */

static int ulistAddUid(unsigned long uid)
{
    unsigned long *puid;
    DLISTNODE *p_node;

    if (ulist == NULL)
    {
        ulist = dlistInit();
        if (ulist == NULL)
        {
            return 0;
        }
    }
    puid = xmalloc(sizeof *puid);
    if (puid == NULL)
    {
        return 0;
    }
    p_node = dlistCreateNode(puid);
    if (p_node == NULL)
    {
        xfree(puid);
        return 0;
    }
    *puid = uid;
    dlistAddNode(ulist, p_node);
    return 1;
}

/*
 *  Frees the list of msgnumbers.
 */

static void ulistTerm(void)
{
    DLISTNODE *p_node;
    if (ulist == NULL)
    {
        return;
    }
    p_node = dlistTravFirst(ulist);
    while (p_node != NULL)
    {
        unsigned long *puid;
        puid = dlistGetElement(p_node);
        xfree(puid);
        p_node = dlistTravNext(p_node);
    }
    dlistTerm(ulist);
    ulist = NULL;
}

/*
 *  Re-displays the entire screen.
 */

static void update(MLHEAD * headers, unsigned long i, int y)
{
    TTBeginOutput();
    while (i <= CurArea.messages && y <= maxy - 4)
    {
        getheader(i, &headers[y - 1], 1);
        showit(&headers[y - 1], y, 0);
        i++;
        y++;
    }
    if (y <= (maxy - 4))
    {
        WndClear(1, y, maxx - 2, maxy - 4, cm[LS_NTXT]);
    }
    TTEndOutput();
}

/*
 *  Shows a header on the screen.
 */

static void showit(MLHEAD * h, int y, int sel)
 {
    unsigned long msgn;
    char line[384];
    char msgnbuf[9];
    int l;
    char *cp;

    TTBeginOutput();

    msgn = SW->showrealmsgn ? h->umsgid : h->msgnum;
    sprintf(msgnbuf, "%5ld %c", msgn, h->sel ? SC14 : ' ');
    l = strlen(msgnbuf) - 1;

    if (long_subj)
    {
        sprintf(line, "%c%s%-15.15s %-70s", h->times_read > 0 ? '*' : ' ',
          msgnbuf, h->fr_name, h->subj);
    }
    else
    {
        sprintf(line, "%c%s%-15.15s %-15.15s %-70s",
          h->times_read > 0 ? '*' : ' ', msgnbuf, h->fr_name,
          h->to_name, h->subj);
    }

    /* caveat the broken subject line!!! */
    for (cp=line; *cp; cp++)
        if ((*cp >= 0 && *cp < ' ') && (*cp != SC14))
            *cp = ' ';

    if (sel)
    {
        if (l>0)
            WndPutsn(1, y, l, cm[LS_STXT], line);
        if (l>1)
            WndPutsn(1+l, y, 1, cm[LS_STXT] | F_ALTERNATE, line + l);
        if (l + 1 < maxx - 2)
            WndPutsn(1+l+1, y, (maxx - 2) - (l + 1), cm[LS_STXT],
                     line+l+1);
    }
    else if (stricmp(h->to_name, ST->username) == 0 ||
      stricmp(h->fr_name, ST->username) == 0)
    {
        if (l > 0)
            WndPutsn(1, y, l, cm[LS_ITXT], line);
        if (l>1)
            WndPutsn(1+l, y, 1, cm[LS_ITXT] | F_ALTERNATE, line + l);
        if (l + 1 < maxx - 2)
            WndPutsn(l + 2, y, (maxx - 2) - (l + 1), cm[LS_ITXT],
                     line + l + 1);
    }
    else
    {
        if (l > 0)
            WndPutsn(1, y, l, cm[LS_NTXT], line);
        if (l>1)
            WndPutsn(1+l, y, 1, cm[LS_NTXT] | F_ALTERNATE, line + l);
        if (l + 1 < maxx - 2)
            WndPutsn(l + 2, y, (maxx - 2) - (l + 1), cm[LS_NTXT],
                     line + l + 1);
    }

    TTEndOutput();

}

/*
 *  Gets a header from the msgbase.
 */

static void getheader(unsigned long n, MLHEAD * h, int check_sel)
{
    msg *x;
    char *text;
    char *charset; int level;
    char *tokens[5];
    LOOKUPTABLE *ltable = NULL;

    /* Read the message header */
    
    memset(h, 0, sizeof *h);
    x = MsgReadHeader((unsigned int)n, RD_ALL);
    if (x == NULL)
    {
        return;
    }
    

    /* Search the CHRS kludge */

    if (ST->input_charset != NULL)
    {
	charset = xstrdup(ST->input_charset);
	level = 2;
    }
    else
    {
	charset = xstrdup("ASCII");
        level = 2; /* ASCII 2 is nonsense, but get_readtable will return
                      the correct table */
    }

    while ((text = MsgReadText((unsigned int)n)) != NULL)
    {
        if (*text == '\01')
        {
            if (strncmp(text + 1, "CHRS:", 5) == 0)
            {
		memset(tokens, 0, sizeof(tokens));
		parse_tokens(text + 7, tokens, 2);
		if (tokens[1] != NULL)
		{

                    if ( have_readtable(tokens[0], atoi(tokens[1])) ||
                        ST->input_charset == NULL)
                    {
                        release(charset);
                        charset = xstrdup(tokens[0]);
                        level = atoi(tokens[1]);
                    }
                }
            }
            release(text);
        }
        else
        {
            /* Kludges are over! */
            release(text); 
            /* break;  this does not work, we have to read the whole msg */
        }
    }

    MsgClose();

    ltable = get_readtable(charset, level);
    release(charset);

    /* copy the header info */

    h->msgnum = n;
    h->umsgid = x->msgnum;
    h->to_net = x->to.net;
    h->to_node = x->to.node;
    h->fr_net = x->from.net;
    h->fr_node = x->from.node;
    h->times_read = x->times_read;
    if (check_sel)
    {
        h->sel = ulistFindUid(x->msgnum);
    }
    else
    {
        h->sel = 0;
    }

    text = translate_text(x->subj, ltable);
    strncpy(h->subj, text, 72);
    h->subj[72] = '\0';
    release(text);
    
    text = translate_text(x->isto, ltable);
    strncpy(h->to_name, text, 36);
    h->to_name[36] = '\0';
    release(text);

    text = translate_text(x->isfrom, ltable);
    strncpy(h->fr_name, text, 36);
    h->fr_name[36] = '\0';
    release(text);

    dispose(x);
}

/*
 *  Deletes the selected messages, or the current one if none.
 */

static void DeleteMsgs(unsigned long *CurrMsgn)
{
    if (ulist == NULL || dlistIsEmpty(ulist))
    {
/*
        if (!confirm("Erase message?"))
        {
            return;
        }
*/
        CurArea.current = *CurrMsgn;
        deletemsg();
    }
    else
    {
        int confirm_temp;
        DLISTNODE *p_node;
        unsigned long msgn, oldmsgn;
        char messagetxt[80];

        if (!confirm("Erase all selected messages?"))
        {
            return;
        }
        if (!OpenMsgWnd(50, 6, "Deleting Messages", NULL, 0, 0))
        {
            return;
        }
        SendMsgWnd("Press Esc to interrupt", 2);

        confirm_temp = SW->confirmations;
        SW->confirmations = 0;

        oldmsgn = 0L;

        p_node = dlistTravFirst(ulist);
        while (p_node != NULL)
        {
            unsigned long *puid;

            if (KeyHit() && GetKey() == Key_Esc)
            {
                p_node = NULL;
                break;
            }

            puid = dlistGetElement(p_node);
            msgn = UidToMsgn(*puid);

            sprintf(messagetxt, "Working on message #%lu", 
                    SW->showrealmsgn ? *puid: msgn);
            SendMsgWnd(messagetxt, 1);

            if (oldmsgn == 0L)
            {
                oldmsgn = msgn - 1;
            }
            if (msgn != 0L)
            {
                CurArea.current = msgn;
                deletemsg();
            }
            p_node = dlistTravNext(p_node);
        }

        if (oldmsgn == 0L)
        {
            oldmsgn = *CurrMsgn;
        }
        *CurrMsgn = oldmsgn;
        CurArea.current = oldmsgn;
        SW->confirmations = confirm_temp;

        CloseMsgWnd();
    }
}

/*
 *  Forwards, redirects, moves or copies a group of messages.
 */

static int movemsgs(int rc, int to_area)
{
    int clear = (to_area == -1);
    
    if (rc == 2 || rc == 3)
    {
        DrawHeader();
    }
    switch (rc)
    {
    case 0:                    /* Move */
        to_area = move_msg(to_area);
        break;

    case 1:                    /* Copy */
        to_area = copy_msg(to_area);
        break;

    case 2:                    /* Redirect */
        to_area = redirect_msg(to_area);
        break;

    case 3:                    /* Forward */
        to_area = forward_msg(to_area);
        break;

    case -1:                   /* Escape */
        return -1;
    }

    if (clear)
    {
        TTBeginOutput();
        WndClearLine(0, cm[MN_NTXT]);
        WndWriteStr(2, 0, cm[LS_TTXT], CurArea.description);

        if (rc == 2 || rc == 3)
        {
            int i;
            for (i = 1; i <= 5; i++)
            {
                WndClearLine(i, cm[MN_NTXT]);
            }
            WndBox(0, 1, maxx - 1, maxy - 2, cm[LS_BTXT], SBDR);
        }
        TTEndOutput();
    }
    return to_area;
}

/*
 *  Moves, copies, redirects or forwards the selected messages, or the
 *  current one if none.
 */

static void MoveMsgs(unsigned long *CurrMsgn)
{
    int rc;
    WND *hCurr;

    rc = DoMenu((maxx / 2) - 10, (maxy / 2) - 1, (maxx / 2) + 9, (maxy / 2) + 2,
      forgroupmenu, 0, SELBOX_MOVEMSG, "");

    if (ulist == NULL || dlistIsEmpty(ulist))
    {
        CurArea.current = *CurrMsgn;
        hCurr = WndTop();
        WndCurr(hMnScr);
        groupmove = 1;
        movemsgs(rc, -1);
        groupmove = 0;
        WndCurr(hCurr);
    }
    else
    {
        DLISTNODE *p_node, *p_old_node;
        unsigned long msgn, oldmsgn;
        int to_area = -1;
        char messagetxt[80];
        int ogroup;

        ogroup = group_set_group(0); /* allow copies etc. to everywhere */

        oldmsgn = 0L;

        switch(rc)
        {
        case 0:
            strcpy(messagetxt," Moving");
            break;

        case 1:
            strcpy(messagetxt," Copying");
            break;

        case 2:
            strcpy(messagetxt," Redirecting");
            break;

        case 3:
            strcpy(messagetxt," Forwarding");
            break;

        default:
            strcpy(messagetxt," <internal error>");
            break;
        }
        strcat(messagetxt, " Messages ");

        if (!OpenMsgWnd(50, 6, messagetxt, NULL, 0, 0))
        {
            return;
        }
        SendMsgWnd("Press Esc to stop", 2);

        p_node = dlistTravFirst(ulist);
        while (p_node != NULL)
        {
            unsigned long *puid;

            if (KeyHit() && GetKey() == Key_Esc)
            {
                p_node = NULL;
                break;
            }

            puid = dlistGetElement(p_node);
            msgn = UidToMsgn(*puid);

            sprintf(messagetxt, "Working on message #%lu", 
                    SW->showrealmsgn ? *puid: msgn);
            SendMsgWnd(messagetxt, 1);
            
            if (oldmsgn == 0L)
            {
                oldmsgn = msgn - 1L;
            }
            p_old_node = p_node;
            if (msgn != 0L)
            {
                CurArea.current = msgn;
                hCurr = WndTop();
                WndCurr(hMnScr);
                groupmove = 1;
                to_area = movemsgs(rc, to_area);
                groupmove = 0;
                WndCurr(hCurr);
            }
            p_node = dlistTravNext(p_node);
            dlistDropNode(ulist, p_old_node);

            if (to_area == -1) /* an escape or an error occured */
            {
                p_node = NULL;
            }
        }
        if (oldmsgn == 0L)
        {
            oldmsgn = *CurrMsgn;
        }
        *CurrMsgn = oldmsgn;
        CurArea.current = oldmsgn;

        CloseMsgWnd();

        group_set_group(ogroup);
    }
}

/*
 *  Puts a list of messages up in a window on the screen.  Allows for
 *  some basic management of those messages.
 */

int do_list(void)
{
    EVT event;
    WND *hWnd, *hCurr;
    static int in_list = 0;     /* stop recursion */
    MLHEAD *headers;            /* headers */
    int done = 0;               /* finished ? */
    int down = 0;
    int ForceEvt = 0;           /* forced/piped keypress */
    int Msg;                    /* message */
    int lbutton = 0;
    unsigned long i, a, j;
    int y;

begin:

    if (in_list || !CurArea.status)  /* stop recursion */
    {
        return 0;
    }
    else
    {
        in_list = 1;
    }

    /* Open the window and draw the screen and allocate the memory. */

    TTBeginOutput();
    WndClearLine(0, cm[MN_NTXT]);
    WndClearLine(maxy - 1, cm[MN_NTXT]);
    WndWriteStr(2, 0, cm[LS_TTXT], CurArea.description);
    hCurr = WndTop();
    hWnd = WndOpen(0, 1, maxx - 1, maxy - 2, NBDR | NOSAVE, 0, cm[LS_NTXT]);
    headers = xcalloc(maxy, sizeof(MLHEAD));

    WndBox(0, 0, maxx - 1, maxy - 3, cm[LS_BTXT], SBDR);

    message = KillMsg(message);

    if (done == 2)
    {
         done = 0;  /* this an subsequent entry into the list which
                       results from a window resize operation. */
    }
    else
    {
         a = CurArea.current;
                    /* this is the first entry - set the pointer to
                       the current message in this area */
    }
    y = 1;
    update(headers, a, y);
    TTEndOutput();


    while (!done)
    {
        TTBeginOutput();
#if defined(MSDOS) && !defined(__FLAT__)
        /* shows memory if compiled under dos */

        if (SW->statbar)
        {
            char line[255];
            sprintf(line, "%c %3ldK ", SC7, (long)(corerem() / 1024));
            WndCurr(WndTop());
            WndPutsn(maxx - 7, maxy - 1, 1, cm[CM_ITXT] | F_ALTERNATE,
                     line + 1);
            WndPutsn(maxx - 6, maxy - 1, 6, cm[CM_ITXT], line + 1);
            WndCurr(hWnd);
        }
#endif

        WndWriteStr(3, 0, cm[LS_TTXT], "Msg");
        WndWriteStr(9, 0, cm[LS_TTXT], "From");
        if (long_subj)
        {
            char tmp[8];

            WndWriteStr(25, 0, cm[LS_TTXT], "Subject");

            memset(tmp, SC8, 7);
            *(tmp + 7) = '\0';
            WndWriteStr(41, 0, cm[LS_BTXT] | F_ALTERNATE, tmp);
        }
        else
        {
            char tmp[8];

            memset(tmp, SC8, 7);
            *(tmp + 7) = '\0';
            WndWriteStr(25, 0, cm[LS_BTXT] | F_ALTERNATE, tmp);
            WndWriteStr(25, 0, cm[LS_TTXT], "To");
            WndWriteStr(41, 0, cm[LS_TTXT], "Subject");
        }
        showit(&headers[y - 1], y, 1);
        TTEndOutput();

        if (down)
        {
            /* If a selection has occured, then force cursor down. */

            Msg = Key_Dwn;
            event.msg = Msg;
            event.msgtype = WND_WM_CHAR;
            down = 0;
        }
        else if (ForceEvt)
        {
            /*
             * These events are from the mouse (no point in
             * duplicating code).
             */

            Msg = ForceEvt;
            event.msg = Msg;
            event.msgtype = WND_WM_CHAR;
            ForceEvt = 0;
        }
        else
        {
            Msg = MnuGetMsg(&event, hWnd->wid);
        }

        switch (event.msgtype)
        {
        case WND_WM_RESIZE:  
                /* the window has been resized. we have to exit and
                   rebuild the list. */
                maxx = term.NCol;
                maxy = term.NRow;
                done = 2;
                break;
                
        case WND_WM_MOUSE:
            switch (Msg)
            {
            case LMOU_RPT:
                {
                    int y1 = event.y;

                    if (y1 > maxy - 4 && lbutton)
                    {
                        ForceEvt = Key_Dwn;
                    }
                    else if (y1 < 1 && lbutton)
                    {
                        ForceEvt = Key_Up;
                    }
                }
                break;

            case LMOU_CLCK:
            case MOU_LBTDN:
            case MOU_LBTUP:
            case MOUSE_EVT:
                {
                    int y1 = event.y - 1, ok = 0;

                    if (Msg == MOU_LBTDN)
                    {
                        lbutton = 1;
                    }
                    else if (Msg == MOU_LBTUP)
                    {
                        lbutton = 0;
                    }

                    if (y1 > maxy - 4)
                    {
                        if (Msg == MOU_LBTDN && lbutton)
                        {
                            ForceEvt = Key_Dwn;
                        }
                    }
                    else
                    {
                        if (y1 < 1)
                        {
                            if (Msg == MOU_LBTDN && lbutton)
                            {
                                ForceEvt = Key_Up;
                            }
                        }
                        else
                        {
                            /* The event occured on the list. */

                            if (y == y1 && (Msg == MOU_LBTUP || Msg == LMOU_CLCK))
                            {
                                ForceEvt = Key_Ent;
                                continue;
                            }
                            showit(&headers[y - 1], y, 0);
                            if (y > y1)
                            {
                                if (a - y - y1 >= 1)
                                {
                                    a -= y - y1;
                                    ok = TRUE;
                                }
                            }
                            else
                            {
                                if (a + y1 - y <= CurArea.messages)
                                {
                                    a += y1 - y;
                                    ok = TRUE;
                                }
                            }

                            if (ok == TRUE)
                            {
                                y = y1;
                                if (Msg == MOU_LBTUP || Msg == LMOU_CLCK)
                                {
                                    ForceEvt = Key_Ent;
                                }
                            }
                        }
                    }
                }
                break;

            default:
                break;
            }
            break;

        case WND_WM_CHAR:
            switch (Msg)
            {
            case Key_PgDn:
                i = maxy - 4 - y;
                while (i > 0 && a < CurArea.messages)
                {
                    i--;
                    a++;
                }
                y = 1;
                update(headers, a, y);
                break;

            case Key_PgUp:
                if (y == 1)
                {
                    i = maxy - 5;
                }
                else
                {
                    i = y - 1;
                }
                while (i > 0 && a > 1)
                {
                    i--;
                    a--;
                }
                y = 1;
                update(headers, a, y);
                break;

            case Key_Up:
                if (a > 1)
                {
                    showit(&headers[y - 1], y, 0);
                    a--;
                    y--;

                    if (y < 1)
                    {
                        y = 1;
                        WndScroll(1, 1, maxx - 2, maxy - 4, 0);
                        if (SW->statbar)
                        {
                            memmove(headers + 1, headers,
                              sizeof(MLHEAD) * (maxy - 2));
                        }
                        else
                        {
                            memmove(headers + 1, headers,
                              sizeof(MLHEAD) * (maxy - 1));
                        }
                        getheader(a, &headers[0], 1);
                    }
                }
                break;

            case Key_Dwn:
                if (a < CurArea.messages)
                {
                    showit(&headers[y - 1], y, 0);
                    a++;
                    y++;

                    if (y > maxy - 4)
                    {
                        y = maxy - 4;
                        WndScroll(1, 1, maxx - 2, y, 1);
                        if (SW->statbar)
                        {
                            memmove(headers, headers + 1,
                              sizeof(MLHEAD) * (maxy - 2));
                        }
                        else
                        {
                            memmove(headers, headers + 1,
                              sizeof(MLHEAD) * (maxy - 1));
                        }
                        getheader(a, &headers[y - 1], 1);
                    }
                }
                break;

            case Key_Home:
                a = CurArea.first;
                update(headers, a, y = 1);
                break;

            case Key_End:
                a = CurArea.last;
                update(headers, a, y = 1);
                break;

            case '+':
                ulistTerm();
                for (j = CurArea.first; j <= CurArea.messages; j++)
                {
                    ulistAddUid(MsgnToUid(j));
                }
                update(headers, a, y = 1);
                break;

            case '-':
                ulistTerm();
                update(headers, a, y = 1);
                break;

            case Key_Spc:
                if (headers[y - 1].sel == 0)
                {
                    headers[y - 1].sel = 1;
                    ulistAddUid(headers[y - 1].umsgid);
                }
                else
                {
                    ulistDropUid(headers[y - 1].umsgid);
                    headers[y - 1].sel = 0;
                }
                showit(&headers[y - 1], y, 1);
                down = 1;
                break;

            case Key_Del:
                DeleteMsgs(&a);
                if (a > CurArea.last)
                {
                    a = CurArea.last;
                }
                update(headers, a, y = 1);
                break;

            case Key_A_H:
                if (ST->helpfile)
                {
                    DoHelp(2);
                }
                break;

            case Key_Ent:
                CurArea.current = a;
                done = 3;
                break;

            case Key_A_X:
            case Key_Esc:
                done = 1;
                break;

            case Key_A_S:
                long_subj ^= 1;
                update(headers, a, y = 1);
                break;

            case Key_A_A:
                display_address ^= 1;
                update(headers, a, y = 1);
                break;

            case Key_A_M:
                MoveMsgs(&a);
                if (a > CurArea.last)
                {
                    a = CurArea.last;
                }
                update(headers, a, y = 1);
                break;

            default:
                break;
            }
            break;
        }
    }
    in_list = 0;
    ulistTerm();
    xfree(headers);
    WndClose(hWnd);
    WndCurr(hCurr);

    switch(done)
    {
    case 1:
        return 0;               /* exit with ESC */
    case 2:                     /* resize occured, continue with
                                   rebuilding the list */
        goto begin;
    case 3:
        return 1;               /* exit with Enter */
    }

    abort();                    /* something went wrong! */
}
