/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2023 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "E.h"
#include "borders.h"
#include "dialog.h"
#include "emodule.h"
#include "ewins.h"
#include "groups.h"
#include "list.h"
#include "settings.h"
#include "snaps.h"

#define DEBUG_GROUPS 0
#if DEBUG_GROUPS
#define Dprintf(fmt, ...) Eprintf("%s: " fmt, __func__, __VA_ARGS__)
#else
#define Dprintf(fmt...)
#endif

#define USE_GROUP_SHOWHIDE 1    /* Enable group borders */

#define GROUP_SELECT_ALL             0
#define GROUP_SELECT_EWIN_ONLY       1
#define GROUP_SELECT_ALL_EXCEPT_EWIN 2

#define SET_OFF    0
#define SET_ON     1
#define SET_TOGGLE 2

typedef struct _groupconfig {
    char            iconify;
    char            kill;
    char            move;
    char            raise;
    char            set_border;
    char            shade;
    char            stick;
} GroupConfig;

struct _group {
    dlist_t         list;
    int             index;
    EWin          **members;
    int             num_members;
    GroupConfig     cfg;
    char            keep;       /* Don't destroy when empty */
    char            used;       /* Don't discard when saving */
};

static          LIST_HEAD(group_list);

static struct {
    GroupConfig     dflt;
} Conf_groups;

static struct {
    Group          *current;
} Mode_groups;

static void     _GroupsSave(void);

static Group   *
_GroupCreate(int gid, int cfg_update)
{
    Group          *g;

    g = ECALLOC(Group, 1);
    if (!g)
        return NULL;

    LIST_APPEND(Group, &group_list, g);

    if (gid == -1)
    {
        /* Create new group id */
        /* ... using us time. Should really be checked for uniqueness. */
        g->index = (int)GetTimeUs();
    }
    else
    {
        /* Use given group id */
        g->index = gid;
    }
    g->cfg.iconify = Conf_groups.dflt.iconify;
    g->cfg.kill = Conf_groups.dflt.kill;
    g->cfg.move = Conf_groups.dflt.move;
    g->cfg.raise = Conf_groups.dflt.raise;
    g->cfg.set_border = Conf_groups.dflt.set_border;
    g->cfg.stick = Conf_groups.dflt.stick;
    g->cfg.shade = Conf_groups.dflt.shade;

    Dprintf("%s: grp=%p gid=%d\n", __func__, g, g->index);

    if (cfg_update)
        _GroupsSave();

    return g;
}

static void
_GroupDestroy(Group *g, int cfg_update)
{
    if (!g)
        return;

    Dprintf("%s: grp=%p gid=%d\n", __func__, g, g->index);

    LIST_REMOVE(Group, &group_list, g);

    if (g == Mode_groups.current)
        Mode_groups.current = NULL;

    Efree(g->members);
    Efree(g);

    if (cfg_update)
        _GroupsSave();
}

static int
_GroupMatchId(const void *data, const void *match)
{
    return ((const Group *)data)->index != PTR2INT(match);
}

static Group   *
_GroupFind(int gid)
{
    return LIST_FIND(Group, &group_list, _GroupMatchId, INT2PTR(gid));
}

EWin           *const *
GroupGetMembers(const Group *g, int *num)
{
    *num = g->num_members;
    return g->members;
}

int
GroupGetIndex(const Group *g)
{
    return g->index;
}

void
GroupSetUsed(int gid)
{
    Group          *g;

    g = _GroupFind(gid);
    if (!g)
        return;

    g->used = 1;
}

int
GroupMatchAction(const Group *g, int action)
{
    int             match;

    switch (action)
    {
    default:
        match = 0;
        break;
    case GROUP_ACTION_ANY:
        match = 1;
        break;
    case GROUP_ACTION_SET_WINDOW_BORDER:
        match = g->cfg.set_border;
        break;
    case GROUP_ACTION_ICONIFY:
        match = g->cfg.iconify;
        break;
    case GROUP_ACTION_MOVE:
        match = g->cfg.move;
        break;
    case GROUP_ACTION_STACKING:
        match = g->cfg.raise;
        break;
    case GROUP_ACTION_STICK:
        match = g->cfg.stick;
        break;
    case GROUP_ACTION_SHADE:
        match = g->cfg.shade;
        break;
    case GROUP_ACTION_KILL:
        match = g->cfg.kill;
        break;
    }

    return match;
}

