/* xbtuser.c -- main module for gbch-user

   Copyright 2009 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "config.h"
static  char    rcsid2[] = "@(#) $Revision: 1.11 $";
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef  HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <errno.h>
#ifdef  TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#elif   defined(HAVE_SYS_TIME_H)
#include <sys/time.h>
#else
#include <time.h>
#endif
#include <gtk/gtk.h>
#include "incl_sig.h"
#include "defaults.h"
#include "files.h"
#include "ecodes.h"
#include "errnums.h"
#include "statenums.h"
#include "incl_unix.h"
#include "incl_ugid.h"
#include "cfile.h"
#include "btmode.h"
#include "btuser.h"
#include "xbtu_ext.h"
#include "gtk_lib.h"

char    *Curr_pwd;

int     hchanges,       /* Had changes to default */
        uchanges;       /* Had changes to user(s) */

BtuserRef       ulist;
static  char    *defhdr;

#ifdef  NOTUSED
$P{Read adm full}
$P{Write adm full}
$P{Create entry full}
$P{Special create full}
$P{Stop sched full}
$P{Change default modes full}
$P{Combine user group full}
$P{Combine user other full}
$P{Combine group other full}
$P{Read mode name}
$P{Write mode name}
$P{Reveal mode name}
$P{Display mode name}
$P{Set mode name}
$P{Assume owner mode name}
$P{Assume group mode name}
$P{Give owner mode name}
$P{Give group mode name}
$P{Delete mode name}
$P{Kill mode name}
#endif

struct  privabbrev      privnames[] =
        {{      BTM_RADMIN,     $P{Read adm abbr}                       },
         {      BTM_WADMIN,     $P{Write adm abbr}                      },
         {      BTM_CREATE,     $P{Create entry abbr}                   },
         {      BTM_SPCREATE,   $P{Special create abbr}                 },
         {      BTM_SSTOP,      $P{Stop sched abbr}                     },
         {      BTM_UMASK,      $P{Change default modes abbr}           },
         {      BTM_ORP_UG,     $P{Combine user group abbr}             },
         {      BTM_ORP_UO,     $P{Combine user other abbr}             },
         {      BTM_ORP_GO,     $P{Combine group other abbr}            }};

#define NUM_PRIVS       sizeof(privnames) / sizeof(struct privabbrev)

GtkWidget       *toplevel,      /* Main window */
                *dwid,          /* Default list */
                *uwid;          /* User scroll list */

GtkListStore            *raw_ulist_store;
GtkTreeModelSort        *ulist_store;

static void  cb_about();

static GtkActionEntry entries[] = {
        { "FileMenu", NULL, "_File" },
        { "DefMenu", NULL, "_Defaults" },
        { "UserMenu", NULL, "_Users"  },
        { "HelpMenu", NULL, "_Help" },
        { "Quit", GTK_STOCK_QUIT, "_Quit", "<control>Q", "Quit and save", G_CALLBACK(gtk_main_quit)},
        { "Dpri", NULL, "Default _pri", "<shift>P", "Set default priorities", G_CALLBACK(cb_pris)},
        { "Dloadl", NULL, "Default _loadlevel", "<shift>L", "Set default load levels", G_CALLBACK(cb_loadlev)},
        { "Djmode", NULL, "Default job perms", "<shift>J", "Set default job permissions", G_CALLBACK(cb_jmode)},
        { "Dvmode", NULL, "Default var perms", "<shift>A", "Set default var permissions", G_CALLBACK(cb_vmode)},
        { "Dpriv", NULL, "Default privileges", "<shift>V", "Set default privileges", G_CALLBACK(cb_priv)},
        { "Defcpy", NULL, "Copy all", NULL, "Copy to all users", G_CALLBACK(cb_copyall)},

        { "upri", NULL, "_Priorities", "P", "Set priorities", G_CALLBACK(cb_pris)},
        { "uloadl", NULL, "_Loadlevel", "L", "Set load levels for users", G_CALLBACK(cb_loadlev)},
        { "ujmode", NULL, "_Job permissions", "J", "Set default job permissions for users", G_CALLBACK(cb_jmode)},
        { "uvmode", NULL, "_Var pPermissions", "A", "Set default var permissions for users", G_CALLBACK(cb_vmode)},
        { "upriv", NULL, "Privileges", "V", "Set privileges for users", G_CALLBACK(cb_priv)},
        { "copydef", NULL, "Copy defaults", NULL, "Copy defaults to selected users", G_CALLBACK(cb_copydef)},

        { "About", NULL, "About xbtuser", NULL, "About xbtuser", G_CALLBACK(cb_about)}  };

/* For when we run out of memory.....  */

void  nomem(const char *fl, const int ln)
{
        fprintf(stderr, "%s:Mem alloc fault: %s line %d\n", progname, fl, ln);
        exit(E_NOMEM);
}

