/*
 * MultiMail offline mail reader
 * address book

 Copyright 1996 Kolossvary Tamas <thomas@vma.bme.hu>
 Copyright 1997-2018 William McBrine <wmcbrine@gmail.com>
 Distributed under the GNU General Public License, version 3 or later. */

#include "interfac.h"

extern "C" int perscomp(const void *a, const void *b)
{
    return strcasecmp((*((Person **) a))->name,
                      (*((Person **) b))->name);
}

Person::Person(const char *sname, const char *saddr)
{
    if (saddr && (*saddr == 'I'))
        saddr++;
    netmail_addr = saddr;
    setname(sname);
    killed = false;
    next = 0;
}

Person::Person(const char *sname, net_address &naddr)
{
    netmail_addr = naddr;
    setname(sname);
    killed = false;
    next = 0;
}

Person::~Person()
{
    delete[] name;
}

void Person::setname(const char *sname)
{
    name = strdupplus(sname);
}

void Person::dump(FILE *fd)
{
    fprintf(fd, (netmail_addr.isInternet ? "%s\nI%s\n\n" : "%s\n%s\n\n"),
            name, (const char *) netmail_addr);
}

AddressBook::AddressBook()
{
    NumOfPersons = NumOfActive = 0;
    people = living = 0;
    filter = 0;
}

AddressBook::~AddressBook()
{
    DestroyChain();
}

void AddressBook::MakeActive(bool NoEnterA)
{
    int expmode = mm.resourceObject->getInt(ExpertMode);
    statetype s = ui->prevactive();
    if (s != address)
        inletter = ((s == letter) || (s == littlearealist)) &&
                   !mm.areaList->isReplyArea();

    NoEnter = NoEnterA;

    list_max_y = LINES - (expmode ? 9 : 12);
    list_max_x = COLS - 6;
    top_offset = 2;

    borderCol = C_ADDR1;

    char tmp[60];
    char *p = tmp + sprintf(tmp, "Addresses");
    if (NumOfActive > list_max_y)
        p += sprintf(p, " (%d)", NumOfActive);
    if (filter)
        sprintf(p, " | %.20s", filter);

    int xwidth = list_max_x + 2;
    list = new InfoWin(LINES - 6, xwidth, 2, borderCol, tmp, C_ADDR2,
                       expmode ? 3 : 6);

    list->attrib(C_ADDR2);
    list->put(1, 2, "Name                            Netmail address");

    if (!expmode) {
        int x = xwidth / 3 + 1;
        int y = list_max_y + 2;

        list->horizline(y);

        list->put(++y, 3, ": Quit addressbook");
        list->put(y, x + 3, ": Add new address");
        list->put(y, x * 2 + 1, ": search / next");
        list->put(++y, 3, ": Kill current address");
        list->put(y, x + 3, ": Edit address");

        if (inletter)
            list->put(y, x * 2 + 1, ": address from Letter");

        list->attrib(C_ADDR1);
        list->put(y, 2, "K");
        list->put(y, x + 2, "E");

        if (inletter)
            list->put(y, x * 2, "L");

        list->put(--y, 2, "Q");
        list->put(y, x + 2, "A");
        list->put(y, x * 2 - 3, "/, .");
    }
    DrawAll();
}

void AddressBook::Delete()
{
    delete list;
}

bool AddressBook::extrakeys(int key)
{
    switch (key) {
    case MM_ENTER:
        SetLetterThings();
        break;
    case 'A':
        NewAddress();
        break;
    case 'E':
        ChangeAddress();
        break;
    case MM_DEL:
    case 'K':
        kill();
        break;
    case 'L':
        GetAddress();
    }
    return false;
}

void AddressBook::setFilter(const char *item)
{
    delete[] filter;
    filter = strdupplus(item);
    MakeChain();
    if (!NumOfActive) {
        delete[] filter;
        filter = 0;
        MakeChain();
    }
}

void AddressBook::WriteFile()
{
    FILE *fd = fopen(addfname, "wt");
    for (int x = 0; x < NumOfPersons; x++)
        people[x]->dump(fd);
    fclose(fd);
}

void AddressBook::kill()
{
    if (highlighted) {
        if (ui->WarningWindow("Remove this address?")) {
            highlighted->killed = true;

            if (position)
                position--;

            NumOfPersons--;

            MakeChain();
            WriteFile();
        }
        Delete();
        MakeActive(NoEnter);
    }
}

void AddressBook::SetLetterThings()
{
    if (!NoEnter && highlighted)
        ui->letterwindow.set_Letter_Params(highlighted->netmail_addr,
                                           highlighted->name);
}

void AddressBook::Add(const char *Name, net_address &Address)
{
    if (Address.isSet) {
        bool found = false;

        // Dupe check; also positions curr at end of list:
        curr = &head;
        while (curr->next && !found) {
            curr = curr->next;
            found = (curr->netmail_addr == Address) &&
                    !strcasecmp(curr->name, Name);
        }

        if (!found) {
            curr->next = new Person(Name, Address);

            FILE *fd = fopen(addfname, "at");
            curr->next->dump(fd);
            fclose(fd);

            NumOfPersons++;
            MakeChain();

            active = NumOfPersons;
            Draw();
        } else
            ui->nonFatalError("Already in addressbook");
    } else
        ui->nonFatalError("No address found");
}