static Group   *
_GroupFind2(const char *groupid)
{
    int             gid;

    if (groupid[0] == '*' || groupid[0] == '\0')
        return Mode_groups.current;

#if ENABLE_DIALOGS
#define GROUP_CHOOSE ((Group*)1)
    if (groupid[0] == '=')
        return GROUP_CHOOSE;
#endif

    gid = 0;
    sscanf(groupid, "%d", &gid);
    if (gid == 0)
        return NULL;

    return _GroupFind(gid);
}

static int
_EwinGroupIndex(const EWin *ewin, const Group *g)
{
    int             i;

    for (i = 0; i < ewin->num_groups; i++)
        if (ewin->groups[i] == g)
            return i;

    return -1;
}

static int
_GroupEwinIndex(const Group *g, const EWin *ewin)
{
    int             i;

    for (i = 0; i < g->num_members; i++)
        if (g->members[i] == ewin)
            return i;

    return -1;
}

static void
_GroupEwinAdd(Group *g, EWin *ewin, int snap_update)
{
    if (!ewin || !g)
        return;

    if (_EwinGroupIndex(ewin, g) >= 0)
        return;                 /* Already there */

    Dprintf("%s: gid=%8d: %s\n", __func__, g->index, EoGetName(ewin));

    ewin->num_groups++;
    ewin->groups = EREALLOC(Group *, ewin->groups, ewin->num_groups);
    ewin->groups[ewin->num_groups - 1] = g;
    g->num_members++;
    g->members = EREALLOC(EWin *, g->members, g->num_members);
    g->members[g->num_members - 1] = ewin;

    if (snap_update)
        SnapshotEwinUpdate(ewin, SNAP_USE_GROUPS);
}

static void
_GroupEwinRemove(Group *g, EWin *ewin, int snap_update)
{
    int             i, ie, ig;

    if (!ewin || !g)
        return;

    Dprintf("%s: gid=%8d: %s\n", __func__, g->index, EoGetName(ewin));

    ie = _EwinGroupIndex(ewin, g);
    if (ie < 0)
    {
        /* Should not happen */
        Dprintf("%s: g=%p gid=%8d: %s: Group not found?!?\n", __func__,
                g, g->index, EoGetName(ewin));
        return;
    }

    ig = _GroupEwinIndex(g, ewin);
    if (ig < 0)
    {
        /* Should not happen */
        Dprintf("%s: g=%p gid=%8d: %s: Ewin not found?!?\n", __func__,
                g, g->index, EoGetName(ewin));
        return;
    }

    Dprintf("%s: gid=%8d index=%d/%d: %s\n", __func__,
            g->index, ie, ig, EoGetName(ewin));

    /* remove it from the group */
    g->num_members--;
    for (i = ig; i < g->num_members; i++)
        g->members[i] = g->members[i + 1];

    if (g->num_members > 0)
        g->members = EREALLOC(EWin *, g->members, g->num_members);
    else if (g->keep)
        EFREE_NULL(g->members);
    else
        _GroupDestroy(g, snap_update);

    /* and remove the group from the groups that the window is in */
    ewin->num_groups--;
    for (i = ie; i < ewin->num_groups; i++)
        ewin->groups[i] = ewin->groups[i + 1];

    if (ewin->num_groups > 0)
        ewin->groups = EREALLOC(Group *, ewin->groups, ewin->num_groups);
    else
        EFREE_NULL(ewin->groups);

    if (snap_update)
        SnapshotEwinUpdate(ewin, SNAP_USE_GROUPS);
}

static void
_GroupDelete(Group *g)
{
    if (!g)
        return;

    Dprintf("%s: gid=%d\n", __func__, g->index);

    g->keep = 1;
    while (g->num_members > 0)
        _GroupEwinRemove(g, g->members[0], 1);
    _GroupDestroy(g, 1);
}

Group         **
GroupsGetList(int *pnum)
{
    return LIST_GET_ITEMS(Group, &group_list, pnum);
}

#if ENABLE_DIALOGS
static Group  **
_EwinListGroups(const EWin *ewin, char group_select, int *num)
{
    Group         **groups;
    Group         **groups2;
    int             i, j, n, killed;

    groups = NULL;
    *num = 0;

    switch (group_select)
    {
    case GROUP_SELECT_EWIN_ONLY:
        *num = n = ewin->num_groups;
        if (n <= 0)
            break;
        groups = EMALLOC(Group *, n);
        if (!groups)
            break;
        memcpy(groups, ewin->groups, n * sizeof(Group *));
        break;
    case GROUP_SELECT_ALL_EXCEPT_EWIN:
        groups2 = GroupsGetList(num);
        if (!groups2)
            break;
        n = *num;
        for (i = killed = 0; i < n; i++)
        {
            for (j = 0; j < ewin->num_groups; j++)
            {
                if (ewin->groups[j] == groups2[i])
                {
                    groups2[i] = NULL;
                    killed++;
                }
            }
        }
        if (n - killed > 0)
        {
            groups = EMALLOC(Group *, n - killed);
            if (groups)
            {
                for (i = j = 0; i < n; i++)
                    if (groups2[i])
                        groups[j++] = groups2[i];
                *num = n - killed;
            }
        }
        Efree(groups2);
        break;
    case GROUP_SELECT_ALL:
    default:
        groups = GroupsGetList(num);
        break;
    }

    return groups;
}
#endif                          /* ENABLE_DIALOGS */