char    *authlist[] =  { "John M Collins", NULL  };

static void  cb_about()
{
        GtkWidget  *dlg = gtk_about_dialog_new();
        char    *cp = strchr(rcsid2, ':');
        char    vbuf[20];

        if  (!cp)
                strcpy(vbuf, "Initial version");
        else  {
                char  *ep;
                cp++;
                ep = strchr(cp, '$');
                int  n = ep - cp;
                strncpy(vbuf, cp, n);
                vbuf[n] = '\0';
        }
        gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dlg), vbuf);
        gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dlg), "Xi Software Ltd 2008");
        gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dlg), "http://www.xisl.com");
        gtk_about_dialog_set_authors(GTK_ABOUT_DIALOG(dlg), (const char **) authlist);
        gtk_dialog_run(GTK_DIALOG(dlg));
        gtk_widget_destroy(dlg);
}

/*  Make top level window and set title and icon */

static void  winit()
{
        GError *err;
        char    *fn;

        toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_window_set_default_size(GTK_WINDOW(toplevel), 400, 400);
        fn = gprompt($P{xbtuser app title});
        gtk_window_set_title(GTK_WINDOW(toplevel), fn);
        free(fn);
        gtk_container_set_border_width(GTK_CONTAINER(toplevel), 5);
        fn = envprocess(XBTUSER_ICON);
        gtk_window_set_default_icon_from_file(fn, &err);
        free(fn);
        gtk_window_set_resizable(GTK_WINDOW(toplevel), TRUE);
        g_signal_connect(G_OBJECT(toplevel), "delete_event", G_CALLBACK(gtk_false), NULL);
        g_signal_connect(G_OBJECT(toplevel), "destroy", G_CALLBACK(gtk_main_quit), NULL);
}