void AddressBook::GetAddress()
{
    if (inletter)
        Add(mm.letterList->getFrom(), ui->letterwindow.PickNetAddr());
}

int AddressBook::HeaderLine(ShadowedWin &win, char *buf, int limit,
                            int pos, coltype color)
{
    int getit = win.getstring(pos, 8, buf, limit, color, color);
    return getit;
}

int AddressBook::Edit(Person &p)
{
    char NAME[100], NETADD[100];

    const int maxitems = 2;
    int result, current = 0;
    bool end = false;

    if (p.netmail_addr.isSet) {
        sprintf(NAME, "%.99s", p.name);
        sprintf(NETADD, "%.99s", (const char *) p.netmail_addr);
    } else
        NAME[0] = NETADD[0] = '\0';

    ShadowedWin add_edit(4, COLS - 4, (LINES >> 1) - 2, C_LETEXT);

    add_edit.put(1, 2, "Name:");
    add_edit.put(2, 2, "Addr:");

    add_edit.attrib(C_LEGET1);
    add_edit.put(1, 8, NAME);

    add_edit.attrib(C_LEGET2);
    add_edit.put(2, 8, NETADD);

    add_edit.update();

    do {
        result = HeaderLine(add_edit, current ? NETADD : NAME, 99,
                            current + 1, current ? C_LEGET2 : C_LEGET1);

        switch (result) {
        case 0:
            end = true;
            break;
        case 1:
            current++;
            if (current == maxitems)
                end = true;
            break;
        case 2:
            if (current > 0)
                current--;
            break;
        case 3:
            if (current < maxitems - 1)
                current++;
        }
    } while (!end);

    if (result && NAME[0] && NETADD[0]) {
        p.setname(NAME);
        p.netmail_addr = NETADD;
        if (!p.netmail_addr.isSet)
            result = 0;
    } else
        result = 0;
    return result;
}

void AddressBook::NewAddress()
{
    Person p;

    p.netmail_addr.isSet = false;
    if (Edit(p))
        Add(p.name, p.netmail_addr);
    ui->redraw();
}

void AddressBook::ChangeAddress()
{
    if (highlighted)
        if (Edit(*highlighted))
            WriteFile();
    ui->redraw();
}

void AddressBook::oneLine(int i)
{
    int z = position + i;
    curr = (z < NumOfActive) ? living[z] : 0;

    if (z == active)
        highlighted = curr;

    char *tmp = list->lineBuf;
    int x = curr ? sprintf(tmp, " %-31s %s", curr->name,
                           (const char *) curr->netmail_addr) : 0;
    for (; x < list_max_x; x++)
        tmp[x] = ' ';
    tmp[x] = '\0';

    DrawOne(i, C_ADDR3);
}

searchret AddressBook::oneSearch(int x, const char *item, int)
{
    const char *s;

    s = searchstr(living[x]->name, item);
    if (!s)
        s = searchstr(living[x]->netmail_addr, item);
    return s ? True : False;
}

int AddressBook::NumOfItems()
{
    return NumOfActive;
}

void AddressBook::ReadFile()
{
    FILE *fd;
    char name[256], nmaddr[256], other[256];
    bool end = false;

    fd = fopen(addfname, "rt");
    if (fd) {
        curr = &head;
        while (!end) {
            do
                end = !myfgets(name, sizeof name, fd);
            while (name[0] == '\n' && !end);

            end = !myfgets(nmaddr, sizeof nmaddr, fd);

            if (!end) {
                strtok(name, "\n");
                strtok(nmaddr, "\n");
                curr->next = new Person(name, nmaddr);
                curr = curr->next;
                NumOfPersons++;
            }

            do
                end = !myfgets(other, sizeof other, fd);
            while (other[0] != '\n' && !end);
        }
        fclose(fd);
    }

    MakeChain();

    if (NumOfPersons > 1) {
        qsort(people, NumOfPersons, sizeof(Person *), perscomp);
        if (NumOfActive > 1)
            qsort(living, NumOfActive, sizeof(Person *), perscomp);
        ReChain();
    }
}


void AddressBook::MakeChain()
{
    delete[] people;
    delete[] living;

    NumOfActive = 0;

    if (NumOfPersons) {
        people = new Person *[NumOfPersons];
        living = new Person *[NumOfPersons];

        curr = head.next;
        int c = 0;
        while (curr) {
            if (!curr->killed) {
                people[c++] = curr;
                if (!filter || (searchstr(curr->name, filter)
                    || searchstr(curr->netmail_addr, filter)))
                    living[NumOfActive++] = curr;
            }
            curr = curr->next;
        }
    } else
        people = living = 0;
}

void AddressBook::ReChain()
{
    head.next = people[0];
    for (int c = 0; c < (NumOfPersons - 1); c++)
        people[c]->next = people[c + 1];

    people[NumOfPersons - 1]->next = 0;
}

void AddressBook::DestroyChain()
{
    while (NumOfPersons)
        delete people[--NumOfPersons];
    delete[] people;
}

void AddressBook::Init()
{
    addfname = mm.resourceObject->get(AddressFile);
    ReadFile();
}