/* Update groups on snapped window appearance */
void
GroupsEwinAdd(EWin *ewin, const int *pgid, int ngid)
{
    Group          *g;
    int             i, gid;

    for (i = 0; i < ngid; i++)
    {
        gid = pgid[i];
        g = _GroupFind(gid);
        Dprintf("ewin=%p gid=%d grp=%p\n", ewin, gid, g);
        if (!g)
        {
            /* This should not happen, but may if group/snap configs are corrupted */
            g = _GroupCreate(gid, 1);
        }
        _GroupEwinAdd(g, ewin, 0);
    }
}

/* Update groups on snapped window disappearance */
void
GroupsEwinRemove(EWin *ewin)
{
    while (ewin->num_groups > 0)
        _GroupEwinRemove(ewin->groups[0], ewin, 0);
}

Group          *
EwinsInGroup(const EWin *ewin1, const EWin *ewin2)
{
    int             i;

    if (ewin1 && ewin2)
    {
        for (i = 0; i < ewin1->num_groups; i++)
        {
            if (_GroupEwinIndex(ewin1->groups[i], ewin2) >= 0)
                return ewin1->groups[i];
        }
    }
    return NULL;
}

#if ENABLE_DIALOGS
static char   **
_GrouplistMemberNames(Group **groups, int num)
{
    int             i, j, len;
    char          **group_member_strings;
    const char     *name;

    group_member_strings = ECALLOC(char *, num);

    if (!group_member_strings)
        return NULL;

    for (i = 0; i < num; i++)
    {
        group_member_strings[i] = EMALLOC(char, 1024);

        if (!group_member_strings[i])
            break;

        len = 0;
        for (j = 0; j < groups[i]->num_members; j++)
        {
            name = EwinGetTitle(groups[i]->members[j]);
            if (!name)          /* Should never happen */
                continue;
            len += Esnprintf(group_member_strings[i] + len, 1024 - len,
                             "%s\n", name);
            if (len >= 1024)
                break;
        }
        if (len == 0)
            snprintf(group_member_strings[i], 1024, "(empty)");
    }

    return group_member_strings;
}
#endif                          /* ENABLE_DIALOGS */

#if USE_GROUP_SHOWHIDE
static void
_EwinGroupsShowHide(EWin *ewin, int group_index, char onoff)
{
    EWin          **gwins;
    int             i, num;
    const Border   *b = NULL;

    if (!ewin || group_index >= ewin->num_groups)
        return;

    if (group_index < 0)
    {
        gwins = ListWinGroupMembersForEwin(ewin, GROUP_ACTION_ANY, 0, &num);
    }
    else
    {
        gwins = ewin->groups[group_index]->members;
        num = ewin->groups[group_index]->num_members;
    }

    if (onoff == SET_TOGGLE)
        onoff = (ewin->border == ewin->normal_border) ? SET_ON : SET_OFF;

    for (i = 0; i < num; i++)
    {
        if (onoff == SET_ON)
            b = EwinBorderGetGroupBorder(gwins[i]);
        else
            b = gwins[i]->normal_border;

        EwinBorderChange(gwins[i], b, 0);
    }
    if (group_index < 0)
        Efree(gwins);
}
#else

#define _EwinGroupsShowHide(ewin, group_index, onoff)

#endif                          /* USE_GROUP_SHOWHIDE */

static void
_GroupsSave(void)
{
    Group          *g;
    FILE           *f;
    char            s[1024];

    Dprintf("%s\n", __func__);

    Esnprintf(s, sizeof(s), "%s.groups", EGetSavePrefix());
    f = fopen(s, "w");
    if (!f)
        return;

    LIST_FOR_EACH(Group, &group_list, g)
    {
        fprintf(f, "NEW: %i\n", g->index);
        fprintf(f, "ICONIFY: %i\n", g->cfg.iconify);
        fprintf(f, "KILL: %i\n", g->cfg.kill);
        fprintf(f, "MOVE: %i\n", g->cfg.move);
        fprintf(f, "RAISE: %i\n", g->cfg.raise);
        fprintf(f, "SET_BORDER: %i\n", g->cfg.set_border);
        fprintf(f, "STICK: %i\n", g->cfg.stick);
        fprintf(f, "SHADE: %i\n", g->cfg.shade);
    }

    fclose(f);
}