gint  sort_userid(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
{
        guint   id1, id2;
        gtk_tree_model_get(model, a, UID_COL, &id1, -1);
        gtk_tree_model_get(model, b, UID_COL, &id2, -1);
        return  id1 < id2? -1:  id1 == id2? 0: 1;
}

gint  sort_username(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
{
        gchar   *name1, *name2;
        gint    ret = 0;

        gtk_tree_model_get(model, a, USNAM_COL, &name1, -1);
        gtk_tree_model_get(model, b, USNAM_COL, &name2, -1);

        if  (!name1  ||  !name2)  {
                if  (!name1  &&  !name2)
                        return  0;
                if  (!name1)  {
                        g_free(name2);
                        return  -1;
                }
                else  {
                        g_free(name1);
                        return  1;
                }
        }

        ret = g_utf8_collate(name1, name2);
        g_free(name1);
        g_free(name2);
        return  ret;
}

gint  sort_groupname(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer userdata)
{
        gchar   *name1, *name2;
        gint    ret = 0;

        gtk_tree_model_get(model, a, GRPNAM_COL, &name1, -1);
        gtk_tree_model_get(model, b, GRPNAM_COL, &name2, -1);

        if  (!name1  ||  !name2)  {
                if  (!name1  &&  !name2)
                        return  0;
                if  (!name1)  {
                        g_free(name2);
                        return  -1;
                }
                else  {
                        g_free(name1);
                        return  1;
                }
        }

        ret = g_utf8_collate(name1, name2);
        g_free(name1);
        g_free(name2);
        return  ret;
}

void  set_sort_col(int colnum)
{
        GtkTreeSortable *sortable = GTK_TREE_SORTABLE(ulist_store);
        GtkSortType      order;
        gint             sortid;

        if  (gtk_tree_sortable_get_sort_column_id(sortable, &sortid, &order) == TRUE  &&  sortid == colnum)  {
                GtkSortType  neworder;
                neworder = (order == GTK_SORT_ASCENDING)? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
                gtk_tree_sortable_set_sort_column_id(sortable, sortid, neworder);
        }
        else
                gtk_tree_sortable_set_sort_column_id(sortable, colnum, GTK_SORT_ASCENDING);
}

GtkWidget *wstart()
{
        char    *mf;
        GError *err;
        GtkActionGroup *actions;
        GtkUIManager *ui;
        GtkTreeSelection    *sel;
        GtkWidget  *vbox;
        int     cnt;

        actions = gtk_action_group_new("Actions");
        gtk_action_group_add_actions(actions, entries, G_N_ELEMENTS(entries), NULL);
        ui = gtk_ui_manager_new();
        gtk_ui_manager_insert_action_group(ui, actions, 0);
        gtk_window_add_accel_group(GTK_WINDOW(toplevel), gtk_ui_manager_get_accel_group(ui));
        mf = envprocess(XBTUSER_MENU);
        if  (!gtk_ui_manager_add_ui_from_file(ui, mf, &err))  {
                g_message("Menu build failed");
                exit(E_SETUP);
        }

        free(mf);
        vbox = gtk_vbox_new(FALSE, 0);
        gtk_container_add(GTK_CONTAINER(toplevel), vbox);
        gtk_box_pack_start(GTK_BOX(vbox), gtk_ui_manager_get_widget(ui, "/MenuBar"), FALSE, FALSE, 0);

        /* Create user display treeview */

        uwid = gtk_tree_view_new();
        for  (cnt = 0;  cnt <= P_COL-USNAM_COL;  cnt++)  {
                GtkCellRenderer     *renderer = gtk_cell_renderer_text_new();
                char    *msg = gprompt($P{xbtuser user hdr}+cnt);
                gtk_tree_view_column_set_resizable(
                        gtk_tree_view_get_column(GTK_TREE_VIEW(uwid),
                                                 gtk_tree_view_insert_column_with_attributes(
                                                        GTK_TREE_VIEW(uwid), -1, msg, renderer, "text", cnt+USNAM_COL, NULL) - 1),
                        TRUE);
                free(msg);
        }

        raw_ulist_store = gtk_list_store_new(12,
                                             G_TYPE_UINT, /* Index number we don't display */
                                             G_TYPE_UINT, /* User id which we don't display */
                                             G_TYPE_UINT, /* Group id which we don't display */
                                             G_TYPE_STRING, /* User name */
                                             G_TYPE_STRING, /* Group name */
                                             G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, /* Def/min/max pri */
                                             G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT, /* Load levs */
                                             G_TYPE_STRING);

        ulist_store = (GtkTreeModelSort *) gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(raw_ulist_store));

        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ulist_store), USNAM_COL, sort_username, NULL, NULL);
        gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ulist_store), GRPNAM_COL, sort_groupname, NULL, NULL);
        for  (cnt = DEFP_COL;  cnt <= P_COL;  cnt++)
                gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(ulist_store), cnt, sort_userid, NULL, NULL);

        /* set initial sort order */

        gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(ulist_store), DEFP_COL, GTK_SORT_ASCENDING);
        gtk_tree_view_set_headers_clickable(GTK_TREE_VIEW(uwid), TRUE);

        for  (cnt = 0;  cnt < 9;  cnt++)  {
                GtkTreeViewColumn   *col = gtk_tree_view_get_column(GTK_TREE_VIEW(uwid), cnt);
                g_signal_connect_swapped(G_OBJECT(col), "clicked", G_CALLBACK(set_sort_col), GINT_TO_POINTER(cnt+USNAM_COL));
        }

        sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(uwid));
        gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);

        /* Bit for display of default */

        dwid = gtk_text_view_new();
        gtk_text_view_set_editable(GTK_TEXT_VIEW(dwid), FALSE);

        defhdr = gprompt($P{xbtuser default string});

        /* Fetch privilege names from file */

        for  (cnt = 0;  cnt < NUM_PRIVS;  cnt++)  {
                privnames[cnt].priv_abbrev = gprompt(privnames[cnt].priv_mcode);
                privnames[cnt].priv_name = gprompt(privnames[cnt].priv_mcode + $P{Read adm full} - $P{Read adm abbr});
        }
        return  vbox;
}

static void  wcomplete(GtkWidget *vbox)
{
        GtkWidget  *paned, *scroll;

        gtk_tree_view_set_model(GTK_TREE_VIEW(uwid), GTK_TREE_MODEL(ulist_store));
        scroll = gtk_scrolled_window_new(NULL, NULL);
        gtk_container_set_border_width(GTK_CONTAINER(scroll), 5);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
        gtk_container_add(GTK_CONTAINER(scroll), uwid);
        paned = gtk_vpaned_new();
        gtk_box_pack_start(GTK_BOX(vbox), paned, TRUE, TRUE, 0);
        gtk_paned_pack1(GTK_PANED(paned), scroll, TRUE, TRUE);
        gtk_paned_pack2(GTK_PANED(paned), dwid, FALSE, TRUE);
        gtk_widget_show_all(toplevel);
}

void  upd_udisp(BtuserRef uitem, GtkTreeIter *iter)
{
        GString  *privstr = g_string_new(NULL);

        if  (uitem->btu_priv)  {
                int  cnt;
                for  (cnt = 0;  cnt < NUM_PRIVS;  cnt++)
                        if  (uitem->btu_priv & privnames[cnt].priv_flag)  {
                                if  (privstr->len != 0)
                                        g_string_append_c(privstr, '|');
                                g_string_append(privstr, privnames[cnt].priv_abbrev);
                        }
        }

        gtk_list_store_set(raw_ulist_store, iter,
                           DEFP_COL,    (guint) uitem->btu_defp,
                           MINP_COL,  (guint) uitem->btu_minp,
                           MAXP_COL,  (guint) uitem->btu_maxp,
                           MAXLL_COL,  (guint) uitem->btu_maxll,
                           TOTLL_COL,  (guint) uitem->btu_totll,
                           SPECLL_COL,  (guint) uitem->btu_spec_ll,
                           P_COL,       privstr->str,
                           -1);
        g_string_free(privstr, TRUE);
}