void
GroupsPrune(void)
{
    Group          *g, *tmp;
    int             pruned;

    pruned = 0;

    LIST_FOR_EACH_SAFE(Group, &group_list, g, tmp)
    {
        if (g->used)
            continue;
        if (g->members)
            continue;
        _GroupDestroy(g, 0);
        pruned += 1;
    }

    Dprintf("%s: Pruned=%d\n", __func__, pruned);

    if (pruned)
        _GroupsSave();
}

static int
_GroupsLoad(FILE *fs)
{
    char            s[1024];
    Group          *g = NULL;

    while (fgets(s, sizeof(s), fs))
    {
        char            ss[128];
        int             ii;

        if (strlen(s) > 0)
            s[strlen(s) - 1] = 0;
        ii = 0;
        sscanf(s, "%100s %d", ss, &ii);

        if (!strcmp(ss, "NEW:"))
        {
            g = _GroupCreate(ii, 0);
            continue;
        }
        if (!g)
            continue;

        if (!strcmp(ss, "ICONIFY:"))
        {
            g->cfg.iconify = ii;
        }
        else if (!strcmp(ss, "KILL:"))
        {
            g->cfg.kill = ii;
        }
        else if (!strcmp(ss, "MOVE:"))
        {
            g->cfg.move = ii;
        }
        else if (!strcmp(ss, "RAISE:"))
        {
            g->cfg.raise = ii;
        }
        else if (!strcmp(ss, "SET_BORDER:"))
        {
            g->cfg.set_border = ii;
        }
        else if (!strcmp(ss, "STICK:"))
        {
            g->cfg.stick = ii;
        }
        else if (!strcmp(ss, "SHADE:"))
        {
            g->cfg.shade = ii;
        }
    }

    return 0;
}

void
GroupsLoad(void)
{
    char            s[4096];

    Dprintf("%s\n", __func__);

    Esnprintf(s, sizeof(s), "%s.groups", EGetSavePrefix());

    ConfigFileLoad(s, NULL, _GroupsLoad, 0);
}

#if ENABLE_DIALOGS

#define GROUP_OP_ADD    1
#define GROUP_OP_DEL    2
#define GROUP_OP_BREAK  3

typedef struct {
    EWin           *ewin;
    int             action;
    const char     *message;
    Group         **groups;
    int             group_num;
    int             cur_grp;    /* Current  group */
    int             prv_grp;    /* Previous group */
} GroupSelDlgData;

static void
_DlgApplyGroupChoose(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    GroupSelDlgData *dd = DLG_DATA_GET(d, GroupSelDlgData);

    if (!dd->groups)
        return;

    switch (dd->action)
    {
    case GROUP_OP_ADD:
        _GroupEwinAdd(dd->groups[dd->cur_grp], dd->ewin, 1);
        break;
    case GROUP_OP_DEL:
        _GroupEwinRemove(dd->groups[dd->cur_grp], dd->ewin, 1);
        break;
    case GROUP_OP_BREAK:
        _GroupDelete(dd->groups[dd->cur_grp]);
        break;
    default:
        break;
    }
}

static void
_DlgExitGroupChoose(Dialog *d)
{
    GroupSelDlgData *dd = DLG_DATA_GET(d, GroupSelDlgData);

    if (!dd->groups)
        return;
    _EwinGroupsShowHide(dd->ewin, dd->cur_grp, SET_OFF);
    Efree(dd->groups);
}

static void
_DlgSelectCbGroupChoose(Dialog *d, int val, void *data __UNUSED__)
{
    GroupSelDlgData *dd = DLG_DATA_GET(d, GroupSelDlgData);

    /* val is equal to dd->cur_grp */
    _EwinGroupsShowHide(dd->ewin, dd->prv_grp, SET_OFF);
    _EwinGroupsShowHide(dd->ewin, val, SET_ON);
    dd->prv_grp = val;
}

static void
_DlgFillGroupChoose(Dialog *d, DItem *table, void *data)
{
    GroupSelDlgData *dd = DLG_DATA_GET(d, GroupSelDlgData);
    DItem          *di, *radio;
    int             i, num_groups;
    char          **group_member_strings;

    *dd = *(GroupSelDlgData *) data;

    DialogItemTableSetOptions(table, 1, 0, 0, 0);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetText(di, dd->message);

    num_groups = dd->group_num;
    group_member_strings = _GrouplistMemberNames(dd->groups, num_groups);
    if (!group_member_strings)
        return;                 /* Silence clang - It should not be possible to go here */

    radio = NULL;               /* Avoid warning */
    for (i = 0; i < num_groups; i++)
    {
        di = DialogAddItem(table, DITEM_RADIOBUTTON);
        if (i == 0)
            radio = di;
        DialogItemSetCallback(di, _DlgSelectCbGroupChoose, i, NULL);
        DialogItemSetText(di, group_member_strings[i]);
        DialogItemRadioButtonSetFirst(di, radio);
        DialogItemRadioButtonGroupSetVal(di, i);
    }
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->cur_grp);

    StrlistFree(group_member_strings, num_groups);
}

static const DialogDef DlgGroupChoose = {
    "GROUP_SELECTION",
    NULL, N_("Window Group Selection"),
    sizeof(GroupSelDlgData),
    SOUND_SETTINGS_GROUP,
    "pix/group.png",
    N_("Enlightenment Window Group\n" "Selection Dialog"),
    _DlgFillGroupChoose,
    DLG_OC, _DlgApplyGroupChoose, _DlgExitGroupChoose
};

static void
_EwinGroupChooseDialog(EWin *ewin, int action)
{
    int             group_sel;
    GroupSelDlgData gsdd, *dd = &gsdd;

    if (!ewin)
        return;
    dd->ewin = ewin;

    dd->action = action;
    dd->cur_grp = dd->prv_grp = 0;

    switch (action)
    {
    default:
        return;
    case GROUP_OP_ADD:
        dd->message = _("Pick the group the window will belong to:");
        group_sel = GROUP_SELECT_ALL_EXCEPT_EWIN;
        break;
    case GROUP_OP_DEL:
        dd->message = _("Select the group to remove the window from:");
        group_sel = GROUP_SELECT_EWIN_ONLY;
        break;
    case GROUP_OP_BREAK:
        dd->message = _("Select the group to break:");
        group_sel = GROUP_SELECT_EWIN_ONLY;
        break;
    }

    dd->groups = _EwinListGroups(dd->ewin, group_sel, &dd->group_num);

    if (!dd->groups)
    {
        if (action == GROUP_OP_BREAK || action == GROUP_OP_DEL)
        {
            DialogOK(_("Window Group Error"),
                     _
                     ("This window currently does not belong to any groups.\n"
                      "You can only destroy groups or remove windows from groups\n"
                      "through a window that actually belongs to at least one group."));
            return;
        }

        if (group_sel == GROUP_SELECT_ALL_EXCEPT_EWIN)
        {
            DialogOK(_("Window Group Error"),
                     _("Currently, no groups exist or this window\n"
                       "already belongs to all existing groups.\n"
                       "You have to start other groups first."));
            return;
        }

        DialogOK(_("Window Group Error"),
                 _
                 ("Currently, no groups exist. You have to start a group first."));
        return;
    }

    _EwinGroupsShowHide(dd->ewin, 0, SET_ON);

    DialogShowSimple(&DlgGroupChoose, dd);
}

typedef struct {
    EWin           *ewin;
    GroupConfig     cfg;        /* Dialog data for current group */
    GroupConfig    *cfgs;       /* Work copy of ewin group cfgs */
    int             ngrp;
    int             cur_grp;    /* Current  group */
    int             prv_grp;    /* Previous group */
} EwinGroupDlgData;

static void
_DlgApplyGroups(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    EwinGroupDlgData *dd = DLG_DATA_GET(d, EwinGroupDlgData);
    EWin           *ewin;
    int             i;

    /* Check ewin */
    ewin = EwinFindByPtr(dd->ewin);
    if (ewin && ewin->num_groups != dd->ngrp)
        ewin = NULL;

    if (ewin)
    {
        dd->cfgs[dd->cur_grp] = dd->cfg;
        for (i = 0; i < ewin->num_groups; i++)
            ewin->groups[i]->cfg = dd->cfgs[i];
    }

    _GroupsSave();
}

static void
_DlgExitGroups(Dialog *d)
{
    EwinGroupDlgData *dd = DLG_DATA_GET(d, EwinGroupDlgData);
    EWin           *ewin;

    ewin = EwinFindByPtr(dd->ewin);
    _EwinGroupsShowHide(ewin, dd->cur_grp, SET_OFF);

    Efree(dd->cfgs);
}

static void
_DlgSelectCbGroups(Dialog *d, int val, void *data __UNUSED__)
{
    EwinGroupDlgData *dd = DLG_DATA_GET(d, EwinGroupDlgData);

    /* val is equal to dd->cur_grp */
    dd->cfgs[dd->prv_grp] = dd->cfg;
    dd->cfg = dd->cfgs[val];
    DialogRedraw(d);
    _EwinGroupsShowHide(dd->ewin, dd->prv_grp, SET_OFF);
    _EwinGroupsShowHide(dd->ewin, val, SET_ON);
    dd->prv_grp = val;
}