void  update_all_users()
{
        GtkTreeIter  iter;
        gboolean  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(raw_ulist_store), &iter);

        while  (valid)  {
                unsigned  indx;
                gtk_tree_model_get(GTK_TREE_MODEL(raw_ulist_store), &iter, INDEX_COL, &indx, -1);
                upd_udisp(&ulist[indx], &iter);
                valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(raw_ulist_store), &iter);
        }
}

void  update_selected_users()
{
        GtkTreeSelection *selection;
        GList  *pu, *nxt;

        selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(uwid));
        pu = gtk_tree_selection_get_selected_rows(selection, NULL);
        for  (nxt = g_list_first(pu);  nxt;  nxt = g_list_next(nxt))  {
                GtkTreeIter  iter, citer;
                unsigned  indx;
                if  (!gtk_tree_model_get_iter(GTK_TREE_MODEL(ulist_store), &iter, (GtkTreePath *)(nxt->data)))
                        continue;
                gtk_tree_model_get(GTK_TREE_MODEL(ulist_store), &iter, INDEX_COL, &indx, -1);
                gtk_tree_model_sort_convert_iter_to_child_iter(ulist_store, &citer, &iter);
                upd_udisp(&ulist[indx], &citer);
        }
        g_list_foreach(pu, (GFunc) gtk_tree_path_free, NULL);
        g_list_free(pu);
}

void  udisplay()
{
        unsigned  ucnt;
        GtkTreeIter   iter;

        for  (ucnt = 0;  ucnt < Npwusers;  ucnt++)  {

                /* Add reow to store.
                   Put in index and uid which udisp doesn't do */

                gtk_list_store_append(raw_ulist_store, &iter);
                gtk_list_store_set(raw_ulist_store, &iter,
                                   INDEX_COL, (guint) ucnt,
                                   UID_COL, (guint) ulist[ucnt].btu_user,
                                   USNAM_COL, prin_uname((uid_t) ulist[ucnt].btu_user),
                                   -1);
                gtk_list_store_set(raw_ulist_store, &iter,
                                   GID_COL, (guint) lastgid,
                                   GRPNAM_COL, prin_gname((gid_t) lastgid),
                                   -1);
                upd_udisp(&ulist[ucnt], &iter);
        }
}

void  defdisplay()
{
        GString  *defstr = g_string_new(NULL);
        g_string_printf(defstr, "%s Def pri %d min %d max %d\nMax ll %u Tot ll %u Specll %u",
                 defhdr, Btuhdr.btd_defp, Btuhdr.btd_minp, Btuhdr.btd_maxp, Btuhdr.btd_maxll, Btuhdr.btd_totll, Btuhdr.btd_spec_ll);
        gtk_text_buffer_set_text(gtk_text_view_get_buffer(GTK_TEXT_VIEW(dwid)), defstr->str, -1);
        g_string_free(defstr, TRUE);
}

/* Ye olde main routine.  */

MAINFN_TYPE  main(int argc, char **argv)
{
        BtuserRef       mypriv;
        GtkWidget  *vbox;

        versionprint(argv, "$Revision: 1.11 $", 0);

        if  ((progname = strrchr(argv[0], '/')))
                progname++;
        else
                progname = argv[0];

        init_mcfile();

        Realuid = getuid();
        Realgid = getgid();
        Effuid = geteuid();
        Effgid = getegid();
        if  ((LONG) (Daemuid = lookup_uname(BATCHUNAME)) == UNKNOWN_UID)
                Daemuid = ROOTID;
        Cfile = open_cfile("XBTUSERCONF", "xmbtuser.help");

        /* If we haven't got a directory, use the current */

        if  (!Curr_pwd  &&  !(Curr_pwd = getenv("PWD")))
                Curr_pwd = runpwd();

#ifdef  HAVE_SETREUID
        setreuid(Daemuid, Daemuid);
#else
        setuid(Daemuid);
#endif
        gtk_init(&argc, &argv);

        winit();

        mypriv = getbtuentry(Realuid);

        if  (!(mypriv->btu_priv & BTM_WADMIN))  {
                doerror($EH{No write admin file priv});
                exit(E_NOPRIV);
        }
        ulist = getbtulist();
        vbox = wstart();
        defdisplay();
        udisplay();
        wcomplete(vbox);
        gtk_main();
        if  (uchanges || hchanges)
                putbtulist(ulist);
        return  0;
}