static void
_DlgFillGroups(Dialog *d, DItem *table, void *data)
{
    EwinGroupDlgData *dd = DLG_DATA_GET(d, EwinGroupDlgData);
    EWin           *ewin = (EWin *) data;
    DItem          *radio, *di;
    int             i;
    char          **group_member_strings;

    dd->ewin = ewin;
    dd->cfgs = EMALLOC(GroupConfig, ewin->num_groups);
    dd->ngrp = ewin->num_groups;
    dd->cur_grp = dd->prv_grp = 0;
    for (i = 0; i < ewin->num_groups; i++)
        dd->cfgs[i] = ewin->groups[i]->cfg;
    if (ewin->num_groups > 0)   /* Avoid compiler warning */
        dd->cfg = dd->cfgs[0];

    _EwinGroupsShowHide(ewin, 0, SET_ON);

    DialogItemTableSetOptions(table, 2, 0, 0, 0);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Pick the group to configure:"));

    group_member_strings =
        _GrouplistMemberNames(ewin->groups, ewin->num_groups);
    if (!group_member_strings)
        return;                 /* Silence clang - It should not be possible to go here */

    radio = NULL;               /* Avoid warning */
    for (i = 0; i < ewin->num_groups; i++)
    {
        di = DialogAddItem(table, DITEM_RADIOBUTTON);
        if (i == 0)
            radio = di;
        DialogItemSetColSpan(di, 2);
        DialogItemSetCallback(di, _DlgSelectCbGroups, i, d);
        DialogItemSetText(di, group_member_strings[i]);
        DialogItemRadioButtonSetFirst(di, radio);
        DialogItemRadioButtonGroupSetVal(di, i);
    }
    DialogItemRadioButtonGroupSetValPtr(radio, &dd->cur_grp);

    StrlistFree(group_member_strings, ewin->num_groups);

    di = DialogAddItem(table, DITEM_SEPARATOR);
    DialogItemSetColSpan(di, 2);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("The following actions are\n"
                            "applied to all group members:"));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Changing border style"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.set_border));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Iconifying"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.iconify));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Killing"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.kill));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Moving"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.move));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Raising/lowering"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.raise));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Sticking"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.stick));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Shading"));
    DialogItemCheckButtonSetPtr(di, &(dd->cfg.shade));
}

static const DialogDef DlgGroups = {
    "CONFIGURE_GROUP",
    NULL, N_("Window Group Settings"),
    sizeof(EwinGroupDlgData),
    SOUND_SETTINGS_GROUP,
    "pix/group.png",
    N_("Enlightenment Window Group\n" "Settings Dialog"),
    _DlgFillGroups,
    DLG_OAC, _DlgApplyGroups, _DlgExitGroups
};

static void
_EwinGroupsConfig(EWin *ewin)
{
    if (!ewin)
        return;

    if (ewin->num_groups == 0)
    {
        DialogOK(_("Window Group Error"),
                 _("This window currently does not belong to any groups."));
        return;
    }

    DialogShowSimple(&DlgGroups, ewin);
}

typedef struct {
    GroupConfig     group_cfg;
} GroupCfgDlgData;

static void
_DlgApplyGroupDefaults(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    GroupCfgDlgData *dd = DLG_DATA_GET(d, GroupCfgDlgData);

    Conf_groups.dflt = dd->group_cfg;

    autosave();
}

static void
_DlgFillGroupDefaults(Dialog *d, DItem *table, void *data __UNUSED__)
{
    GroupCfgDlgData *dd = DLG_DATA_GET(d, GroupCfgDlgData);
    DItem          *di;

    dd->group_cfg = Conf_groups.dflt;

    DialogItemTableSetOptions(table, 2, 0, 0, 0);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Per-group settings:"));

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Changing border style"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.set_border);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Iconifying"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.iconify);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Killing"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.kill);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Moving"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.move);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Raising/lowering"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.raise);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Sticking"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.stick);

    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetColSpan(di, 2);
    DialogItemSetText(di, _("Shading"));
    DialogItemCheckButtonSetPtr(di, &dd->group_cfg.shade);
}

const DialogDef DlgGroupDefaults = {
    "CONFIGURE_DEFAULT_GROUP_CONTROL",
    N_("Groups"), N_("Default Group Control Settings"),
    sizeof(GroupCfgDlgData),
    SOUND_SETTINGS_GROUP,
    "pix/group.png",
    N_("Enlightenment Default\n" "Group Control Settings Dialog"),
    _DlgFillGroupDefaults,
    DLG_OAC, _DlgApplyGroupDefaults, NULL
};
#endif                          /* ENABLE_DIALOGS */

/*
 * Groups module
 */

static void
_GroupShowEwins(Group *g)
{
    int             i;

    IpcPrintf(" gid=%8d: ", g->index);
    for (i = 0; i < g->num_members; i++)
        IpcPrintf(" %s", EoGetName(g->members[i]));
    IpcPrintf("\n");
}

static void
_GroupsShowGroups(void)
{
    Group          *g;

    IpcPrintf("%s:\n", __func__);
    LIST_FOR_EACH(Group, &group_list, g)
    {
        _GroupShowEwins(g);
    }
}

static void
_GroupsShow(void)
{
    _GroupsShowGroups();
}

static void
_GroupShow(Group *g)
{
    int             j;

    for (j = 0; j < g->num_members; j++)
        IpcPrintf("%d: %s\n", g->index, EwinGetIcccmName(g->members[j]));

    IpcPrintf("        index: %d\n" "  num_members: %d\n"
              "      iconify: %d\n" "         kill: %d\n"
              "         move: %d\n" "        raise: %d\n"
              "   set_border: %d\n" "        stick: %d\n"
              "        shade: %d\n",
              g->index, g->num_members,
              g->cfg.iconify, g->cfg.kill,
              g->cfg.move, g->cfg.raise,
              g->cfg.set_border, g->cfg.stick, g->cfg.shade);
}

static void
IPC_GroupOps(const char *params)
{
    Group          *group;
    char            windowid[128];
    char            operation[128];
    char            groupid[128];
    unsigned int    win;
    EWin           *ewin;

    if (!params)
    {
        IpcPrintf("Error: no window specified\n");
        return;
    }

    windowid[0] = operation[0] = groupid[0] = '\0';
    sscanf(params, "%100s %100s %100s", windowid, operation, groupid);
    win = 0;
    sscanf(windowid, "%x", &win);

    if (!operation[0])
    {
        IpcPrintf("Error: no operation specified\n");
        return;
    }

    ewin = EwinFindByExpr(windowid);
    if (!ewin)
    {
        IpcPrintf("Error: no such window: %s\n", windowid);
        return;
    }

#if ENABLE_DIALOGS
    if (!strcmp(operation, "cfg"))
    {
        _EwinGroupsConfig(ewin);
    }
    else
#endif
    if (!strcmp(operation, "start"))
    {
        group = _GroupCreate(-1, 1);
        Mode_groups.current = group;
        _GroupEwinAdd(group, ewin, 1);
    }
    else if (!strcmp(operation, "add"))
    {
        group = _GroupFind2(groupid);
#if ENABLE_DIALOGS
        if (group == GROUP_CHOOSE)
            _EwinGroupChooseDialog(ewin, GROUP_OP_ADD);
        else
            _GroupEwinAdd(group, ewin, 1);
#endif
    }
    else if (!strcmp(operation, "del"))
    {
        group = _GroupFind2(groupid);
#if ENABLE_DIALOGS
        if (group == GROUP_CHOOSE)
            _EwinGroupChooseDialog(ewin, GROUP_OP_DEL);
        else
#endif
            _GroupEwinRemove(group, ewin, 1);
    }
    else if (!strcmp(operation, "break"))
    {
        group = _GroupFind2(groupid);
#if ENABLE_DIALOGS
        if (group == GROUP_CHOOSE)
            _EwinGroupChooseDialog(ewin, GROUP_OP_BREAK);
        else
#endif
            _GroupDelete(group);
    }
    else if (!strcmp(operation, "showhide"))
    {
        _EwinGroupsShowHide(ewin, -1, SET_TOGGLE);
    }
    else
    {
        IpcPrintf("Error: no such operation: %s\n", operation);
        return;
    }
}

static void
IPC_Group(const char *params)
{
    char            groupid[128];
    char            operation[128];
    char            param1[128];
    Group          *group;
    int             onoff;

    if (!params)
    {
        IpcPrintf("Error: no operation specified\n");
        return;
    }

    groupid[0] = operation[0] = param1[0] = '\0';
    sscanf(params, "%100s %100s %100s", groupid, operation, param1);

    if (!strcmp(groupid, "info"))
    {
        IpcPrintf("Number of groups: %d\n", LIST_GET_COUNT(&group_list));
        LIST_FOR_EACH(Group, &group_list, group) _GroupShow(group);
        return;
    }
    else if (!strcmp(groupid, "list"))
    {
        _GroupsShow();
        return;
    }

    if (!operation[0])
    {
        IpcPrintf("Error: no operation specified\n");
        return;
    }

    group = _GroupFind2(groupid);
    if (!group)
    {
        IpcPrintf("Error: no such group: %s\n", groupid);
        return;
    }

    if (!strcmp(operation, "info"))
    {
        _GroupShow(group);
        return;
    }
    else if (!strcmp(operation, "del"))
    {
        _GroupDelete(group);
        return;
    }

    if (!param1[0])
    {
        IpcPrintf("Error: no mode specified\n");
        return;
    }

    onoff = -1;
    if (!strcmp(param1, "on"))
        onoff = 1;
    else if (!strcmp(param1, "off"))
        onoff = 0;

    if (onoff == -1 && strcmp(param1, "?"))
    {
        IpcPrintf("Error: unknown mode specified\n");
    }
    else if (!strcmp(operation, "iconify"))
    {
        if (onoff >= 0)
            group->cfg.iconify = onoff;
        else
            onoff = group->cfg.iconify;
    }
    else if (!strcmp(operation, "kill"))
    {
        if (onoff >= 0)
            group->cfg.kill = onoff;
        else
            onoff = group->cfg.kill;
    }
    else if (!strcmp(operation, "move"))
    {
        if (onoff >= 0)
            group->cfg.move = onoff;
        else
            onoff = group->cfg.move;
    }
    else if (!strcmp(operation, "raise"))
    {
        if (onoff >= 0)
            group->cfg.raise = onoff;
        else
            onoff = group->cfg.raise;
    }
    else if (!strcmp(operation, "set_border"))
    {
        if (onoff >= 0)
            group->cfg.set_border = onoff;
        else
            onoff = group->cfg.set_border;
    }
    else if (!strcmp(operation, "stick"))
    {
        if (onoff >= 0)
            group->cfg.stick = onoff;
        else
            onoff = group->cfg.stick;
    }
    else if (!strcmp(operation, "shade"))
    {
        if (onoff >= 0)
            group->cfg.shade = onoff;
        else
            onoff = group->cfg.shade;
    }
    else
    {
        IpcPrintf("Error: no such operation: %s\n", operation);
        onoff = -1;
    }

    if (onoff == 1)
        IpcPrintf("%s: on\n", operation);
    else if (onoff == 0)
        IpcPrintf("%s: off\n", operation);

    if (onoff >= 0)
        _GroupsSave();
}

static const IpcItem GroupsIpcArray[] = {
    {
     IPC_GroupOps,
     "group_op", "gop",
     "Group operations",
     "use \"group_op <windowid> <property> [<value>]\" to perform "
     "group operations on a window.\n"
     "Available group_op commands are:\n"
     "  group_op <windowid> cfg\n"
     "  group_op <windowid> start\n"
     "  group_op <windowid> add [<groupid>]\n"
     "  group_op <windowid> del [<groupid>]\n"
     "  group_op <windowid> break [<groupid>]\n"
     "  group_op <windowid> showhide\n" }
    ,
    {
     IPC_Group,
     "group", "grp",
     "Group commands",
     "use \"group <groupid> <property> <value>\" to set group properties.\n"
     "Available group commands are:\n"
     "  group info\n"
     "  group <groupid> info\n"
     "  group <groupid> del\n"
     "  group <groupid> iconify <on/off/?>\n"
     "  group <groupid> kill <on/off/?>\n"
     "  group <groupid> move <on/off/?>\n"
     "  group <groupid> raise <on/off/?>\n"
     "  group <groupid> set_border <on/off/?>\n"
     "  group <groupid> stick <on/off/?>\n"
     "  group <groupid> shade <on/off/?>\n" }
    ,
};

/*
 * Configuration items
 */
static const CfgItem GroupsCfgItems[] = {
    CFG_ITEM_BOOL(Conf_groups, dflt.iconify, 1),
    CFG_ITEM_BOOL(Conf_groups, dflt.kill, 0),
    CFG_ITEM_BOOL(Conf_groups, dflt.move, 1),
    CFG_ITEM_BOOL(Conf_groups, dflt.raise, 0),
    CFG_ITEM_BOOL(Conf_groups, dflt.set_border, 1),
    CFG_ITEM_BOOL(Conf_groups, dflt.stick, 1),
    CFG_ITEM_BOOL(Conf_groups, dflt.shade, 1),
};

extern const EModule ModGroups;

const EModule   ModGroups = {
    "groups", "grp",
    NULL,
    MOD_ITEMS(GroupsIpcArray),
    MOD_ITEMS(GroupsCfgItems)
};
